这几个概念比较容易混淆,总结一下。
1、默认构造函数
默认构造函数是没有参数、和类同名的构造函数。
当一个类没有任何构造函数时,编译器将会合成一个默认构造函数。
那么编译器合成的默认构造函数是做什么用的呢?是初始化类的成员变量吗?
事实上不是。编译器合成的默认构造函数只是满足编译器的需要,而不是按照程序员想的去做。
更详细的内容可参考:谈一谈默认构造函数
编译器合成的默认构造函数初始化的成员变量包括两类。
1、内置类型。如果内置类型变量作用域为全局作用域或者为静态局部变量,那么将初始化为0。为局部作用域的内置变量不做初始化。
2、类类型。如果类的成员变量包括类类型,那么合成的成员变量将调用这个类的默认构造函数来初始化类类型成员变量。
举个例子。
class A
{
public:
A()
{
num=100;
}
int num;
};
class B
{
public:
int num;
A a;
static int k;
};
int B::k;
int main()
{
B *b=new B();
cout<<b->num<<endl;
cout<<b->k<<endl;
cout<<b->a.num<<endl;
return 0;
}
看到的输出结果是:
-842150451
0
100
0
100
2、非默认构造函数
非默认构造函数是我们自己设计的,通常含有参数。目的同样是初始化类成员变量。
当我们设计了类的非默认构造函数,而没有设计默认构造函数,这时候编译器就不会合成默认构造函数了。一个类,如果没有默认构造函数,那么将会有以下限制
1、在创建类成员时,必须显示调用非默认构造函数,传入参数初始化类成员变量。
2、这个类将不能用作动态分配数组的元素类型。这是因为动态数组元素初始化会调用默认构造函数。
3、这个类如果作为静态数组类型,必须提供显示调用构造函数。
4、这个类的对象如果保存到容器中。那么就不允许使用接受容器大小而没有提供一个显示初始化式的构造函数。
在实际应用中,当我们设计了一个非默认构造函数,那么再设计一个默认构造函数总是“有益无害”的。
通常在非默认构造函数初始化类的成员变量时有两种方法,一是初始化列表中初始化,二是在构造函数体内初始化。这是有区别的,可以认为构造函数分两阶段执行。1初始化阶段,2普通计算阶段,这个是值构造函数体内执行的语句。
可以这样理解,在初始化列表中初始化成员变量,是在变量定义的同时初始化;而在函数体内初始化就是先定义后初始化了。对于引用和const变量,只能在初始化列表中初始化。
还有一个就是变量的初始化顺序。在初始化列表中初始化的变量并不是按照指定的次序来初始化的,而是按照变量的定义顺序类初始化的。一个好的习惯就是不用一个成员变量来初始化另外一个成员变量。
非默认构造函数可以用来到类类型的转换。可以用单个实参的构造函数到类类型的转换。一个例子:
class A{
public:
A(){num=0;}
A(int k){num=k;}
int num;
};
int main()
{
A a;
cout<<a.num<<endl;
a=10;//隐式调用A(int k){num=k;}
cout<<a.num<<endl;
return 0;
}
输出:
0
10
10
如果我们不想这样的转换,即禁止隐式调用,声明函数为explicit即可。
3、复制构造函数
复制构造函数是只有单个形参,且形参是本类型的引用(常用const修饰)。
复制构造函数在以下情况下调用
1、用一个同类型的对象初始化另一个对象。
2、复制一个对象,将它作为实参船体给一个函数。
3、函数返回值为这个类类型(非引用),返回时复制这个对象。
4、初始化顺序容器的元素。
同默认构造函数一样,当我们没有定义复制构造函数时,编译器会合成一个默认的复制构造函数。合成的复制构造函数只是满足编译器的需求,把新对象的每个成员变量初始化为原对象的副本,通常是大家所说的浅拷贝。一般来说合成的复制构造函数能满足我们的需求,但是包含有指针的类一般情况下需要定义自己的复制构造函数。
关于深拷贝浅拷贝可参考:Memberwise copy(深拷贝)与Bitwise copy(浅拷贝)的区别
至于有指针的类为什么需要复制构造函数可参考:C++中的智能指针
有时候我们禁止对象的复制,这时只需要把复制构造函数声明为private。
4、赋值操作符
复制操作符是指等号“=”。对于类,是指重载赋值操作符。
像复制构造函数一样,如果我们没有定义复制操作符,编译器会合成复制操作符,功能像复制构造函数一样,逐个初始化类成员。
什么情况下调用的复制构造函数?什么情况下用赋值操作符?
赋值操作符是双目运算符,在两个对象都存在情况下,一个对象给另一个对象复制用赋值操作符。
而复制构造函数是只有一个参数。
例如
A一个类,a是类A的一个对象
A a2(a);调用复制构造函数。
A a2=a;同样调用复制构造函数
下面例子调用赋值操作符
A a3;
a3=a;这时候a3已经存在了。