C++ 类 对象初始化方法
从class A{}; 开始
默认构造函数与默认复制构造函数
这里已经默认生成了默认构造函数(无参的),即:
A() //无参构造函数
{
啥都不做
}
和默认复制构造函数(参数是const A &),即:
A(const A & b) //默认复制构造函数
{
里面的全部成员变量=b中的全部成员变量 //这里会有指针指向同一位置的浅拷贝问题
}
顺便提一嘴:如果重载了构造函数,默认构造函数就不存在,要重写
一旦有构造函数,就不能有默认构造函数;一旦有复制构造函数,就不能有默认复制构造函数。复制构造函数也是构造函数。因此,一旦定义了复制构造函数,默认构造函数也不复存在;但只定义构造函数,默认复制构造函数还在。
四种初始化方式
好的,进入正题。
四种初始化方式(注意这里重点谈论的是初始化,和之后阶段的=赋值,重载赋值运算符没有任何关系)
1、A a1; 无参
2、A a1(参数列表); 无参时理论上变成了A a1(); 但事实上A a();初始化对象是不对的,会被认成在声明函数。
3、A a1=A(参数列表); 无参时 A a1=A(); 这个绝对正确,后面会证明,没有调用复制构造函数。但需要一个形式上的复制构造函数,一旦自定义了复制构造函数,且没有加const 这个句子会报错(详见后面代码实验与解释)。证明是编译器进行了优化。
4、A a1(a2); A a1=a2; a2是已经存在的类A的一个对象实例,这个方式和前三种都不一样,调用的是复制构造函数。
A a= A();
主要的问题在第三种方式,有人理解A a1=A();是先生成了一个无名临时对象,再作为初始化调用复制构造函数来初始化a1,理论很美好,事实上并不正确!! 有一定C++基础的人知道复制构造函数的深拷贝浅拷贝问题,如果按照上述理论,如果A中有指针成员变量,直接调用默认复制构造函数会出现问题(譬如析构两次之类)。以下做了一个小实验:
#include<iostream>
using namespace std;
class A
{
public:
char *a;
int integer;
A() //无参构造函数
{
a=new char(3);
a[0]='a';
}
A(int n) //有参构造函数
{
a=new char(n);
a[0]='n';
}
A(A& b) //消除默认复制构造函数
{
a=b.a;
}
A(const A & b) //如果注释这个类型的复制构造函数A x=A(参数列表);会报错
{
a=new char(3);
a[0]='c';
cout<<"copy"<<endl;
}
~A() //自定义析构函数
{
a[0]='d';
cout<<a[0]<<endl;
delete[] a;
}
};
int main()
{
//A z();在定义函数,而非类对象
A y;
cout<<y.a[0]<<endl;
cout<<y.integer<<endl;
A x=A(); //编译器优化 == A x;
cout<<x.a[0]<<endl;
cout<<x.integer<<endl;
A n=A(5); //编译器优化 == A n(5);
cout<<n.a[0]<<endl; //这个的integer其实还是随机初始化,我这边正好是0,把n x y换个位置就不是0了
cout<<n.integer<<endl;
return 0;
}
得到输出:
a //A y;正常调用无参构造函数生成对象
58 //随机初始化
a //A x=A();经编译器优化相当于A x;
4199705 //随机初始化
n //A x=A(5);经编译器优化相当于A x(5);
0 //其实也是随机初始化,正好是0而已
d //这里才开始析构,前面根本没有临时无名对象
d //析构2
d //析构3
我们在自定义的复制构造函数里,把对象的值固定的改成c,在自定义的析构函数里delete了成员变量a。事实是,A x=A();根本没有生成临时无名对象,调用复制构造函数,并析构。编译器自动优化成了A x;所以再也不用担心A x=A()初始化的时候浅拷贝。另外提一嘴,int没初始化产生奇怪的数字,并不像 https://stackoverflow.com/questions/17690095/c-initialization-of-int-variables-by-an-implicit-constructor/17690411#17690411 里说的A a=A();会自动初始化integer。
总结
总结,经过编译器优化后,A a(参数列表); 和 A a=A(参数列表);没有任何区别。都不经过复制构造函数,后者形式性的需要一个A (const A &a){}的复制构造函数存在(默认复制构造函数就长这样),但又不会经过这个函数。当参数列表为空,即用无参构造函数生成对象,前者写作 A a;不可以写作A a();否则编译器会认为是在声明函数。
最后再次强调,这里的编译器优化只针对初始化,初始化完之后,再进行赋值a1=A(),就会出现刚刚美好的三步理论:1、=右边调用无参构造函数生成无名临时对象 2、调用赋值运算符(如要避免浅拷贝请重载)赋值a1(注意这里不是初始化,和复制构造函数没关系!)