💞💞 前言
hello hello~ ,这里是大耳朵土土垚~💖💖 ,欢迎大家点赞🥳🥳关注💥💥收藏🌹🌹🌹
1.拷贝构造
1.1概念
拷贝构造函数:拷贝构造是指在创建一个新对象时,使用已存在的对象作为其初始值的构造函数。只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
1.2拷贝构造函数特征
拷贝构造函数也是特殊的成员函数,其特征如下:
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
例如:
#include<iostream> using namespace std; class Date { public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } // Date(const Date& d) //正确形式 Date(const Date d)//错误形式 { _year = d._year; _month = d._month; _day = d._day; } private: int _year; int _month; int _day; }; int main() { Date d1; Date d2(d1); return 0; }
结果如下:
如上图所示,Date d2(d1)使用时,会传参,对d1进行临时拷贝,这里的拷贝就需要调用类和对象的拷贝构造函数,而如果拷贝构造函数的参数没有引用的话,那么在使用拷贝构造函数时又要进行传参临时拷贝,又要调用拷贝构造函数就会陷入死循环;
所以在自定义拷贝构造函数时要传引用调用;
- 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。(但如果类中含有指针或动态分配的资源,可能会导致浅拷贝的问题。为了避免这种问题,可以自定义拷贝构造函数,并在其中进行深拷贝操作)
class Time//自定义类型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; Date d2(d1); // 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数 // 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数 return 0; }
在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。
结果如下:
上图可以看出在拷贝日期类对象时生成了默认的拷贝构造函数,该拷贝构造函数对内置类型按照字节的方式进直接拷贝,对自定义类型则调用它的拷贝构造函数;
既然编译器会自动生成拷贝构造函数,那还需要我们显示构造实现吗?对于日期类是可以利用默认生成的拷贝构造函数但对于下面的类仅仅进行浅拷贝是肯定不行的:
// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。 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的成员变量的值浅拷贝简单赋值给了s2,如下图所示:
对于s1,s2的成员变量_array地址一样指向同一块空间,当s1的生命周期结束时,会自动调用析构函数释放_array指向的空间;此时s2中_array指向的空间在s1调用析构函数就被释放了,s2中_array就成为了一个野指针,s2生命周期结束时也会自动调用析构函数,再次释放_array指向的已经释放的空间就会出现错误导致程序崩溃;图示如下:
综上所述,类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝,可能会造成程序崩溃。
1.3拷贝构造函数典型调用场景
- 使用已存在对象创建新对象
- 函数参数类型为类类型对象
- 函数返回值类型为类类型对象
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; }; //3.函数返回值类型为类类型对象 Date Test(Date d) { Date temp(d);//1.使用已存在对象创建新对象 return temp; } int main() { Date d1(2022, 1, 13); Test(d1);//2.函数参数类型为类类型对象 return 0; }
结果如下:
这里函数返回值类型为类类型的拷贝构造没有显示出来,但实际上作为函数返回值在返回时是需要临时拷贝的;
【C++类和对象】拷贝构造与赋值运算符重载(下):https://developer.aliyun.com/article/1496874