【C++】类与对象(2)
作者:爱写代码的刚子
时间:2023.5.4
本篇博客有关构造函数、析构函数、拷贝构造的知识,由于本篇博客可能比较详细,还剩一些内容没介绍,所以我将剩余的知识放在下一篇博客。
目录
- 【C++】类与对象(2)
- 类的6个默认成员函数
- 构造函数
- 特性
- 析构函数
- 拷贝构造函数
- 特征
类的6个默认成员函数
构造函数
能否在对象创建时,不去调用成员函数,就能将信息设置进去呢?于是就有了构造函数。
构造函数:构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。
特性
构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
其特征如下:
-
函数名与类名相同。
-
无返回值(也不需要写void)。
-
对象实例化时编译器自动调用对应的构造函数。
-
构造函数可以重载。(可以有多个形式的构造函数)
-
如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
注意:
自动生成的无参的默认构造函数不能很好地初始化,如图:
- 编译器默认生成的构造函数对于内置类型不做初始化,对于自定义类型, 则调用自定义类型的构造函数(不是所有的编译器都这样,vs2019中如果仅含内置类型,编译器不做初始化,如果含有自定义类型,编译器才会将内置类型初始化为0,自定义类型调用自己的构造函数)
总结:只要成员变量含内置类型,必须自己实现构造函数进行初始化,不用编译器生成的(为了统一,因为不同编译器处理不同),如果成员变量全都为自定义类型,可以考虑让编译器自动生成默认构造函数,前提是这些自定义类型都定义了默认构造函数。
对于编译器初始化不统一在这里举一个例子:
vs2019中:
VS2019编译器进行了优化,当类的成员变量中有指针类型时,会将所有的普通成员变量初始化。
vs2017中:
即使成员变量中含有指针类型,编译器也不会进行初始化。
所以编译器之间的初始化并不统一。
注:所以这相当于C++的一个缺陷(编译器对于成员变量的初始化不统一),针对这个缺陷,C++11标准发布的时候打了一个补丁,对于成员变量可以给缺省值。
- 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。
注意:无参的构造函数和全缺省的构造函数只能存在一个,若同时存在调用将会产生冲突:
- 不传参编译器不知道调用无参的构造函数或全缺省的构造函数,因为两者在语法上形式相同,无法区分。
-
C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如int/char…,自定义类型就是我们使class/struct/union自己定义的类型,编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。
-
成员变量的命名风格。一般在成员变量前加_或成员变量后加_。
注意:无参数传递的构造函数在写法上不加(),防止语法上和函数的声明发生冲突。
test a;//正确
test a();//错误
析构函数
析构函数:对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。(可以利用析构函数来防止内存泄漏,局部对象的销毁工作一般由编译器完成的)
析构函数是特殊的成员函数。
特征:
其特征如下:
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值。
- 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。(析构函数不能重载)
同构造函数,系统自动生成的默认析构函数并不能很好地进行资源的清理工作(析构函数对内置类型不做处理,如果有自定义类型,自定义类型将调动自己的析构函数)所以当类里面只有自定义类型时可以使用系统自动生成的析构函数,其他情况需要我们自己实现析构函数 - 对象生命周期结束时,C++编译系统系统自动调用析构函数。
- 编译器生成的默认析构函数,对会自定类型成员调用它的析构函数析构函数防止内存泄漏:
对象生命周期结束后调用析构函数将空间销毁,不用手动进行操作。
总结:
1.一般情况下,有动态申请资源,就需要显示写析构函数释放资源
2.没有动态申请的资源,不需要写析构
3.需要释放资源的成员都是自定义类型,不需要写析构
4.析构函数满足后进先出,后定义的先析构
拷贝构造函数
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
特征
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。(自定义类型传参(如果是传值传参)是必须要调用拷贝构造的,内置类型就是直接拷贝)
拷贝构造函数内的参数最好使用const修饰,防止拷贝后破坏了原来的对象。
补充:(上面图片里使用this指针是防止产生歧义,左边的b可以是test类型的b或类成员变量的b,这里编译器显示为前者,所以不加this会报错)
3. 若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。(对于内置类型按照字节序拷贝,对于自定义类型将调用自己的拷贝构造函数)
4. 编译器生成的默认拷贝构造函数不能运用于所有场景
总结:test a2(a1);语句中a2传给了this指针,a1作为了参数,a1要使用引用类型来接收(构成)
1.内置类型成员完成值拷贝/浅拷贝。
2.自定义类型会调用它自己的拷贝构造。
3.C++中Date和MyQueue不用写拷贝构造(已经自己实现了)
4.C++中Stack需要自己实现拷贝构造
5.拷贝函数使用引用参数能大大减少拷贝次数
- 注意:内置类型貌似可以不用写拷贝构造了,但是真的是这样吗?
这里有个大坑,如果成员变量只是像日期一样的普通内置类型,的确可以不用写拷贝构造函数,如果内置类型有指针,只是单纯的拷贝地址,拷贝出来的类里的指针相同,指向同一块空间,实际上并没有进行真正意义上的拷贝,这两个类里的指针对同一块空间进行操作,很容易出现问题。(1.如析构时可能析构两次,可能会释放两次空间,发生报错2.一个修改会影响另一个) - 所以为了避免以上的错误,我们可以自己实现深拷贝(深拷贝有很多应用场景,下面只举一个例子,之后会介绍)
如果成员变量里有使用malloc开辟了一块空间,这时我们不能单纯地指针拷贝,应该开辟一块同样大小的空间,同时将该空间里面的数据也进行拷贝。(memcpy函数)
本篇完结