在我们平时创建一个类对象的时候,会默认调用类中的构造函数,进行内存空间的创建和初始化类的成员变量,构造函数是属于一个类的,它可以由用户自定义也可以由系统自动生成。
在我们需要撤销类的对象的时候,会默认调用析构函数,进行回收存储空间以及一些善后工作。
一.对象的初始化和构造函数
1.1系统自动生成的构造函数
当我们用户没有主动书写构造函数时,系统会自动生成一个构造函数
当67行我们定义一个对象时,系统就会自动生成一个无参的构造函数A(){},它的函数体是空的,只能为对象开辟数据成员的存储空间,而不能为数据成员赋初值。
1.2用户自定义的构造函数
那我们现在来看看我们用户如何自定义构造函数
可以看到第七行和第十一行有两个很特别的成员函数,其实他们就是构造函数。
构造函数定义时的注意事项:
1.构造函数的名字必须与类名相同,如上图所示都是B这个类名。
2.构造函数没有返回值(也不能使用void)
3.构造函数与其他普通成员函数一样,可以写在类内也可以写在类外
4.构造函数一般声明为公有成员,它不能被显示调用(即不能写成 b1.B(2); ),它在定义对象时会被自动调用,而且只执行一次。
5.构造函数可以重载
6.当用户自动定义了构造函数,系统自带的默认构造函数将不能被使用。
上述代码的打印结果为:
说明在定义b0,b1这两个对象时系统自动调用了两个构造函数。
1.3构造函数的调用方式
![](https://img-blog.csdnimg.cn/direct/8f228843aac1466ba1ad2caf06117495.png)
![](https://img-blog.csdnimg.cn/direct/a6a6045d105b46e48080929aabaa6cec.png)
![](https://img-blog.csdnimg.cn/direct/1ab6bd63ecc94e4d9cd3c96e51aba289.png)
![](https://img-blog.csdnimg.cn/direct/2195d03991944dbba38e3ee0a813ce76.png)
![](https://img-blog.csdnimg.cn/direct/3708cf86460b4d7084403f1c51409399.png)
第二十八行和第三十行都是在堆区新开辟了一片内存空间去存放一个A类的对象,同时调用了该类
的构造函数给数据成员赋初值,此时这个对象是匿名对象,但是该对象的地址存放在栈区,也就是
指针变量a1,a2中(即访问new动态建立的对象并非是使用对象名进行访问而是通过指针进行访
问)
上段代码的意思如图所示
运行结果如下
如果没有delete,则运行结果会变为
此时就表明根本没有调用析构函数
ps:此时已经说了他是匿名对象,没有对象名,所以此时它是不会主动调用析构函数的,它需要我们手动释放delete(可以理解成为调用析构函数是对象的特有属性)析构函数下文会讲
1.4带默认参数的构造函数
对于带参数的构造函数,在定义对象时必须给构造函数的形参传递参数的值,否则构造函数将不被执行,但实际生活中,有时候有些参数的值大部分情况是一样的,我们就没有必要反复去传相同的值,这时我们就可以将其定义为带默认参数的构造函数
运行结果如下:
这样的话即使我们没有传够两个参数的值,该构造函数仍然可以执行。
1.5用成员初始化列表对数据成员初始化
这种方法不在函数体内用赋值语句对数据成员初始化,而是在函数首部实现的
一般形式为:类名(参数列表):成员名1(参数名1),成员名2(参数名2),........{}
使用这种方法去初始化,一种原因是因为看起来简单,另一种原因是在C++中某些类型的成员是不允许在构造函数中用赋值语句直接赋值的。(对于const修饰的数据成员或者是引用修饰的数据成员是不允许在构造函数中使用赋值语句直接赋值的)。
注意:数据成员是按照他们在类中声明顺序进行初始化,与初始化列表顺序无关
二.析构函数
形式:~类名(){};
注意事项:
1.无返回值,无参数,函数名与类名同名,无重载特性(即一个类只能有一个析构函数)
2.撤销对象时,编译系统会自动调用析构函数。
3.系统会自动生成析构函数,但是我们用户也可以自定义往析构函数里面加东西,值得注意的是,如图1.3.2所示,如果在构造函数中主动在堆区申请了内存空间,我们需要在析构函数中手动释放所申请的内存空间。
三.拷贝构造函数
拷贝构造函数是一种特殊的构造函数,其形参类型是本类对象的引用。
3.1定义拷贝构造函数
形式: 类名(const 类名&对象名),如图1.3.1所示第三十八行
注意:当我们没有手动定义拷贝构造函数时,系统会自动生成拷贝构造函数
3.2调用拷贝构造函数
3.2.1当用一个类的对象去初始化该类的另一个对象时,拷贝函数将被调用
![](https://img-blog.csdnimg.cn/direct/fb81db7689c64231a3d091ad5f26e7f7.png)
![](https://img-blog.csdnimg.cn/direct/e98ba4e31ec14a67acd8b6aec3d82c56.png)
![](https://img-blog.csdnimg.cn/direct/10c404b90fd048eea7857dd0fa1f44a0.png)
![](https://img-blog.csdnimg.cn/direct/bd206717aed54a46bfba51302ad25aa9.png)
![](https://img-blog.csdnimg.cn/direct/6e135c91a1d148a0be10420493fbdcbb.png)
![](https://img-blog.csdnimg.cn/direct/bb560be06c7e4fe08986cde73a153ca4.png)
3.3深拷贝和浅拷贝
3.3.1浅拷贝
浅拷贝:就是拷贝多次但是仍使用的同一片堆区空间,那么为什么会出现这种情况呢?
上面代码是浅拷贝,因为始终只在堆区开辟了一片内存空间,后续第52行和53行都只是将堆区的地址进行了拷贝,并没有开辟新的空间,所以最终就是p1,p2,p3的name都指向同一块堆区
只是将在栈区的地址值进行了拷贝,但这样会有风险,比如说,当我们需要析构p1,p2,p3时,所调用析构函数会对同一片堆区的空间反复free,这样就会出错,所以我们上述代码写了一个release_name_pointer函数去处理这个风险。
3.3.2深拷贝
每次拷贝都会申请新的堆区空间,大家互不影响
这样书写的话,就可以直接在析构函数里面free,因为每一个对象的name所申请的空间都不同,就不存在将同一块空间反复释放的情形了。