参数为什么是引用类型
拷贝构造函数是构造函数的一种重载形式,
其格式为
类名(const 类名 & 形参)
关于这里形参的类型为什么一定要用引用类型,我们要知道值传递,当函数的形参和传进来的实参相结合的时候,会发生拷贝,生成一个临时对象,用以存放该实参。
假设拷贝构造函数里参数不是引用传递而是值传递的话,会出现下面现象
假如我们以类data的对象 d1 去初始化d2, data d2(d1);
即d1作为拷贝函数的实参,在传给形参d时会发生一次拷贝构造,这时还没有进去函数的主体部分;继续调用该拷贝构造函数,可看到拷贝构造函数会进入无限递归。
当改为引用类型时,我们知道,引用传递实际上传的是对象的地址,在调用时不会开辟新的内存空间,故可以正常的调用拷贝构造函数。
调用拷贝构造函数
关于拷贝构造函数,初学的我第一印象是在用一个对象去初始化另一个对象时,会调用拷贝构造函数。其实,以下三种情况,拷贝构造函数会被调用
1)用类的对象去初始化另一个对象时;
2)函数的形参是类的对象,在调用函数时 进行形参和实参的结合;
3)函数的返回值是类的对象时,在函数执行完返回调用者时。
对于第一点,很好理解,这里不赘述;
对第二点,回顾函数的调用方式 —值传递中, 出现在函数定义中的参数称为形参,出现在函数调用中的参数为形参。形参在函数定义时,编译器并不为其分配存储空间,只有在函数调用时,临时为形参分配空间,接收来自实参的值。函数调用结束后,该内存空间被释放。
看下面例子1:
#include <iostream>
using namespace std;
class point{
private:
int x;
int y;
public:
point()
{
cout<<"construct"<<endl;
}
point(const point &p)
{
cout<<"copy construct"<<endl;
}
int get_x()
{
return x;
}
~point()
{
cout<<"destruct"<<endl;
}
};
void f1(point p)
{
cout<<p.get_x()<<endl;
}
point f2(){
return point();
}
int main() {
point p1;
f1(p1);
return 0;
}
关闭返回值优化后,结果如下:
可以看到,在关闭返回值优化后,在执行f1(p1) 时,调用了拷贝构造函数。原因在于,p1对象作为实参,传递给f1()函数的形参p 时, 编译器会为这个形参p临时分配存储空间,也就是临时对象,用以存放实参p1。f1()函数调用结束后,该临时对象被析构。
对于第三点,看下面例子
#include <iostream>
using namespace std;
class point{
private:
int x;
int y;
public:
point()
{
cout<<"construct"<<endl;
}
point(const point &p)
{
cout<<"copy construct"<<endl;
}
int get_x()
{
return x;
}
~point()
{
cout<<"destruct"<<endl;
}
};
void f1(point p)
{
cout<<p.get_x()<<endl;
}
point f2(){
return point();
}
int main() {
point p1 = f2();
return 0;
}
对于上面6条输出,解释如下:
1—construt :执行f2() 里面的 point(),进行构造;
2—copy construct :在执行 return point () 语句时,会调用拷贝构造函数 复制 point()的一个临时对象;
3—destruct:销毁上面产生的一个临时对象
4—copy construct:在执行main() 函数中赋值语句,亦会产生一个临时对象,调用拷贝构造函数;
借用一张图分析4,
5— destruct:析构4中的临时对象;
6—destruct:析构p1;
可以看到,关于临时对象的问题会影响对象初始化的效率,特别是对于包含大量成员数据的类多次深拷贝;关于拷贝构造函数的三点情况分析,主要目的是为了进一步学习后面的右值引用即移动语句来处理这类问题。
浅拷贝/深拷贝
上面提到了深拷贝,其实关于浅拷贝和深拷贝是关于怎么处理值和指针的问题。
浅拷贝就是拷贝一个类中的所有成员变量的值,也包括指针的值。
深拷贝除了拷贝所有成员变量的值,同时也把指针指向的数据也拷贝过来了;
这样做的原因不难想象, 假如单纯的只使用浅拷贝,例如b是由a拷贝过来的,里面都包含指针成员变量。那么a和b中的指针实际上是指向同一地址单元的,假如我此时释放了a中的指针所指的单元,那么会使得b中的指针称为野指针,易发生内存泄漏。类默认是浅拷贝,对于含有指针变量的类而言,一般采用深拷贝,需要重写拷贝构造函数。