2.拷贝构造函数
1.拷贝构造函数的概念
拷贝构造函数是一种特殊的构造函数,负责类对象之间的复制,与构造函数相同,当我们没有实现拷贝构造函数时,编译器会为我们产生默认拷贝构造函数,举个栗子:
class A { public: A(int _a,int _b) : a(_a),b(_b){ std::cout << "A constructors" << std::endl; } A(const A& rhs) { this->a = rhs.a; this->b = rhs.b; std::cout << "A copy constructors" << std::endl; } int a; int b; }; int main() { A a(13,14); std::cout << a.a << "," << a.b << std::endl; A b(a); std::cout << b.a << "," << b.b << std::endl; return 0; }
测试结果为:
A constructors
13,14
A copy constructors
13,14
可以看到,b直接利用a的值为自己完成了对象的创建和赋值。
2.拷贝构造函数的调用时机
在C++中,在三种情况下会发生拷贝构造的调用
1.对象以值传递的方式传入函数参数
假设存在函数fun(Class_A a){},当调用函数fun(A a)时,一个Class_A类型的对象被传入 。我们知道,函数形参传入,需要生成临时对象.假设传入一个Class_A的对象tset,当test传入时,产生临时对象,调用拷贝构造函数将test的值传给临时对象,执行完fun()之后,析构掉临时对象。上代码:
class A { public: A(){} A(const A&rhs) { this->a = a; this->b = b; std::cout << "A copy constructors" << std::endl; } ~A() { std::cout << "A destructors" << std::endl; } public: int a; int b; }; void fun(A _a) { } int main() { A c; fun(c); return 0; }
测试结果是:
A copy constructors;
A destructors;
A destructors;
2.对象以值传递的方式返回
与上一条类似,如果一个函数的返回值是一个类类型,并且以值传递的方式返回。那么会返回一个临时变量,而这个临时变量会通过拷贝构造函数接受函数的返回值。
class A { public: A(){} A(const A&rhs) { this->a = a; this->b = b; std::cout << "A copy constructors" << std::endl; } public: int a; int b; }; A fun() { A a; return a; } int main() { fun(); return 0; }
测试结果如下:
A copy constructors
3.对象显式的通过另外一个对象进行初始化
class A { public: A(){} A(const A&rhs) { this->a = a; this->b = b; std::cout << "A copy constructors" << std::endl; } public: int a; int b; }; int main() { A a; A b(a); A c = a; return 0; }
测试结果:
A contructors
A copy contructors
A copy contructors
3.浅拷贝和深拷贝
拷贝构造有时候会出现一些问题,设想这样一种情况,一个类中有一个成员函数为指针类型,举个栗子:
class A { public: A(){ p = new int(100); } A(const A&rhs) { this->p = p; } ~A() { delete p; } public: int *p; }; int main() { A a; A b(a); return 0; }
测试结果:程序崩溃。
为什么?我们的拷贝构造函数简单粗暴的拷贝了指针的值,使得a的p指针和b的指针p同时指向堆的同一块空间,当a析构之后,那块空间已经释放,而b再执行析构函数,程序自然崩溃。如果我们不写拷贝构造函数,编译器实现的默认拷贝构造一般都是这种类型的。这种情况我们称之为浅拷贝。
浅拷贝会导致两种情况:
1.两个不同的对象会处理操作同一块内存空间,造成互相影响
2.一块内存空间可能被释放两次!!!造成内存泄漏。
出现这种情况时,我们需要编写符合实际情况的拷贝构造函数,称之为深拷贝。
如果数据成员出现指针变量,不是简单的将指针变量赋值,而是重新分配空间。我们改动上面的栗子。
class A { public: A(int _k = 40){ k = _k; p = new int(100); } A(const A&rhs) { this->k = rhs.k; this->p = new int; *p = *(rhs.p); } ~A() { delete p; } public: int k; int *p; }; int main() { A a; A b(a); return 0; }
测试结果:程序正常执行。
4.拷贝构造函数的几个注意事项
1.拷贝构造函数的参数
拷贝构造函数的参数是常引用。
为什么是常量?这样就可以接受常量,也可以接受非常量(拷贝构造函数肯定不会改变传入参数的值)。当然,你也可以去掉const.只是这样一来,无法使用常量来完成拷贝构造而已。
为什么是引用?引用是必须的,之前提到过,当函数参数以值传递时,会调用拷贝构造函数,如果拷贝构造函数自身居然不是引用,而是以值传递,那么会出现拷贝构造函数的无限递归。不断的调用自身!!!!所以,拷贝构造函数参数必须是引用。
2.拷贝构造函数可以重载吗?
拷贝构造函数是可以重载的!
参数可以是常引用,也可以只是单纯的引用。
A(const A&rhs) { this->k = rhs.k; this->p = new int; *p = *(rhs.p); } A(A&rhs) { this->k = rhs.k; this->p = new int; *p = *(rhs.p); }
测试结果:程序正常执行
3.拷贝构造函数可以被继承吗?
不能,派生类的拷贝构造函数会主动调用基类的拷贝构造。