解读构造函数的调用规则、深拷贝与浅拷贝
作者:gemluoye
1.调用规则
默认情况下,C++至少会给一个类添加三个函数:
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
调用规则:
1.如果用户定义了有参构造函数,C++将不在提供默认无参构造函数,但是会提供默认拷贝构造函数。
如上图,我们给类A提供了一个有参的构造函数,所以此时,如果再去主函数定义一个无参的对象时,编译器就会提示“类A不存在默认构造函数”,这就说明如果自己定义了有参构造函数,那么就不会提供默认的无参构造函数。
#include<iostream> using namespace std; class A { public: int num; A(int num) { this->num = num; } }; int main() { A a(5); A b(a); cout << b.num; return 0; }
在类中我们只提供了一个有参的构造函数,我们在主函数中定义一个b对象,传的是对象a,那么它便会走拷贝构造的函数,如果能够输出b.num的值为5的话,则说明提供了默认的拷贝构造函数。
通过打印b的num,发现确实是5.那么就会提供默认的构造函数。
2.如果用户定义拷贝构造函数,c++不会提供其他构造函数
可以看到当我们仅在类A中写了一个拷贝构造函数时,再去主函数中定义一个无参的对象a时,就会报错,提示“类A不存在默认构造函数”。
2.深拷贝和浅拷贝问题
浅拷贝:就是简单的赋值操作
存在的问题:如果有指针指向堆区内存时,不同对象的指针成员指向的是同一块堆区内存。在对象进行释放时,该堆区会被释放两次。当一个对象修改堆区的内容时,另一个对象的内容也会随着改变。
深拷贝:申请同样大小的堆区内存,保证两个堆区的内容一样。
#include<iostream> using namespace std; class A { int num; int* p; public: A() { num = 0; p = nullptr; cout << "调用无参构造" << endl; } A(int a) { num = a; p = new int[num]; cout << "调用有参构造" << endl; } ~A() { if (p)delete[]p; } }; int main() { A a(3); A b = a; return 0; }
在类A中我们定义了一个指针变量,在有参构造函数中,我们在堆区开辟了一块空间。在主函数数中,定义对象b要走拷贝构造函数。下面我们来运行这段代码:
发现报错了,原因就是对象a和对象b的成员变量*p指向的是同一块堆区内存,再调用析构函数时,对象b先把这块堆区内存释放了,当对象a调用析构函数时,此时那块堆区内存已经不存在了,所以会出现访问内存失败,导致程序崩溃。
那么解决的办法就是重写拷贝构造函数,让他们指向不同的堆区区域。代码如下:
#include<iostream> using namespace std; class A { int num; int* p; public: A() { num = 0; p = nullptr; cout << "调用无参构造" << endl; } A(int a) { num = a; p = new int[num]; cout << "调用有参构造" << endl; } A(const A& other) {//万能引用,避免实参修改形参 num = other.num; p = new int[num];//保证内存大小相同 for (int i = 0; i < num; i++) { p[i] = other.p[i];//保证数据相同 } cout << "调用拷贝构造" << endl; } ~A() { if (p)delete[]p; cout << "调用析构函数" << endl; } }; int main() { A a(3); A b = a; return 0; }
通过再在堆区上开辟一块内存,让这块堆区内存上的内容和对象a的堆区内容一样就行,这就是深拷贝。
程序能正常运行。
3.string类的拷贝构造练习
可以通过下面这个自定义string类进一步加深对深拷贝的理解。
#include<iostream> using namespace std; class String { int size; char* p; public: String() { p = nullptr; size = 0; } String(const char* p) { size = strlen(p); this->p = new char[size + 1];//\0,所以加一 strcpy_s(this->p, size + 1, p); } ~String() { if (p) delete[]p; } String(const String& other) { this->size = other.size; this->p = new char[size + 1]; strcpy_s(this->p, size + 1,other.p); } void print() { cout << p<<endl; } }; int main() { String str = "abcde"; String str2(str); str2.print(); return 0; }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。