文章目录
- 再探拷贝构造函数和重载复制运算符
- 实例化新对象和赋值操作
- 强转为类类型
- 指针和引用时临时对象的构造和析构过程
- 考考你
- 问题
- 答案
再探拷贝构造函数和重载复制运算符
实例化新对象和赋值操作
首先我们写一个类,实现它的拷贝构造并重载赋值运算符。
class Test {
private:int ma;
public:Test(int a = 10): ma(a) {cout << "Test()构造函数" << endl;}~Test() {cout << "~Test析构函数" << endl;}Test(const Test& t) {cout << "Test const Test& t拷贝构造函数" << endl;}Test& operator=(const Test& t) {ma = t.ma;cout << "operator = 重载赋值运算符" << endl;return *this;}
};
然后我们构造类的对象,主要探究什么时候调用重载赋值运算符、什么时候调用拷贝构造
int main () {Test t1;Test t2(t1);Test t3 = t2;Test t4 = Test(20);cout << "-------------" << endl;return 0;
}
结果如下:
为什么没有调用我们的t3和t4没有调用重载赋值运算符呢?
首先t3调用的是拷贝构造函数,说明本质上这是一条构造语句,如果写成
Test t3;
t3 = t2; // 这里调用了重载的赋值运算符
再一个t4是直接调用构造函数,那是因为Test(20)显示生成临时对象,它的生命周期为所在语句,所以它被编译器优化了,实际上调用的是Test t4(20);
,如果写成:
//t4.operator=(t2)
t4 = t2; //赋值,没有生成新对象
// t4.operator=(const Test &t)
t4 = Test(20); //这个临时对象会构造一个新对象,然后赋值给t4
此处的Test(20)会就会调用构造函数了,并且t4=会调用重载赋值构造函数,也就是说,只要我们不是在进行初始化对象,而是在赋值某个对象的话,就会调用重载赋值运算符。
强转为类类型
接着上面的代码
t4 = (Test)30; // int->Test
这样能转吗?其实是可以的,编译器会寻找Test类是否有一个合适的构造函数。显然我们这里是有的,因为我们的构造函数默认输入一个整形变量,所以编译器会为我们生成一个临时对象。甚至我们可以写成:这样的话我们是隐式生成临时对象。
t4 = 30;
//t4 = "aaa"; 报错,因为我们没有实现带char*类型的构造函数
指针和引用时临时对象的构造和析构过程
我们想考察一下指针和引用来获取临时对象:
Test *p = &Test(40);
//p指向的是一个已经析构的临时对象,该指针变为野指针
const Test &ref = Test(40);
//Test &ref = Test(30); 报错,必须是常引用
这里的指针甚至不能编译,这是由于较新的编译器代码检查机制更加严格,放以前肯定就编译通过了。并且该指针一定会成为野指针,因为出语句之后临时对象就会析构。并且较新的编译器const Test &ref = Test(40)
也会报错,报错语句为:
error: taking the address of a temporary object of type 'Test' [-Waddress-of-temporary]
结论:在较新版本的编译器中,无论是用常引用还是指针,都不能指向一个临时对象
考考你
问题
对于以下类:
class Test {
public:// Test() Test(a) Test(b)Test(int a = 5, int b = 5) : ma(a), mb(b) {cout << "Test()构造函数" << endl;}~Test() {cout << "~Test()析构函数" << endl;}Test(const Test& src) : ma(src.ma), mb(src.mb) {cout << "Test(const Test&)拷贝构造函数" << endl;}Test& operator=(const Test& src) {ma = src.ma;mb = src.mb;cout << "operator=(const Test&)重载赋值运算符" << endl;return *this;}
private:int ma;int mb;
};
我们这样实例化这个类,请问每条语句都调用了类的哪些成员函数呢?
Test t1(10, 10);//1.Test(int, int)
int main() {Test t2(20, 20);//3.Test(int, int)Test t3 = t2; //4.Test(const Test&)/*静态变量和全局变量程序运行的时候内存就已经存在了,因为数据段内存是事先就分配好的但是静态局部变量只在第一次运行到它才会初始化,所以并不会构造先t4*///static Test t4(30,30);static Test t4 = Test(30, 30);//5.Test(int, int)t2 = Test(40, 40); //6.Test(int, int) operator= ~Test()//(50, 50) = 50 逗号表达式t2 = (Test)(50, 50);//7.(Test)50; Test(int, int) operator= ~Test()t2 = 60; // Test(int) 8.Test(int, int) operator= ~Test()Test *p1 = new Test(70, 70); //9. Test(int, int)Test *p2 = new Test[2]; // 10. Test(int, int) Test(int, int)//较新的编译器无法通过编译Test *p3 = &Test(80, 80); //11.Test(int, int) ~Test()const Test &p4 = Test(90, 90); //12.Test(int, int)delete p1; //13.~Test() 然后free内存delete[]p2; //14.~Test() ~Test() 然后free内存
}
Test t5(100, 100); //2. Test(int, int)
运行以后,这个类的构造、析构、拷贝构造的调用过程已经写在注释中了,并且最后的析构顺序为p1->p2->p4->t3->t2->t4->t5->t1 一共应该是9连析构
答案
你答对了吗?(这里的输出结果是注释Test *p3 = &Test(80, 80);
后的结果)