1.概念
拷贝构造函数只有单个形参,该形参是对本自定义类型对象的引用(一般需要用const修饰),在用已存在的自定义类型的对象创建新对象时由编译器自动调用
代码示例:
class Date
{
public:
Date()//构造函数
{
_year = 1;
_month = 1;
_day = 1;
cout << "Date()" << endl;
}
Date(const Date& a)//拷贝构造函数
{
_year = a._year;
_month = a._month;
_day = a._day;
cout << "Date(const Date& a)" << endl;
}
~Date()//析构函数
{
cout << "~Date()" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
Date d1 = d;
return 0;
}
2.特性
拷贝构造函数是一个特殊的成员函数
- 拷贝构造函数是构造函数的一个重载形式
- 拷贝构造函数的参数只有一个且必须是自定义类型对象的引用。如果使用传值方式的话编译器会直接报错,因此这样会造成无穷递归调用
class Date { public: Date() { _year = 1; _month = 1; _day = 1; cout << "Date()" << endl; } //Date(const Date a)//错误写法 Date(const Date& a) { _year = a._year; _month = a._month; _day = a._day; cout << "Date(const Date& a)" << endl; } ~Date() { cout << "~Date()" << endl; } private: int _year; int _month; int _day; }; int main() { Date d; Date d1 = d; Date d2(d);//与上面作用一致,同样是拷贝 return 0; }
报错:
无穷递归:
这是由传值引发对象的拷贝引起的。但如果使用引用,则不会造成这种结果,因为引用并没有引起对象的拷贝
- 如果没有显式定义,编译器会生成默认的拷贝构造函数。默认拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,又叫值拷贝。 注:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的
- 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝,那我们还需要字节显示实现拷贝构造函数吗?如:
class Stack { public: Stack(int capacity = 4) { _a = (int*)malloc(sizeof(int) * capacity); _size = 0; _capacity = capacity; } void Push(const int& x) { _a[_size] = x; _size++; } ~Stack() { free(_a); _a = nullptr; _size = _capacity = 0; } private: int* _a; int _size; int _capacity; }; int main() { Stack st; st.Push(1); st.Push(2); st.Push(3); st.Push(4); Stack st1(st); return 0; }
当我们运行上述代码,不定义拷贝构造函数,使用编译器生成的默认构造函数时,我们可以发现代码崩溃了,这是为什么? 我们通过调试,可以发现
st中的_a和st1中的_a所指向的是同一块空间,而代码在执行析构函数时会自动调用,首先编译器先执行st1的析构函数销毁st1中的空间,然后再执行st的析构函数对st进行销毁,由于st1和st指向同一块空间,而st1已经将其销毁,因此当st再执行析构函数再次销毁时会崩溃。如图:
由上述可知,类中如果没有涉及到资源申请时,拷贝构造函数可写可不写。但如果一旦涉及到资源的申请,拷贝构造函数是一定要写的。代码正确写法:
class Stack { public: Stack(int capacity = 4) { _a = (int*)malloc(sizeof(int) * capacity); _size = 0; _capacity = capacity; } Stack(const Stack& s) { int* tmp = (int*)malloc(sizeof(int) * s._capacity);//重新申请一块同等大小的空间 _a = tmp; memcpy(_a, s._a, sizeof(int) * s._capacity);//拷贝数据 _size = s._size; _capacity = s._capacity; } void Push(const int& x) { _a[_size] = x; _size++; } ~Stack() { free(_a); _a = nullptr; _size = _capacity = 0; } private: int* _a; int _size; int _capacity; }; int main() { Stack st; st.Push(1); st.Push(2); st.Push(3); st.Push(4); Stack st1(st); return 0; }
这样我们就可以发现两者指向的内存不一样了,运行析构函数也就不会对同一块空间释放了
3.拷贝构造函数典型的使用场景
-
使用已存在对象创建新对象
-
函数参数类型为自定义类型对象
-
函数返回值类型为自定义类型对象
Date Test(Date d)
{
Date tmp(d);
return tmp;
}