前言
本篇文章来讲解C++中的拷贝构造函数,
一、拷贝构造函数概念
在C++中,拷贝构造函数(Copy Constructor)是一种特殊的构造函数,用于创建一个新对象并初始化其值为同一类别的另一个已有对象。拷贝构造函数接受一个引用参数,该引用参数是同一类别的另一个对象的引用。
拷贝构造函数通常用于以下情况:
对象的初始化:当使用一个已有对象来初始化一个新对象时,拷贝构造函数会被调用。这可以是通过直接赋值、传递对象给函数参数、返回对象等方式触发。
通过值传递参数:当将对象作为参数传递给函数时,拷贝构造函数会被调用来创建参数的副本以便在函数内部使用。
ClassName(const ClassName& other) { // 拷贝构造函数的实现 }
如果没有显式定义拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数。这个默认的拷贝构造函数会按照逐个拷贝成员变量的方式进行对象的拷贝。
示例代码:
#include <iostream> using namespace std; class Test { int a; int b; public: Test() { a = 1; b = 2; cout << "Test" << endl; } void GetVal(void) { cout << "a : " << a << endl; cout << "b : " << b << endl; } }; int main(void) { Test a; Test b = a; b.GetVal(); return 0; }
二、浅拷贝和深拷贝
浅拷贝(Shallow Copy)和深拷贝(Deep Copy)是关于对象拷贝的两个概念,常用于描述在进行对象复制时对于成员数据的处理方式。
浅拷贝是指将一个对象的值复制到另一个对象,包括对象的所有成员变量。当进行浅拷贝时,如果对象中包含指针类型的成员变量,那么只是复制指针的值,而不是创建新的独立副本,这意味着原始对象和新对象将共享相同的数据资源。这样,当其中一个对象修改了共享数据时,另一个对象的数据也会受到影响。
相比之下,深拷贝是指创建一个新对象,并将原始对象的所有成员变量的值复制到新对象,包括动态分配的资源。这样,每个对象都拥有自己的独立副本,修改一个对象的数据不会影响其他对象。
三、什么时候需要使用深拷贝
1.当一个类包含指针成员,并且在拷贝对象时需要创建该指针成员的独立副本时。如果使用浅拷贝,多个对象将共享同一个指针,这可能导致意外的行为和资源管理问题。通过使用深拷贝,每个对象都拥有自己的独立副本,从而避免了这些问题。
2.当一个类拥有资源管理的责任,例如动态分配的内存、文件句柄等,并希望在拷贝对象时创建这些资源的独立副本,以确保每个对象可以独立管理和释放这些资源。这样可以避免多个对象之间的资源冲突或资源泄漏。
3.当一个类是一个容器类,它包含其他对象作为其成员,并且在拷贝容器对象时需要递归地拷贝容器内的所有成员对象。这确保了深层嵌套的对象都具有独立的副本,而不是共享相同的引用。
错误案例:
这里类包含指针成员,没有使用深拷贝,导致了多次对指针变量P进行释放,所以会导致程序的崩溃。
#include <iostream> using namespace std; class Test { int* p; public: Test() { p = new int; *p = 1; cout << "Test" << endl; } Test(const Test& obj) { p = new int; } void GetVal(void) { cout << *p << endl; } void SetVal(int val) { *p = val; } ~Test() { delete p; } }; int main(void) { Test a; Test b = a; a.GetVal(); b.SetVal(10); b.GetVal(); a.GetVal(); return 0; }
正确做法:
这里应该使用深拷贝,创建一个独立的指针副本出来,这样就不会产生多次释放内存的错误了。
#include <iostream> using namespace std; class Test { int* p; public: Test() { p = new int; *p = 1; cout << "Test" << endl; } Test(const Test& obj) { p = new int; } void GetVal(void) { cout << *p << endl; } void SetVal(int val) { *p = val; } ~Test() { delete p; } }; int main(void) { Test a; Test b = a; a.GetVal(); b.SetVal(10); b.GetVal(); a.GetVal(); return 0; }
四、拷贝构造函数和赋值的区别
用途:
1.拷贝构造函数:用于在创建一个新对象时以另一个同类对象进行初始化。
赋值操作符:用于将一个已存在的对象的值赋给另一个已存在的对象。
语法:
2.拷贝构造函数:拷贝构造函数是构造函数的一种特殊形式,其函数名与类名相同,没有返回类型,在函数参数中接受一个同类对象的引用作为参数。
赋值操作符:赋值操作符是类的成员函数,名称为 operator=,其中 operator 是一个关键字,可以重载为类的成员函数,用于对同类对象进行赋值。
调用时机:
3.拷贝构造函数:当使用一个对象去初始化另一个对象时,会调用拷贝构造函数。例如,通过直接声明或传递参数创建对象,或通过值传递方式将对象传递给函数等。
4.赋值操作符:赋值操作符用于将已存在的对象的值赋给另一个已存在的对象。例如,通过赋值语句将一个对象的值赋给另一个对象。
工作方式:
5.拷贝构造函数:拷贝构造函数通过创建一个新对象,并将源对象的成员变量值复制到新对象中来完成对象的拷贝。通常,在拷贝构造函数中,还需要对动态分配的资源(例如内存)执行适当的深拷贝操作,以确保新对象具有独立的资源副本。
赋值操作符:赋值操作符在已存在的对象上修改其成员变量的值,以匹配另一个对象的值。这可能涉及释放原始对象持有的资源,并分配新的资源来保存来自另一个对象的值。
返回类型:
拷贝构造函数:拷贝构造函数没有返回类型,因为它的作用是用于创建新的对象,而不是返回任何值。
赋值操作符:赋值操作符的返回类型通常是指向当前对象的引用(即 T&)或返回一个新的对象副本(即 T)。返回引用类型的赋值操作符允许支持连续赋值的链式语法。
总结
本篇文章就讲解到这里,这一块的知识还是非常重要的大家务必要掌握好。