基本概念
复制构造函数(Copy constructor)是c++中的一个特殊构造函数,也称拷贝构造函数,它只有一个参数,参数类型为同类对象的引用。
如果没有定义复制构造函数,那么编译器将生成默认的复制构造函数。默认的复制构造函数完成复制的功能。
复制构造函数的参数为同类对象的引用,可以是常应用,也可以是非常引用。形如类名::类名(类名&)
或类名::类名(const 类名&)
默认复制构造函数
class Complex {//复数类
public:
double real, imag;//分别表示实部以及虚部
public:
//构造函数
Complex(double r,double i) {
real = r;
imag = i;
}
};
对于上面的复数类,假设我们有如下的初始化:
int main() {
Complex c1(5, 2);
cout << "c1.real=" << c1.real << "\tc1.imag=" << c1.imag << endl;
Complex c2(c1);//调用复制构造函数初始化c2
cout << "c2.real=" << c2.real << "\tc2.imag=" << c2.imag << endl;
return 0;
}
在上面的初始化工作中,我们调用构造函数对c1
进行初始化工作,然后输出c1
的实部以及虚部。然后用c1
去初始化c2
,此时将调用编译器自动生成的构造函数将c2
初始化为和c1
一样,然后输出c2
的实部以及虚部。运行上面的程序,得到如下的输出:
c1.real=5 c1.imag=2
c2.real=5 c2.imag=2
可以看到此时c2
被初始化为和c1
相同。
编写复制构造函数
在上面的例子中,我们并没有编写复制构造函数,此时编译器将生成一个默认的复制构造函数完成复制工作。
当我们自己编写了复制构造函数之后,编译器将不再生成默认的复制构造函数。如下面的例子所示:
class Complex {//复数类
public:
double real, imag;//分别表示实部以及虚部
//构造函数
Complex(double r,double i) {
real = r;
imag = i;
}
Complex(const Complex& c) {//复制构造函数
real = c.real;
imag = c.imag;
cout << "调用复制构造函数!!" << endl;
}
};
对于上面的类,在复制构造函数被调用的时候将完成复制的工作,并且向控制台输出调用复制构造函数!!,假设我们有如下的初始化:
int main() {
Complex c1(5, 2);
cout << "c1.real=" << c1.real << "\tc1.imag=" << c1.imag << endl;
Complex c2(c1);//调用复制构造函数初始化c2
cout << "c2.real=" << c2.real << "\tc2.imag=" << c2.imag << endl;
return 0;
}
执行后可以得到如下的结果:
c1.real=5 c1.imag=2
调用复制构造函数!!
c2.real=5 c2.imag=2
可以看到,此时我们自己编写的复制构造函数被调用,并且向屏幕输出了调用复制构造函数!!。
错误用法
复制构造函数的参数一定要是对同类对象的引用,不能为其它的。
class Complex {
public:
double real, imag;
Complex(double r,double i) {
real = r;
imag = i;
}
Complex(Complex c) {//错误的写法
real = c.real;
imag = c.imag;
cout << "调用复制构造函数!!" << endl;
}
};
如上所示,我们错误的将参数Complex& c
写为了Complex c
,此时我们的编译将无法通过,在visual studio 2019
中将得到以下的报错:
error C2652: “Complex”: 非法的复制构造函数: 第一个参数不应是“Complex”
message : 参见“Complex”的声明
error C2333: “Complex::Complex”: 函数声明中有错误;跳过函数体
error C2558: class“Complex”: 没有可用的复制构造函数或复制构造函数声明为“explicit”
总之,复制构造函数的参数一定要是同类对象的引用。
复制构造函数起作用的情况
用一个对象来初始化正在构造的对象变量
如上面编写复制构造函数小节中所展示的那样,当我们用同类的一个对象去初始化另一个对象时,会导致构造函数被调用。
需要注意的是:
Complex c1(5, 2);
Complex c2(c1);
Complex c3 = c1;//初始化语句,非赋值语句
上面的第三天语句为初始化语句,并非赋值语句。
函数参数作为对象传值
如果某函数有一个参数是类的对象,那么该函数被调用时,该类的复制构造函数将被调用。
class Complex {//复数类
public:
Complex() {};
Complex(const Complex& c) {//复制构造函数
cout << "调用复制构造函数!!" << endl;
}
};
对于上面的类,假设我们有如下调用:
void fun(Complex c){}
int main() {
Complex c;
fun(c);//调用fun(Complex c)
return 0;
}
其中我们定义了一个函数fun(Complex c)
,其参数为一个Complex
对象,当我们在调用fun(Complex c)
的时候,形参c
将被初始化,此时将调用复制构造函数完成初始化工作,产生如下的输出:
调用复制构造函数!!
需要注意的是,如果函数的参数为对象的引用或常引用时,将不会导致构造函数被调用,如下所示:
void fun(Complex &c){}
函数返回一个对象
如果函数的返回值是某类的对象,则函数返回时,复制构造函数将被调用。
class Complex {//复数类
public:
Complex() {};
Complex(const Complex& c) {//复制构造函数
cout << "调用复制构造函数!!" << endl;
}
};
对于上面的类有如下调用:
Complex fun(){
Complex c(2,3);
return c;
}
int main() {
cout << fun().real << endl;
return 0;
}
函数fun()
返回一个Complex
类的对象,在调用fun()
函数时会导致复制构造函数被调用。
注意事项
- 上述情形未必会调用复制构造函数。因为C++标准允许编译器实现做一些优化。例如:
Class X b=X();
优化
当对象作为函数参数,在对函数进行调用时会调用复制构造函数对形参进行初始化工作,此时会产生额外的开销,我们可以将函数的参数写为对象的引用来避免额外的开销,如果担心对象的值在函数中会被改变,我们也可以用常引用的方式。
对于如下的写法:
Complex fun(Complex c){}
我们可以改写为:
Complex fun(Complex& c){}
Complex fun(const Complex& c) {}