C++对象的初始化与清理
C++的构造函数与析构函数
构造函数与析构函数的简介:
在构建好一个类时系统就会自动的产生默认的构造函数与析构函数:
对象的初始化:
构造函数语法:类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序在调用对象时会自动调用构造,无需手动调用,而且只会调用一次
对象的清理:
析构函数语法:~类名(){}
1.析构函数,没有返回值也不写void
2.函数名称与类名相同,在名称前面加上符号~
3.析构函数不可以有参数,因此不可以发生重载
4.程序在对象销毁之前会自动调用析构,无须手动调用而且只会调用一次
构造函数的分类:
这里以一个Person类为例:
按参数分类:
有无参构造函数(或者叫默认构造函数)与有参构造函数。
//构造函数 Person()//无参(或者叫默认构造) { cout << "Person的无参构造函数调用" << endl; } Person(int a)//有参 { age = a; cout << "Person的有参构造函数调用" << endl; }
按类型分类
有普通构造函数和拷贝构造函数。(除了拷贝构造函数其余都是普通的)
//拷贝构造函数(除了用这样的方式做参数都叫普通构造) Person(const Person &P)//用const修饰 不能把本身的值给修改了 且需要加上在参数前加上“&”符 { //将传入的人身上的所有属性,拷贝到我身上 age = P.age; cout << "Person拷贝构造函数的调用" << endl; }
构造函数的调用:
括号法:
//1、括号法 //常用 //Person P1; //默认构造函数的调用 //Person P2(10); //有参构造函数的调用 //Person P3(P2); //拷贝构造函数的调用 //注意事项1 //调用默认构造函数时,不要加“()” ,因为这样这行代码,编译器会认为是一个函数的声明,不会认为在创建一个对象 //如Person P1();
显示法:
//2、显示法 //Person P1; //默认构造函数的调用 //Person P2 = Person(10); //有参构造函数的调用 //Person P3 = Person(P2); //拷贝构造函数的调用
这里再提及一个匿名对象 即:
//Person(10);//匿名对象 (没有手动创建对象) //特点:当前行执行结束之后,系统会自动回收掉匿名对象(此时析构函数就会自动调用并执行) //cout << "aaaaa" << endl; //此处运行顺序是 系统临时创建一个对象 自动调用默认构造函数 系统再回收这个对象 自动调用析构函数 最后再执行打印行 //注意事项2(===等价于的意思) //不要利用拷贝函数构造 初始化匿名对象 编译器会认为Person(P3)=== Person P3; 编译器会认为这又是一个对象的声明 //Person(P3);
隐式转换法:
//3、隐式转换法 Person P4 = 10;//相当于 写了 Person P4 = Person(10); Person P5 = P4;//拷贝构造
拷贝构造函数的调用时机:
使用一个已经创建完毕的对象来初始化一个新对象:
//1、使用一个已经创建完毕的对象来初始化一个新对象 void test01() { Person P1(20); Person P2(P1); cout << "P2的年龄是:" << P2.m_Age << endl; }
值传递的方式给函数参数传值:
//2、值传递的方式给函数参数传值 void doWork(Person P) { } void test02() { Person P; //默认构造函数调用 doWork(P); //拷贝构造函数调用 (可以理解为在这个过程中 值的单项传递就进行了拷贝) }
值方式返回局部变量:
Person doWork2() { Person P1; //默认构造函数的调用 cout << (int*)&P1 << endl; return P1; } void test03() { Person P = doWork2(); //拷贝构造函数的调用 (可以理解为返回值赋值给此处的,P相当于拷贝) cout << (int*)&P << endl; }
构造函数的调用规则:
#include <iostream> using namespace std; //默认情况下,只要创造一个类C++编译器至少给一个类添加3个函数 //1、默认构造函数(无参,函数体为空) //2、默认析构函数(无参,函数体为空) //3、默认拷贝构造函数,对属性进行值拷贝 (也就是不写拷贝调用默认拷贝函数时会自动赋值类的属性) //构造函数调用规则如下 //1、如果用户定义有参构造函数,c++不在提供默认无参构造函数,但是会提供默认拷贝构造函数 //1、此时如果我们自己也不提供默认构造函数 编译器就会报错 //2、如果用户定义拷贝构造函数,c++不会再提供其它构造函数 //简而言之除了默认情况 (那如果用户也不提供那不就报错了。。) //用户提供有参构造函数 编译器不会提供默认构造函数 但会提供拷贝构造函数 //而用户一旦提供了拷贝构造函数 编译器就不会提供 默认构造函数与有参构造函数了 class Person { public: Person() //默认构造函数 { cout << "Preson的默认构造函数的调用" << endl; } Person(int age)//有参构造函数 { m_Age = age; cout << "Preson的有参构造函数的调用" << endl; } Person(const Person &P)//拷贝构造函数 { m_Age = P.m_Age; //默认就只有如这样一般的“值拷贝” cout << "Preson的拷贝构造函数的调用" << endl; } ~Person() { cout << "Preson的析构函数的调用" << endl; } int m_Age; }; //void test01() //{ // Person P; // P.m_Age = 18; // // Person P2(P); // cout << "P2的年龄为:" << P2.m_Age << endl; //} void test02() { Person P; Person P2(P); cout << "P2的年龄为:" << P2.m_Age << endl; } int main() { //test01(); test02(); system("pause"); return 0; }
深拷贝与浅拷贝:
深拷贝与浅拷贝的区别:
浅拷贝:
总的来说就是指简单的值拷贝。
//比如在类中有这样一个成员函数 Person(const Person &P) { cout << "Person浅拷贝构造函数的调用" << endl; m_Age = P.m_Age; //这里直接就将我们传入的对象的成员属性 拷贝到我们所构造的对象当中了 }
注意:系统的默认拷贝构造函数也是浅拷贝。
深拷贝:
前言:可以将类中的成员变量手动分配内存空间到堆区
思考:分配到了堆区后就需要我们手动释放内存空间,显然我们可以选择使用析构函数来实现内存空间的释放,但是这样不会存在问题吗?在浅拷贝构造函数中简单的值传递也会将分配空间的地址值传递给拷贝的对象当其中一个对象销毁时会自动调用析构函数释放空间,那么接着的另一个对象销毁时也会释放同一个地址的内存空间此时就会报错。
#include <iostream> using namespace std; //因为重复释放同一个地址的内存空间 这段代码就会报错 class Person { //公共权限 public: Person() { cout << "Person的默认构造函数的调用" << endl; } Person(int age, int height) { m_Age = age; m_Height = new int(height); cout << "Person的有参构造函数的调用" << endl; } //自己实现拷贝构造函数 解决浅拷贝带来的问题 Person(const Person &P) { cout << "Person拷贝构造函数的调用" << endl; m_Age = P.m_Age; m_Height = P.m_Height; //此处将两个指针变量指向同一个内存地址 } ~Person() { //析构代码,将堆区开辟的数据做释放操作 if (m_Height != NULL) { delete m_Height; m_Height = NULL; } cout << "Person的析构函数的调用" << endl; } //属性 int m_Age; //年龄 int *m_Height; //身高 (为什们用指针,是为了开放到堆区) }; void test01() { Person P1(18, 160); cout << "P1的年龄为:" << P1.m_Age <<"身高为:"<<*P1.m_Height<< endl; Person P2(P1); cout << "P2的年龄为:" << P2.m_Age << "身高为:" << *P2.m_Height << endl; } int main() { test01(); system("pause"); return 0; }
图解:
为了避免上述情况,我们便是用深拷贝:
只需要将上面的拷贝函数代码改为:
Person(const Person &P) { cout << "Person拷贝构造函数的调用" << endl; m_Age = P.m_Age; //深拷贝操作 m_Height = new int(*P.m_Height);//相当于防止这两个指向同一个内存堆区空间位置 }
显然深拷贝在拷贝数据时利用,动态分配内存空间给了被拷贝的对象一个新的内存地址,防止了内存地址的重复释放。
初始化列表:
#include <iostream> using namespace std; //初始化列表 //语法: 构造函数():属性1(值1),属性2(值2),属性3(值3)...{函数的实现体} class Person { public: //传统的初始化操作 /*Person(int a, int b, int c) { m_A = a; m_B = b; m_C = c; }*/ //初始化列表初始化属性 Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c)//Person()中没有参数时所赋的值写死了 { //这里就写点其他的。。 } int m_A; int m_B; int m_C; }; void test01() { //Person P(10, 20, 30); Person P(30, 20, 10); cout << "m_A=" << P.m_A << endl; cout << "m_B=" << P.m_B << endl; cout << "m_C=" << P.m_C << endl; } int main() { test01(); system("pause"); return 0; }
关于类对象作为类成员时的构造函数与析构函数的调用顺序:
构造函数先调用 类中的 类成员变量 然后再调用 类 的构造函数
析构函数先释放 类 然后构造函数再释放 类中的 类成员变量
可以理解为先构造肢体再构造整体,先销毁整体后销毁肢体。
本文作者:编程初学者求大佬指点
本文链接:https://www.cnblogs.com/fragmentary/p/16396493.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步