拷贝构造函数:对一个简单变量的初始化方法是用一个常量或变量初始化另一个变量
也是特殊的成员函数,其特征如下:
(1)拷贝构造函数是构造函数的一个重载形式。
(2)拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
(3)若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
注意:编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,我们还需要自己实现吗?当然像日期类这样的类是没必要的,但是像Stack、String这样的类若只进行浅拷贝,程序会崩溃,需要进行深拷贝。
#include<iostream>
using namespace std;
//拷贝构造函数:拷贝初始化
class A
{
public:
A(int a = 1)
{
_a = a;
}
A(const A& a) //自定义类型的拷贝构造函数
{
cout << "A(const A& a)" << endl;
_a = a._a;
}
~A()
{
cout << "A()" << endl;
}
private:
int _a;
};
class Date
{
public:
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造函数
//默认构造函数,不写编译器会自动生成
//这个拷贝构造对内置类型会完成浅拷贝,或者值拷贝
//对自定义类型成员,会调它的拷贝构造函数
Date(const Date& d) //参数里面不能是Date,而是Date&
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print()
{
cout << _year << "_" << _month << "_" << _day << endl;
}
private:
int _year;
int _month;
int _day;
A _aa;
};
class Stack
{
public:
Stack(int capacity = 4)
{
if (capacity <= 0)
{
_a = nullptr;
_size = _capacity = 0;
}
else
{
_a = (int*)malloc(sizeof(int)*capacity);
_capacity = capacity;
_size = 0;
}
}
//析构函数
~Stack()
{
free(_a);
_size = _capacity = 0;
_a = nullptr;
}
private:
int* _a;
int _size;
int _capacity;
};
int main()
{
/*Date d;
Stack st;*/
Date d1(2020, 5, 25);
//Date d3(); //写法错误,没有调到构造函数
Date d4(d1); //使d4与d1的值一样
d1.Print();
d4.Print();
return 0;
}
分析拷贝构造函数要点:
(1)是构造函数的一个重载,所以函数名与构造函数名一致,也是类名
(2)是为了已经存在的一个类的对象,可以直接初始化另一个类的对象
(3)形参只有一个且要加&,以下面代码为例:
d是d1的别名,d4传给this指针,直接将d4初始化
若不加&,利用直接传值:传值就需要拷贝构造,拷贝构造就需要传值……会陷入无限循环中
若利用指针:可以传值,但是每次利用传递地址,太麻烦
(4)加const:若是不改变d1的值,建议加const,若是不加,如果写成d._year = _year会发现不了,加了const,即便写错,编译也会报错
(5)针对内置类型,即便不写拷贝构造函数,系统也会完成拷贝构造,针对Stack等类,系统不会自动完成
原因:Stack会有资源的存储,拷贝构造一份后,两份均指向同一个内存地址,当调用析构函数时,会被free两次,同时其中一个对象插入删除数据,也会导致另一个对象也插入删除数据
(6)针对自定义类型成员,会调用自定义自己的拷贝构造函数
class Date
{
//Date d4(d1);
Date(const Date& d) //参数里面不能是Date,而是Date&
{
_year = d._year;
_month = d._month;
_day = d._day;
}
};
int main()
{
Date d1(2020, 5, 25);
Date d4(d1); //使d4与d1的值一样 利用拷贝构造
return 0;
}