在之前,我们写的C语言都是面向过程的,需要什么样的操作,我们就给他什么样的操作,而现在,C++引入了类, 可以面向对象,同时关注的也是对象,靠对象之间的交互来完成事件。
目录
一、类的定义
二、类的作用域
三、类对象的大小
四、this指针
五、构造函数
编辑六、析构函数
七、拷贝构造函数
八、运算符重载
C语言的结构体只能定义变量,不能定义函数,C++中的结构体变量和函数都能定义。同时C++新增了关键字class,可以使用class来代替struct
如上图所示,我们定义了一个sleep函数,还有一个char数组和一个int来描述Person这个类。这里也引入了public和private这两个访问限定符
我们可以在类外面访问被public修饰的内容,但是无法访问被private修饰的内容,被private修饰的内容只能在这个类里面访问。
同时还有一个protected访问限定符,我们使用的较少,在继承和多态里面会用到,到时候我们再详细讲解,对于目前,我们只需了解protected修饰成员在类外也不能直接访问即可。
C++ class类默认的访问权限为private,也就是如果你不写访问限定符,默认的权限就是private,而struct默认访问权限是public,因为要兼容C语言。
如果我们再类里面只给到了函数声明,没有给定义,我们想在类外面给定义,那么不能直接给函数定义,如下
这会使得编译器报错,因为编译器不知道你这个sleep是类里面的sleep,会误以为他是一个单独的函数,我们要想让编译器认识,就得加上 :: 作用域操作符,来说明是那个作用域的。
在类里面,函数是不增加类对象的大小的,因为所有类成员是共用的同一份函数,我们把类的函数放在公共代码区,这样可以避免空间的浪费
通过反汇编也可以看到,函数的地址是一样的,因此我们只需要看变量的空间大小即可,这跟结构体是一模一样的运算方法。
结构体内存对齐规则
- 第一个成员在与结构体偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的对齐数为8
- 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
通过这套方法就可以知道类对象的大小了
还有一种特殊情况,就是类里面没有成员变量,如下
这个时候类大小也得分配一个字节,代表站位。如果你新建了一个类对象,如果字节为0就没有为你开辟空间,这个对象也就不存在了,因此分配一个字节能代表这个对象存在。
this指针是C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量” 的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
因此我们可以用函数里使用this来指向类里面的所有参数,如下。
因此我们可以用this来区分类里面的参数和函数参数。当然在实际开发过程中,最好将函数参数和类里面的参数做一下区分,防止混淆。下面只是方便举例子。
this指针特性
- this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
- 只能在“成员函数”的内部使用
- this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给 this形参。所以对象中不存储this指针。
- this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传 递,不需要用户传递。
同时还需要注意,当我们将类对象定义为空时,去调用类里面的函数,如果函数内部有this指针参与时,去调用便会报错,因为this指针为空。
如果没有变量或函数需要调用,那么this就算为nullptr,他不会去解引用,便不会有问题。
构造函数是特殊的成员函数,是在构造对象的时候自动调用初始化对象的
构造函数有以下四个特性:
1. 函数名与类名相同。
2. 无返回值(void都不需要写)。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载(可以写很多个初始化方式,带参、无参等)。
5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
6. 如果成员变量全是自定义类型的成员,可以考虑不写构造函数,因为编译器会自动去调用这个成员的构造函数
7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。 注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数(不传参就可以调用的叫做默认构造函数)。
有了构造函数之后,当我们创建类对象的时候,会自动根据构造函数进行初始化,这可以避免忘记初始化的情况。
同时我们可以对构造函数重载,可以使用让函数带上参数,当我们构造类对象时,可以再对象后添加上括号和参数,类似于函数调用,便会调用你所重载的构造函数,这点也是非常方便的。
我们还可以使用缺省函数写构造函数,通过给缺省值,来完成上面的重载。你给了值就用你给的值,你没给值我就使用缺省值。
构造函数是构建时自动调用,析构函数是类对象生命周期结束时自动调用的,目的是为了完成清理工作。比方说你的变量中有指针,动态开辟的数组什么的,需要把他的空间给free或者delete掉,就可以通过析构函数来帮我们处理。
析构函数特性:
1. 析构函数名是在类名前加上字符 ~(寓意取反)。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
5. 默认析构函数内置类型成员不会处理,自定义类型成员会调用这个成员的析构函数
析构函数还有一个特点,类对象是存在栈帧里面的,因此析构时会后进先出。由下图我们可以看到d2析构函数已经调用完并且打印了,现在调用的是d的析构函数。
拷贝构造函数只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
拷贝构造函数特性:
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用
3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对内置类型进行浅拷贝,对自定义类型会去调用自定义类型的拷贝。
拷贝构造函数编译器会自动帮我们生成, 但是帮我们生成的是浅拷贝,内容只会在内存里存在一份,如果是普遍变量还好,但如果是指针变量,并且我们在构造函数时为指针变量开辟了空间,那么发生拷贝构造后,析构函数开始执行,拷贝的内容开始析构,将存在堆上的数据进行释放,但是你原本的类对象依然指向了被释放的空间,就会发生内存泄漏的问题。
下图执行到了,func函数,func函数拷贝构造了一下类对象a,func函数完成后就会析构对象a,因此会将a对象指向的_a空间进行free,free后_a指向的内容便会还给系统。
执行到main函数结束时,我们新建的类对象a也会进行析构,这里便会发生内存泄漏了,明明还给了系统,你却还指向着他,因此便会发生内存泄漏。
这说明默认拷贝构造不足以去释放开辟在堆上的空间,我们需要自己去写拷贝构造并进行深拷贝。
深拷贝是将开辟出来的空间按照之前的大小再开辟一份,给到拷贝的类对象,到时候进行析构会自己析构自己的空间。(现在是两个指针指向的两份空间了)
注意:拷贝构造函数一定要加上引用,因为如果参数是一个类,便会调用类的拷贝构造进行传参,如果不加上引用,拷贝构造的参数也是类对象,便会一直重复循环进行拷贝构造,不会进入到函数体内部,加上引用,便是之前类的别名,就不会再进行拷贝构造了。
同时,由于是引用,我们在拷贝构造函数内部修改类对象,也会对外部有影响,但是拷贝构造的目的仅仅是拷贝,不会修改值,因此我们最好还要加上const防止修改最合理
A(const A& a)
{
_a = (int*)malloc(sizeof(int) * a._capacity);
memcpy(_a, a._a, a._size * (sizeof(int)));
_capacity = a._capacity;
}
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其 返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
因为自定义类型不支持运算符的操作(除了默认生成赋值重载函数),编译器不知道你想做什么,按照什么方式做,因此如果想要进行类的运算符操作,就需要重载运算符
函数原型:返回值类型 operator操作符(参数列表)
如下,我们对日期类的运算符 < 进行了重载。
bool operator<(const Date& d)
{
if (_year < d._year){
return true;
}
else if(_year == d._year && _month
可以直接使用 < 符号来进行日期类的比较,非常方便
下面两种调用方式是一样的,但是我们通常使用第一种方式更为方便。
注意:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐 藏的this
- .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
对于其他操作符的重载也是类似的,按照你想要的方式去重载就好了。
后面我们会使用所学的类的知识去写一个日期类。