拷贝构造函数
用于用一个对象初始化另一个对象的时候,本质上它也是一个构造函数,写法也和构造函数一样不同的是它用于用对象初始化。这里讨论拷贝构造函数,注意和下文中两个参数的构造函数的区别。
对象初始化和赋值
观察如下两个语句(Test是一个类):
① Test t = Test(1,4);
②Test t1 = t;
两句话都是=操作符,但是含义有所不同:
①中表示初始化一个匿名对象,只会调用一次构造函数,c++早期版本是要调用两次构造函数来完成初始化的,现在优化版本只要调用一次,提高了效率。
②中表示赋值,是一个简单的浅拷贝不会调用构造函数,但是会由系统生成一个默认的拷贝构造函数。浅拷贝中如果有new或者malloc申请的空间,析构时候就会产生段错误,可以自定义一个拷贝构造函数(注释部分)。下面一个程序验证:
#include <iostream>
using namespace std;
class Test
{
public:
Test(int a,int b){
cout << "调用构造函数" << endl;
this->a = a;
this->b = b;
}
/* Test(Test &ob){ */
/* cout << "调用拷贝构造函数" << endl; */
/* a = ob.a; */
/* b = ob.b; */
/* } */
private:
int a,b;
};
int main()
{
Test t = Test(1,4);
cout << "----------------" << endl;
Test t1 = t;
return 0;
}
运行结果:
调用构造函数
----------------
通过运行结果我们可以发现,①中是构造一个匿名对象然后直接转为有名对象t,执行了一次构造函数,而②中是直接浅拷贝对象t给t1,不执行构造函数。这就是初始化和赋值的区别。
拷贝构造函数调用的四个时机
①Test t2 = t1;如果没有自定义拷贝构造函数,系统会自动生成一个拷贝构造函数,进行一个浅拷贝,如果有需要那么就要自己写拷贝函数,执行深拷贝操作。
②Test t2(t1);用一个对象初始化另一个对象的时候。
③实参初始化形参的时候。
④返回值是匿名对象,这个情况稍复杂些后面单独讨论。
下面验证前三种情况:
#include <iostream>
using namespace std;
class Test
{
public:
Test(int a,int b){
cout << "调用构造函数" << endl;
this->a = a;
this->b = b;
}
Test(Test &ob){
cout << "调用拷贝构造函数" << endl;
a = ob.a;
b = ob.b;
}
private:
int a,b;
};
void f(Test t3)
{
return ;
}
int main()
{
Test t(1,4);//执行构造函数创造对象t
cout << "执行构造函数创造对象t" << endl;
cout << "----------------" << endl;
cout << "① 用t给t1赋值时候调用拷贝构造函数" << endl;
Test t1 = t;
cout << "----------------" << endl;
cout << "② 用一个对象t初始化另一个对象t2" << endl; //类似隐式执行构造函数
Test t2(t);
cout << "----------------" << endl;
cout << "③ 实参t初始化形参t3" << endl;
f(t);
cout << "----------------" << endl;
return 0;
}
运行结果:
调用构造函数
执行构造函数创造对象t
----------------
① 用t给t1赋值时候调用拷贝构造函数
调用拷贝构造函数
----------------
② 用一个对象t初始化另一个对象t2
调用拷贝构造函数
----------------
③ 实参t初始化形参t3
调用拷贝构造函数
----------------
匿名对象去和留
返回一个匿名对象的过程较为复杂,可以分为三种情况:
①返回的匿名对象无人接
②返回的匿名对象用来初始化
③返回的匿名对象用来赋值
下面依次看这三种情况:
#include <iostream>
using namespace std;
class Test
{
public:
Test(int a = 0, int b = 0){
cout << "执行了构造函数" << endl;
this->a = a;
this->b = b;
}
Test(const Test &ob){
cout << "执行了拷贝构造函数" << endl;
a = ob.a;
b = ob.b;
}
~Test(){
cout << "执行了析构函数" << endl;
}
private:
int a;
int b;
};
Test f()
{
Test t(1,2);
return t;
}
void g1()
{
f();
}
void g2()
{
Test t1 = f();
}
void g3()
{
Test t2(1,4);
t2 = f();
}
int main()
{
cout << "① 无人接" << endl;
g1();
cout << "-----------------" << endl;
cout << "② 返回匿名对象用来初始化" << endl;
g2();
cout << "-----------------" << endl;
cout << "③ 返回匿名对象用来赋值" << endl;
g3();
return 0;
}
运行结果:
① 无人接
执行了构造函数
执行了析构函数
-----------------
② 返回匿名对象用来初始化
执行了构造函数
执行了析构函数
-----------------
③ 返回匿名对象用来赋值
执行了构造函数
执行了构造函数
执行了析构函数
执行了析构函数
这段比较难理解,这里写三个g()函数只是为了展示一个类完整的声明周期。
首先,①②伞都执行f()创建一个对象t,然后在返回的时候,先利用t创造一个匿名对象,然后析构t,但是匿名对象仍然存在
c++编译器看返回值是一个类,如果有人去接受这个匿名对象的话,匿名对象会直接转化成有名对象,但是如果没有人接受的话,匿名对象随即就会优化,这里云信个结果是因为编译器进行了优化,可以利用命令-fno-elide-constructors关闭编译器优化,参考博客。