21天学会C++:Day9----初识类与对象

news2025/1/12 6:41:40

· CSDN的uu们,大家好。这里是C++入门的第九讲。
· 座右铭:前路坎坷,披荆斩棘,扶摇直上。
· 博客主页: @姬如祎
· 收录专栏:C++专题

目录

 1. 面向过程与面向对象

2. 类的定义

3. 类中的访问限定符

3.1 访问限定符的作用

4. 对象的实例化以及对象的大小

5. this指针

5.1 this指针的引入

5.2 VS中this指针的优化

5.3 考验你对this指针理解的两道题


 1. 面向过程与面向对象

我们之前学习的C语言是面向过程的语言,而我们正在学习的C++是面向对象的语言(OOP语言)。之前提到过,C++是对C语言的补充和改进。那么为什么C++要引入面向对象的概念呢?面向对象又比面向过程好在哪里呢?

面向过程,关注的是解决问题的步骤,将每一个功能都抽象出一个个具体的函数,然后逐步解决问题。

例如:我们在家洗衣服,站在面向过程的角度来看是这个样子:

通过一个一个的步骤来解决问题。在编程中就是通过一个一个的函数来实现。

面向对象:在解决问题的过程中,面向对象的思想是将解决该问题中的事物看作一个一个的对象,对象之间各司其职,达到解决问题的目的。

例如:在洗衣的过程中,人这个对象只需要将衣服放进洗衣机,等洗衣机洗好之后取出来即可。不需要关注洗衣机是怎么洗衣服的。 

现在来分析面向对象与面向过程的优缺点还是太早了。我们只要有一个大致印象,面向对象比面向过程更加高级,面向对象能够更加方便的将问题模块化, 更好的解决问题。但是相比于面向对象,面向过程的代码执行效率较高。

2. 类的定义

我们知道C++是兼容C语言的,C语言的结构体里面是不能定义函数的。但是C++的结构体里面是可以定义函数的,因为C++将结构体升级成为了类,像这样:

struct A
{
	int _a;

	void func()
	{
		cout << "func" << endl;
	}
};

int main()
{
	struct A a1;
	return 0;
}

C++将结构体升级成为了类,那么结构体的名字就是类型的名字,因此在C++中定义结构体是不需要加上struct的。

struct A
{
	int _a;

	void func()
	{
		cout << "func" << endl;
	}
};

int main()
{
	struct A a1;
    A a1; //c++不用加struct
	return 0;
}

在C++里面更喜欢用class代替struct来定义一个类,于是我们顺理成章地推导出了class定义类的方法:

class 类名

{

        // 类的主体,包括成员变量和成员函数

};

注意:分号不能少。

// 定义一个类
class B
{
	int _b;

	void func()
	{
		cout << "func" << endl;
	}
};

上面的代码我们定义了一个类 B。 类中的变量(_b),叫做成员变量,类中定义的函数(func) 叫做成员函数 或者方法

C++规定:在类中的定义的函数,会被自动地视为inline函数,但是他最后是不是内联函数,还是取决于编译器。

注意:是在类中定义的函数,如果你是在类内声明,类外定义,不会被视为内联函数。

struct B
{
	int _b;

	void func() //函数的声明定义均在类里面
	{
		int a = 10;
	}

	void func(int a);
};

void B::func(int a)
{
	int b = 10;
}

int main()
{
	B b;
	b.func();
	b.func(1);

	return 0;
}

上面的代码,func() 函数就是在类内定义的函数, 而func(int a) 则是在类外定义的函数,通过调试观察汇编代码,我们可以看到func()已经是一个内联函数了。

关于内联函数的细节:21天学会C++:Day6----内联函数_姬如祎的博客-CSDN博客

这里还有一个要注意的点:类中函数类外定义的写法,需要加上类名和域作用限定符,告诉编译器这是这个类里面的函数的实现。不然可能会与全局域的函数冲突。

 

 class中的所有成员变量都在其所在的那个类域里面,这样做是理所应当的。

3. 类中的访问限定符

我们用struct定义了一个类 A,用class定义了一个类 B,创建一个变量之后。访问其各自的成员变量。发现struct定义的类可以直接访问,但是class定义的类不能直接访问成员变量。这是为啥呢?

struct A
{
	int _a;

	void func()
	{
		cout << "func" << endl;
	}
};

class B
{
	int _b;

	void func()
	{
		cout << "func" << endl;
	}
};

int main()
{
	A a;
	a._a = 10;

	B b;
	b._b = 10;
	return 0;
}

 这是因为C++中每一个类中的成员都会受到访问限定符的限制。我们来看看C++中的访问限定符有哪些:

其中public表示类成员在类内类外都可以访问;protected,private均是类成员在类内可以访问,在类外不可以访问。protected 与 private之间的区别需要我们学到继承的时候再讲。

于是我们就可以得出结论,在没有写访问限定符的时候,struct定义的类默认访问限定符是public;class定义的类默认访问限定符是private。

访问权限的作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。没有下一个访问限定符就是到类的结束位置。

3.1 访问限定符的作用

这里需要uu们回忆一下我们在使用C语言实现的数据结构,无论哪一个都行。我们就拿栈来说吧,我们当时定义的栈是这样的:

C语言数据结构初阶(5)----栈_姬如祎的博客-CSDN博客


//栈的数据类型
typedef int STDataType;
//栈的结构体,类比顺序表
typedef struct Stack
{
    STDataType* a; //栈的顺序存储的数组
    int top; //栈的top元素
    int capacity; //数组的容量
} ST;

这其中有一个top变量,我们在在实现栈的时候,提到过top可以指向栈顶元素或者栈顶元素的下一个位置,这取决于设计者的实现方式。

因为C语言结构体中的数据是公开的,于是,就会有程序员在访问栈顶的元素时写出这样的代码:

int main
{
    ST s;
    s.a[top];
}

是的,他不调用你实现的访问栈顶元素的函数,而是直接通过你的底层,直接访问数据。如果恰巧你实现的栈关于top的定义是实现方式2,碰巧他还是一个脾气暴躁的程序员,当他看到访问top的时候出现了随机值,他可能会直接破口大骂,这是谁写的 laji 代码。C语言没有常见数据结构的库也有一部分原因是这个吧。

没有访问限定符的限制,用户可以直接访问并修改任意数据,造成意料之外的结果,甚至导致程序崩溃。而有了访问限定符就能很好的解决这些问题。

我们只需要将stack的底层数据用private修饰就能很好的解决这些问题。同时将对应操作的函数用public修饰,提供对外接口供用户使用。


class Stack
{
public:
	void StackInit()
	{
		//
	}

	void StackPush(int val)
	{
		//
	}

	//等等

private:
	int* _a;
	int _capacity;
	int top;
};

这就是封装的具体表现了。

封装是面向对象的三大特性之一,另外两个是继承和多态。继承和多态后面讲解。

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。 封装本质上是一种管理,让用户更方便使用类。

4. 对象的实例化以及对象的大小

思考这样一个问题:你定义好了一个类,他是否在内存中占有空间呢?

答案显然是否定的,因为类是对象的描述,类在定义好时并不分配内存空间,只有在类实例化出对象之后,这个对象占有空间。我们完全可以把类当作一张图纸,而对象就是根据图纸生产出来的商品。

class A
{
public:

	void func()
	{
		cout << "func" << endl;
	}

private:
	int _a;
	int* _p;
};

int main()
{

    A a;
    return 0;
}

 那么我们应该如何计算对象的大小呢?拿上面的代码来说,sizeof(a)结果是什么呢?

(这里写sizeof(A) 也行,根据对象能算出大小,根据类肯定也行撒,类比图纸与商品)

在32位机器下,结果是8。对象大小的计算方式和结构体大小的计算是一样的,都遵循内存对齐。

但是,你可能会问,成员函数存在哪里呢?对象里面没有成员函数是怎么调用的呢?

在回答这些问题之前,我们先来思考。不同的对象调用类中的函数是调用的同一个吗?没错调用的就是同一个。

既然所有的对象都会调用相同的成员函数,那么为什么还要浪费空间在每个对象里面存一份函数的地址呢,因此类的成员函数是存在公共代码段的,源文件编译的时候编译器会找到函数的地址,我们不必关心。

既然对象里面只存储成员变量,那要是我定义的类里面没有成员变量,那他还会有空间吗?

我们看到结果是1,如果没有成员变量就没有空间的话,我们应该用什么来表征这个对象的存在呢?因此即使没有成员变量的类,实例化出的对象也是有空间的,至于具体的大小,依编译器而定。 

5. this指针

5.1 this指针的引入

我们创建了一个Date 类,类的成员变量用来存储年月日。InitiDate函数用来初始化一个Date对象的日期,ShowDate用来打印Date对象表示的日期。

class Date
{
public:
	void InitDate(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void ShowDate()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

private:
	int _year;
	int _month;
	int _day;

};

int main()
{
	Date d1;
	d1.InitDate(2004, 01, 01);
	d1.ShowDate();

	Date d2;
	d2.InitDate(2008, 01, 01);
	d2.ShowDate();
}

我们上一个小节讲到了一个类实例化出来一个对象就会开辟一份属于自己空间,那么上述代码中的d1,d2是两个不同的对象,也就各自拥有一份空间来存储各自表示的日期。但是类的成员函数只有一份,他是怎么做到不同的对象调用同一个函数时,找到不同空间中的数据(d1调用ShowDate打印的是d1中存储的信息,d2调用ShowDate打印的是d2中存储的信息)的呢?

这就得讲讲我们的this指针了,C++语法规定,对于非静态成员函数(没有加static修饰的成员函数),编译器会对成员函数做处理:在普通成员函数中加了一个隐藏的指针参数,让这个指针指向当前对象( 正在调用这个函数的对象,d1调用ShowDate,this指向的就是d1这个对象)。需要注意的是this指针不能在形参和实参显示传递,但可以在函数内部显示使用。

例如,ShowDate函数可以这样写:

void ShowDate()
{
	cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
}

现在我们就能理解为什么同一个函数能访问不同的空间了吧,因为隐藏传递了一个this指针,打印_year等变量,实际上是通过传递过来的this指针找到调用该函数的对象中的数据。因此才能做到一个函数访问两块空间。

那这里我就要问一个问题了,各位uu觉得this指针存储在哪里呢?

A:对象    B:栈    C:堆

答案:B。

A:如果this指针存在对象中,那么我们刚才计算对象大小的时候并没有计算this指针哇,因此排除a。

我们再来看看this的定义嘛,this是对象这个实参传递过来的,那么this就是形参撒,uu们形参当然是存在栈中的撒。因此this就是存在栈中的!!!

5.2 VS中this指针的优化

我们来看看VS对this指针的优化:我们通过反汇编来看看this指针的传递

我们可以看到,VS中直接将this指针存在了寄存器中,我们都知道寄存器的读写速度是非常快的,在类的内部,我们需要大量访问成员变量。而访问成员变量本质上是通过this指针来访问的。因此将他存储到ecx寄存器中能够提高访问的效率。 

5.3 考验你对this指针理解的两道题
 

// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
	void Print()
	{
		cout << "Print()" << endl;
	}
private:
	int _a;
};

int main()
{
	A* p = nullptr;
	p->Print();
	return 0;
}
// 2.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
	void PrintA()
	{
		cout << _a << endl;
	}
private:
	int _a;
};


int main()
{
	A* p = nullptr;
	p->PrintA();
	return 0;
}

我们看到这两道题都是通过一个空对象去调用函数,区别是在函数中是否访问对象中的成员变量。

答案:1:运行正常

           2:运行崩溃

在调用类成员函数的时候,会隐士传递this指针,都传递的是一个空指针,题目一并没有对this指针解引用,但是题目二确尝试解引用当问成员变量。因为操作成员变量的本质都是通过this指针,因此题目二会发生空指针的解引用,引起程序崩溃。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1017069.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux网络编程:使用UDP和TCP协议实现网络通信

目录 一. 端口号的概念 二. 对于UDP和TCP协议的认识 三. 网络字节序 3.1 字节序的概念 3.2 网络通信中的字节序 3.3 本地地址格式和网络地址格式 四. socket编程的常用函数 4.1 sockaddr结构体 4.2 socket编程常见函数的功能和使用方法 五. UDP协议实现网络通信 5.…

SSL双向认证-Nginx配置

SSL双向认证需要CA证书&#xff0c;开发过程可以利用自签CA证书进行调试验证。 自签CA证书生成过程&#xff1a;SSL双向认证-自签CA证书生成 Nginx配置适用于前端项目或前后端都通过Nginx转发的时候&#xff08;此时可不配置后端启用双向认证&#xff09; 1.Nginx配置&#…

20230908_python练习_selenium模块爬取网页小说练习

霍比特人小说爬取&#xff0c;使用 selenium 模块调用谷歌浏览器&#xff0c;无界面模式爬取小说网站信息&#xff0c;将数据按照每次2000字符在mysql中保存。 # https://www.shukuai9.com/b/324694/ # 导入需要的库 from selenium import webdriver # 导入Keys模块&#xff…

AlteraXilinx公司FPGA简介

Intel / Altera公司 Intel/Altera 系列FPGA简介 - 知乎 (zhihu.com) Altera FPGA 提供了多种可配置嵌入式 SRAM、高速收发器、高速 I/O、逻辑模块以及布线。其内置知识产权 (IP) 结合优秀的软件工具&#xff0c;缩短了 FPGA 开发时间&#xff0c;降低了功耗和成本。 Altera FP…

五、数学建模之层次分析法

1.概念 2.例题 一、概念 1.提出 层次分析法&#xff08;Analytic Hierarchy Process&#xff0c;AHP&#xff09;是一种多标准决策分析方法&#xff0c;用于帮助人们在面对复杂的决策问题时进行定量和定性的比较和评估。它最初由美国运筹学家和管理学家托马斯萨蒙&#xff08…

[字符串和内存函数]strcat字符串函数的详解和模拟

strcat函数 strcat函数是C语言中用于将一个字符串追加到另一个字符串末尾的函数。其函数原型如下&#xff1a; char *strcat(char *dest, const char *src);其中&#xff0c;dest是目标字符串&#xff0c;src是要追加的字符串。函数将src中的内容追加到dest的末尾&#xff…

软件测试的基础知识

目录 前言 软件测试的生命周期 如何描述一个bug 如何定位bug的级别 bug的生命周期 和开发人员产生争执怎么办 设计一个测试用例 前言 上篇文章主要写了软件测试的一些基本概念以及软件测试的前置知识,这篇文章主要带大家了解在进行软件测试之前要准备的工作. 软件测试…

常见的数码管中的引脚分布情况

简单介绍 数码管&#xff0c;实际就是用了7段亮的线段表示常见的数字或字符。常见的像下面几种&#xff08;图片是网络中的截图&#xff09;。事件中使用到的知识还是单片机中最基础的矩阵扫描。记得其中重要的有“余晖效应”&#xff0c;好像是要把不用的亮段关闭&#xff0c…

【C++】string 之 assign、at、append函数的学习

前言 在学习string类的过程中&#xff0c;我发现了assign这个函数&#xff0c;感觉很有用&#xff0c;就来记录一下 assign函数原型&#xff1a; void assign(size_type n, const T& x T());void assign(const_iterator first, const_iterator last);assign函数有两种使…

格林公式推导

∫ D ∫ ( ∂ Q ∂ x − ∂ P ∂ y ) d x d y ∮ P d x Q d y \int _D \int (\frac{\partial Q}{\partial x} - \frac{\partial P}{\partial y})dx dy \oint P dx Qdy ∫D​∫(∂x∂Q​−∂y∂P​)dxdy∮PdxQdy 证明&#xff1a; 假设 ∫ Q ( x , y ) d y 的原函数是 q …

linux万字图文学习进程信号

1. 信号概念 信号是进程之间事件异步通知的一种方式&#xff0c;属于软中断。 1.1 linux中我们常用Ctrlc来杀死一个前台进程 1. Ctrl-C 产生的信号只能发给前台进程。一个命令后面加个&可以放到后台运行,这样Shell不必等待进程结束就可以接受新的命令,启动新的进程。2. S…

Git的ssh方式如何配置,如何通过ssh方式拉取和提交代码

git的ssh配置 HTTPS和SSH的区别设置SSH方式配置单个仓库配置账户公钥 大家通过git拉取代码的时候&#xff0c;一般都是通过http的方式&#xff0c;简单方便。但是细心的童鞋肯定也注意到Git也是支持ssh方式的。可能很多人也试过使用这个方式&#xff0c;但是好像没有那么简单。…

企业架构LNMP学习笔记57

MongoDB的安全设置&#xff1a; 安全&#xff1a; MongoDB的安全事件&#xff1a; 2017年年初&#xff1a; 利用SMB漏洞可以获得系统最高权限。wannacry勒索病毒。当年确实遇到过这个事情。 比特币的概念&#xff1a;开源软件 P2P网络 P2P形式的数字货币。交易记录公开透明。…

Mysql--Java的JDBC编程

Java的数据库编程&#xff1a;JDBC JDBC&#xff0c;即Java Database Connectivity&#xff0c;java数据库连接。是一种用于执行SQL语句的Java API&#xff0c;它是 Java中的数据库连接规范。 下载驱动包作为项目的依赖&#xff0c;数据库驱动包的版本要和数据库服务器的版本…

前端中的跨域请求及其解决方案

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 跨域&#xff08;Cross-Origin&#xff09;⭐CORS&#xff08;跨域资源共享&#xff09;⭐JSONP&#xff08;JSON with Padding&#xff09;⭐代理服务器⭐ WebSocket⭐服务器设置响应头⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a…

Spring + vue 项目部署(全网最详细教程_含内网穿透部署)

本项目以Springboot 2.7.11和vue2做参考示例 第一步 展示前后端代码的成品 前端Vue 后端Java 项目写完后&#xff0c;差不多就是这个样子&#xff0c;仅供参考&#xff01; 第二步 开始打包前后端项目 前端打包的方式有以下几种&#xff1a; 方法1: #直接打包&#xff0…

【数据结构】【C++】红黑树RBTree的模拟实现(平衡搜索二叉树)

【数据结构】&&【C】红黑树的模拟实现(平衡搜索二叉树&#xff09; 一.红黑树的性质二.红黑树的模拟实现1.结点的定义2.搜索树的插入3.变色向上处理4.旋转变色 三.红黑树与AVL树的差别四.完整代码 一.红黑树的性质 1.什么是红黑树&#xff1f; 红黑树是一种搜索二叉树…

页面上下左右滑动事件

1.下载插件 npm install vue-touchnext -S 2.main.js加入以下代码 import VueTouch from vue-touch Vue.use(VueTouch, { name: v-touch }) VueTouch.config.swipe { threshold: 50 //设置左右滑动的距离 } 3.完整代码 <template><div><v-touch swipe…

Java 基础——运行第一个Java程序

【学习笔记】Java 基础——运行第一个Java程序 关键词&#xff1a;Java、Spring Boot、Idea、数据库、一对一、培训、教学本文主要内容是在IDEA中运行第一个Java程序&#xff1a;Hello World计划30分钟完成&#xff0c;请同学尽量准备工具有学习需要请联系&#xff1a;xujian_…

css多个物体椭圆旋转

实现效果 html代码 <div class"background-img"><div class"area"><div class"ball ball1"></div><div class"ball ball2"></div><div class"ball ball3"></div><div …