构造函数、析构函数、拷贝构造函数、赋值构造函数、取地址运算符重载
一、构造函数的定义和使用
1、函数名和类名相同
2、构造函数无函数返回类型说明。即什么也不写,实际上构造函数有返回值,返回的就是构造函数所创建的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Test { public : Test() { cout << "Creat Test Object:" << this << endl; data = 0; } private : int data; }; void main() { Test t1; printf ( "%p" ,&t1); } |
3、在程序运行时,当新的对象被建立,该对象所属的类的构造函数自动被调用,在该对象的生存周期中也只被调用一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #include "Test.h" class Test { public : Test() { cout << "Creat Test Object:" << this << endl; data = 0; } private : int data; }; void main() { Test t1; Test t2; Test t3; } //output //Creat Test Object : 004FF914 //Creat Test Object : 004FF908 //Creat Test Object : 004FF8FC |
4、构造函数可以重载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | class Test { public : Test() { cout << "Creat Test Object:" << this << endl; data = 0; } Test( int d) { data = d; } Test( int d, int x, int y) { data = d; this ->x = x; this ->y = y; } private : int data; int x; int y; int d; }; void main() { Test t1; //这里的t1后面没有() Test t2(10); Test t3(10,2,4); } |
5、构造函数可以在类中定义,也可以在类外定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | #include "Test.h" class Test { public : Test() { cout << "Creat Test Object:" << this << endl; data = 0; } Test( int x, int y); Test( int d) { data = d; } Test( int d, int x, int y) { data = d; this ->x = x; this ->y = y; } private : int data; int x; int y; int d; }; Test::Test( int x, int y) { this ->x = x; this ->y = y; } void main() { Test t1; Test t2(10); Test t3(10,2,4); Test t4(20, 30); } |
6、如果类中没有给构造函数,则C++编译器自动给出一个缺省的构造的函数:
只要构造函数是无参的或者只要各参数均有缺省值的,C++编译器都都认为是缺省的构造函数,并且缺省的构造函数只有一个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include "Test.h" class Test { public : Test( int x=1, int y=2); private : int data; int x; int y; int d; }; Test::Test( int x, int y) { this ->x = x; this ->y = y; cout << "Creat Test Object:" << this << endl; } void main() { Test t1; } |
7、如果对象的数据成员全为公有的,也可以在对象名后加 “=” 加 “{ }”。在花括号中顺序填入全体数据成员的初始值。
二、析构函数定义和使用
当一个对象定义时,C++自动调用构造函数建立该对象并进行初始化,那么当一个对象的生命周期结束时,C++也会自动调用一个函数注销该对象并进行善后工作,这个特殊的成员函数就是析构函数。
1、函数名和类名相同,但在前面要加‘~’
2、无函数返回类型,不带任何参数
3、一个类有一个也只有一个析构函数(相当于java中的static函数,所有对象共用这个函数,只需要把各自对象地址传递过去即可,实现原理就是用的this指针)
4、对象注销时,系统自动调用析构函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #include "Test.h" class Test { public : Test() { cout << "Creat Test Obj:" << this << endl; } ~Test() { cout << "Free Test Obj:" << this << endl; } public : int data; int x; int y; int d; }; void main() { Test t1 ; Test t2; Test t3; Test t4; } |
上图构造顺序是1 2 3 4 析构顺序是4 3 2 1
因为构造是入栈的过程,析构是出栈的过程
三、自动装箱和自动拆箱
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #include "Test.h" class Test { public : Test( int d=0) { cout << "Creat Test Obj:" << this << endl; data = d; cout << d << endl; } ~Test() { cout << "Free Test Obj:" << this << endl; } operator int () { return data; } public : int data; int x; int y; }; void main() { Test t1 = 200; t1 = 100; //自动装箱,调用Test(int d=0)构造函数 给data赋值 cout <<"t1.data="<<t1.data<<endl; int value; value = t1; //自动拆箱,调用operator int() 返回data值 cout << "value=" << value << endl; } |
这是我表面理解成自动装箱和自动拆箱,实际上这跟java的自动拆装箱原理还是不太一样的
1 | t1 = 100; t1是Test类型 100是整型,要想进行赋值操作,就得找个中间的Test类型的变量用来接收100<br>同理 double a = 22.14; int b = a;实际上是找了中间变量c保存22,再赋值给b<br><br>三、引用<br> 1、对变量引用 |
1 2 | int a = 10; int &b = a; |
2、对指针引用
1 2 3 | int a = 10; int *p = &a; int *&q = p; //指针q引用指针p |
3、对数组引用
1 2 | int arr[10] = {1,2,3,4,5,6,7,8,9,10}; int (&brr)[10] = arr; |
4、常引用
1 2 3 4 5 6 | const int x = 100; const int &y = x; int n = 20; const int &m = n; |
5、诡异的引用
1 2 | const double d = 12.34; const int &f = d; |
debug查看d和f的地址,以及值如下:
&d != &f 说明类型不一样时,d截取12保存在临时空间,再把临时空间起个别名叫f
四、拷贝构造函数
同一个类的对象在内存中有完全相同的解构,如果进行整体复制是完全可行的,这个拷贝过程只需要拷贝数据成员,而函数成员是公用的。所以在创建对象时,可以用同一个类的另一个对象来初始化该对象,这时所用到的构造函数称为拷贝构造函数,当不编写拷贝构造函数时,默认编译器会有一个拷贝构造函数
拷贝构造函数使用的三个场景:
1、直接赋值
1 2 | Test t2(t1); //拷贝构造 Test t3 = t1; //拷贝构造 |
2、当函数参数是对象时,会调用拷贝构造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | #include "iostream" using namespace std; class Test { public : Test( int d = 0) { cout << "Creat default construct" << endl; data = d; } Test( const Test& t) //拷贝构造函数 { cout << "Creat copy construct" << this << endl; data = t.data; } Test& operator = ( const Test& t) //赋值构造函数 { if ( this != &t) { data = t.data; } return * this ; } ~Test() { cout << this << ":free this obj" << endl; } public : int getData() { return data; } private : int data; }; int fun(Test x) { int value; value = x.getData(); return value; void main() { Test t1; fun(t1); } |
3、函数返回值是对象时,调用拷贝构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | #include "iostream" using namespace std; class Test { public : Test( int d = 0) { cout << "Creat default construct" << endl; data = d; } Test( const Test& t) //拷贝构造函数,采用引用代码出现无穷拷贝构造函数的调用,其实不采用引用,编译会报错 { cout << "Creat copy construct" << this << endl; data = t.data; } Test& operator = ( const Test& t) //赋值构造函数,采用引用可以避免调用构造函数 { if ( this != &t) { data = t.data; } return * this ; } ~Test() { cout << this << ":free this obj" << endl; } public : int getData() { return data; } private : int data; }; Test fun(Test x) { int value; value = x.getData(); Test tmp(10); return tmp; } void main() { Test t1; Test t2(t1); //拷贝构造 Test t3 = t1; //拷贝构造 Test t4; t4 = t1; //赋值构造 Test t6 = fun(t1); //函数返回时已经拷贝构造了一个无名的对象,所以t6就不需要再调用拷贝构造函数了 Test t5; t5 = fun(t1); //这里需要调用赋值构造函数 } |
五、赋值构造函数
C++中不编写赋值构造函数时,编译器会默认提供,功能就是把成员变量进行赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include "Test.h" class Test { public : Test( int d=0) { cout << "Creat Test Obj:" << this << endl; data = d; } ~Test() { cout << "Free Test Obj:" << this << endl; } public : int data; }; void main() { Test t1 = 10; Test t2; t2 = t1; } |
上述代码就是把t1的成员变量值赋值给t2的成员变量,即t2里的data也是10。
自己定义赋值构造函数
1、模板1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #include "Test.h" class Test { public : Test( int d=0) { cout << "Creat Test Obj:" << this << endl; data = d; } Test( const Test &t) { cout << "Creat copy construct" << this << endl; data = t.data; } void operator= (Test t) { data = t.data; } ~Test() { cout << "Free Test Obj:" << this << endl; } public : int data; }; void main() { Test t1 = 200; Test t2; t2 = t1; } |
(1)这里在执行t2 = t1的时候会先执行一个拷贝构造函数(这样效率很低),再执行赋值构造函数,因为赋值构造函数里的形参是Test类对象
(2)t2 = t1 其实就是t2.operate=(t1),就是t2调用operate()函数,
2、模板2 对模板1的缺点:调用拷贝构造函数,效率低 优化,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #include "Test.h" class Test { public : Test( int d=0) { cout << "Creat Test Obj:" << this << endl; data = d; } Test( const Test &t) { cout << "Creat copy construct" << this << endl; data = t.data; } void operator= ( const Test &t) //追加1、const 防止被修改 2、&应用,可以避免调用构造函数 { if ( this != &t) //追加这个判断的作用是防止,对象自己给自己复制,避免效率低 { data = t.data; } } ~Test() { cout << "Free Test Obj:" << this << endl; } public : int data; }; void main() { Test t1 = 200; Test t2; t2 = t1; } |
这里还有个缺点,就是返回值问题,这里是void
3、模板3 当出现连等赋值时,模板二就会编译出错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #include "Test.h" class Test { public : Test( int d=0) { cout << "Creat Test Obj:" << this << endl; data = d; } Test( const Test &t) { cout << "Creat copy construct" << this << endl; data = t.data; } Test& operator= (Test &t) //修改两点: 1、返回值追加Test类型,这样main函数就可以使用连等运算 2、返回以引用返回,避免调用构造函数 { if ( this != &t) { data = t.data; } return * this ; } ~Test() { cout << "Free Test Obj:" << this << endl; } public : int data; }; void main() { Test t1 = 200; Test t2; Test t3; t3 = t2 = t1; //相当于t3.operator=(t2.operator=(t1)) 如果赋值构造函数无返回值,那么这个调用就会编译器报错 } |
总结:模板3才是真正的模板,但也要注意,引用不能滥用,比如看下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | #include "Test.h" class Test { public : Test( int d=0) { cout << "Creat Test Obj:" << this << endl; data = d; } Test( const Test &t) { cout << "Creat copy construct" << this << endl; data = t.data; } Test& operator= (Test &t) { if ( this != &t) { data = t.data; } return * this ; } ~Test() { cout << "Free Test Obj:" << this << endl; } public : int GetData() { return data; } private : int data; }; Test& fun(Test x) { int value; value = x.GetData(); Test tmp(value); return tmp; } void main() { Test t1 = 10; Test t2; t2 = fun(t1); } |
debug时会发现,程序执行后t2里的data是个随机值,并不是10,说明t1给t2赋值失败,原因是什么呢?
其实是因为调用fun函数时,返回的是引用且tmp是局部变量,出了fun函数就消失了,所以t2的data值并没有赋值成功。
这就是引用引来的弊端
使用引用的条件:若函数返回的变量是局部变量,则别用引用返回,否则可以
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· 因为Apifox不支持离线,我果断选择了Apipost!