1.概念
在现实生活中,可能存在一个与你一样的自己,我们称之为双胞胎。那在创建对象的时候,可否创建一个与已存在对象一模一样的新对象呢?答案是可以的,这就要通过拷贝构造函数来实现了。
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰)
在用已存在的类类型对象创建新对象时由编译器自动调用
2.特征
拷贝构造函数也是特殊的成员函数,其特征如下:
1.拷贝构造函数是构造函数的一个重载形式
2.拷贝构造函数的参数只有一个且必须是类类型对象的引用使用传值方式编译器直接报错,因为会引发无穷递归调用
因为不是引用,是值调用,所以要创建临时对象来拷贝值,但这样无疑会造成无穷递归调用
3.若没有显式定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝
class Time
{public:Time(){_hour = 1;_minute = 1;_second = 1;}Time(const Time& t){_hour = t._hour;_minute = t._minute;_second = t._second;cout << "Time::Time(const Time&)" << endl;}private:int _hour;int _minute;int _second;
};
class Date
{private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};int main()
{Date d1;// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构
造函数Date d2(d1);return 0;
}
在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。
4.编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,那么还需要我们显式实现吗?
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;
}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType *_array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2(s1);return 0;
}
在这里程序会崩溃掉,为什么呢? 首先s1对象调用构造函数创建,在构造函数中,默认申请了10个元素的空间,然后里面存了4个元素1 2 3 4;
然后s2对象使用s1拷贝构造,而Stack类中没有显式定义拷贝构造函数,则编译对象会给Stack类生成一份默认的拷贝构造函数,默认拷贝构造函数是按照值拷贝的,即将s1中内容原封不动的拷贝到s2中,连指针的值也拷贝了,因此s1和s2指向了同一块内存空间;
当程序退出时,s2和s1要销毁,s2先销毁,s2销毁时调用析构函数,已经将0x11223344的空间释放了,但是s1并不知道,会将该空间再释放一次,一块内存空间多次释放,肯定会造成程序崩溃。
注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以一旦涉及到资源申请时,拷贝构造函数是一定要写的,否则就是浅拷贝
5.拷贝构造函数典型调用场景:使用已存在对象创建新对象函数参数类型为类类型对象函数返回值类型为类类型对象
class Date
{
public:Date(int year, int minute, int day){cout << "Date(int,int,int):" << this << endl;}Date(const Date& d){cout << "Date(const Date& d):" << this << endl;}~Date(){cout << "~Date():" << this << endl;}
private:int _year;int _month;int _day;
};Date Test(Date d){Date temp(d);return temp;}
int main()
{Date d1(2022,1,13);Test(d1);return 0;
}
该程序的执行情况如下:
第一步:调用构造函数创建d1;
第二步:Test函数以值传递,传参时候使用拷贝构造函数创建d;
第三步:调用拷贝构造函数创建temp;
第四步:函数以值方式返回,返回时使用temp拷贝构造临时对象来返回
最后就是销毁Test函数中的temp,d,返回时创建的临时对象,以及main函数的d1。