拷贝构造的功能
拷贝构造函数可以把曾经实例化好的对象的数据拷贝给新创建的数据 ,可见说拷贝构造函数在功能上是构造函数的另一种形式。都是让对象初始化。
写法:
拷贝构造函数在语法层是构造函数的重载,函数名就是类名,无返回值,参数是该类类型对象的引用(为了保护被引用对象数据不被破坏,会在类型前面加上const)
Date(const Date& d) //以日期类为例,定义拷贝构造的写法
Date d1; Date d2(d1); //以日期类为例,实例化对象时的写法
Date d1; Date d2 = d1; //以日期类为例,实例化对象时的另一种写法
拷贝构造函数的参数为什么是引用类型
下面是定义的日期类的拷贝构造函数,但参数并不是引用类型
Date(const Date d) { _year = d._year; _month = d._month; _day = d._day; }
因为是传值,实例化对象时需要调用拷贝构造函数把数据拷贝过来,但拷贝数据函数的参数又需要掉用拷贝构造函数,如此就死循环了,拷贝构造函数会被无限调用。如图所示 这种情况编译器会强制报错。
系统自动生成的拷贝构造函数
若未显式定义,编译器会生成默认的拷贝构造函数 。默认的拷贝构造函数对象按内存存储 按字节序 完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
下面代码中定义了一个时间类 Time ,又定义了一个日期类 Date , 时间类在日期类里实例化了对象_t,_t就是日期类的自定义类型,但并没有定义日期类的拷贝构造函数。那么系统自动生成的拷贝构造函数会怎么处理自定义类型_t和内置类型的年月日呢?
class Time { //时间类 public: Time() { _hour = 1; _minute = 1; _second = 1; } Time(const Time& t) //自定义时间类的拷贝构造函数 { _hour = t._hour; _minute = t._minute; _second = t._second; } private: int _hour; int _minute; int _second; }; class Date //日期类,但没有自定义拷贝构造函数 { private: // 基本类型(内置类型) int _year; int _month; int _day; // 自定义类型 Time _t; //时间类实例化对象 _t }; int main(){ Date d1; Date d2(d1); //拷贝对象d1的数据 // 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数 // 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数 return 0; }
只需明晰一点即可,系统自动生成的默认拷贝构造对内置类型进行浅拷贝,对自定义类型,会调用自定义类型的拷贝构造函数,若自定义类型并未定义拷贝构造函数,系统便会自动生成。
下图是代码执行示意图
下图是逻辑示意图
拷贝构造的深拷贝与浅拷贝
概念
浅拷贝:
又称值拷贝,是按字节序拷贝的,通俗的讲别的数据长啥样就拷贝啥样。
浅拷贝的细节:下面代码中还是定义了一个日期类,但增加了一个int*的变量它指向一块空间。这是让默认构造函数进行浅拷贝会发生什么呢?
class Date //日期类,并没有定义拷贝构造函数 { public: Date()//构造函数 { _year = 1; _month = 1; _day = 1; _a = (int*)malloc(sizeof(int) * 7); //开辟空间并把空间的值初始化 for (int i = 0; i < 7; i++) { _a[i] = 0; } } private: int _year; int _month; int _day; int* _a; //指向一块空间 }; int main() { Date d; Date d1(d); //拷贝d的值 }
结果:d对象和d1对象的数据会一模一样,如下图
我们发现d对象的值和d1对象的值一模一样,并且d对象中的_a和d1对象中的_a都指向同一块空间。如下图
上述就值浅拷贝的坏处,d1对象的_a并没有申请资源而是直接指向了d对象_a的空间,如果想让d1
_a也申请空间就需要深拷贝,想要深拷贝就需要自己定义一个拷贝构造函数,该拷贝构造函数如何申请空间要根据不同的场景具体实现。
深拷贝
下面的代码是对上面代码的改造,
class Date { public: Date() //构造函数 { _year = 1; _month = 1; _day = 1; _a = (int*)malloc(sizeof(int) * 7); for (int i = 0; i < 7; i++) { _a[i] = 0; } } Date(const Date& d) //拷贝构造函数 { _year = d._year; _month = d._month; _day = d._day; _a = (int*)malloc(sizeof(int) * (sizeof(d._a) / sizeof(d._a[0]))); //开辟空间 for (int i = 0; i < (sizeof(d._a) / sizeof(d._a[0])); i++) //赋值 { _a[i] = d._a[i]; } } private: int _year; int _month; int _day; int* _a; }; int main() { Date d; Date d1(d); }
d1对象的_a会独自开空间并且拷贝d对象的数据,如下图 这样就完成了拷贝构造的深拷贝了。
小结
拷贝构造是构造函数的重载。参数是该类类型的引用(一般会加上const),不加引用在逻辑是会让拷贝函数无限调用,此时编译器会报错。如果未显示定义拷贝构造函数,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。浅拷贝又称值拷贝,是按字节序拷贝的,通俗的讲别的数据长啥样就拷贝啥样。而深拷贝会申请资源(空间)再赋值。