个人主页:Jason_from_China-CSDN博客
所属栏目:C++系统性学习_Jason_from_China的博客-CSDN博客
所属栏目:C++知识点的补充_Jason_from_China的博客-CSDN博客
概念概述
- 默认成员函数就是用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。一个类,我们不写的情况下编译器会默认生成以下 6 个默认成员函数,需要注意的是这 6 个中最重要的是前 4 个,最后两个取地址重载不重要,我们稍微了解一下即可。其次就是 C++11 以后还会增加两个默认成员函数,移动构造和移动赋值,这个我们后面再讲解。默认成员函数很重要,也比较复杂,我们要从两个方面去学习。
包括
拷贝构造函数
概念概述
如果一个构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数,也就是说拷贝构造是一个特殊的构造函数。
同时C++规定了,这里是规定,传值传参必须调用拷贝构造
拷贝构造函数的特点
- 拷贝构造函数是构造函数的一个重载。
- 拷贝构造函数的第一个参数必须是类类型对象的引用,使用传值方式编译器直接报错,因为语法逻辑上会引发无穷递归调用。拷贝构造函数也可以多个参数,但是第一个参数必须是类类型对象的引用,后面的参数必须有缺省值。
- C++ 规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成。
- 若未显式定义拷贝构造,编译器会生成自动生成拷贝构造函数。自动生成的拷贝构造对内置类型成员变量会完成值拷贝 / 浅拷贝 (一个字节一个字节的拷贝),对自定义类型成员变量会调用他的拷贝构造。
- 像 Date 这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的拷贝构造就可以完成需要的拷贝,所以不需要我们显式实现拷贝构造。像 Stack 这样的类,虽然也都是内置类型,但是 _a 指向了资源,编译器自动生成的拷贝构造完成的值拷贝 / 浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝 (对指向的资源也进行拷贝)。像 MyQueue 这样的类型内部主要是自定义类型 Stack 成员,编译器自动生成的拷贝构造会调用 Stack 的拷贝构造,也不需要我们显式实现 MyQueue 的拷贝构造。这里还有一个小技巧,如果一个类显示实现了析构并释放资源,那么他就需要显示写拷贝构造,否则就不需要。
- 传值返回会产生一个临时对象调用拷贝构造,传值引用返回,返回的是返回对象的别名 (引用),没有产生拷贝。但是如果返回对象是一个当前函数局部域的局部对象,函数结束就销毁了,那么使用引用返回是有问题的,这时的引用相当于一个野引用,类似一个野指针一样。传引用返回可以减少拷贝,但是一定要确保返回对象,在当前函数结束后还在,才能用引用返回
拷贝函数的使用
拷贝构造的实现其实是很简单,这里先举出实例讲解,之后会进行解释,主要就是传参的时候,如果传递的是传值传参,那么需要用引用进行接收,不然会导致死循环从而导致崩溃
下面的的代码里面首先我们实现声明和实现的分离
//.h //拷贝构造函数的实现 class Date { public: //构造函数的实现 Date(); //拷贝构造的实现 Date(Date& d); //打印函数的实现 void print(); private: int _year; int _month; int _day; }; //构造函数的实现 Date::Date() { _year = 2000; _month = 2; _day = 28; cout << "构造函数的实现"; cout << _year << "/" << _month << "/" << _day << endl << endl; } //拷贝构造的实现 Date::Date(Date& d) { _year = d._year; _month = d._month; _day = d._day; } //打印函数的实现 void Date::print() { cout << _year << "/" << _month << "/" << _day << endl << endl; } //.cpp int main() { //构造函数的实现 Date d1; //拷贝构造的实现:1 Date d2(d1); d2.print(); //拷贝构造的实现:2 Date d3 = d2; d3.print(); return 0; }
运行代码,可以清楚的看见,两个拷贝构造都实现成功,这两种形式,看看你喜欢哪一种拷贝构造实现形式
拷贝构造的过程
//.h //拷贝构造函数的实现 class Date { public: //构造函数的实现 Date(); //拷贝构造的实现 Date(Date& d); //打印函数的实现 void print(); private: int _year; int _month; int _day; }; //构造函数的实现 Date::Date() { _year = 2000; _month = 2; _day = 28; cout << "构造函数的实现"; cout << _year << "/" << _month << "/" << _day << endl << endl; } //拷贝构造的实现 Date::Date(Date& d) { _year = d._year; _month = d._month; _day = d._day; } //打印函数的实现 void Date::print() { cout << _year << "/" << _month << "/" << _day << endl << endl; } //.cpp void Func(Date& d) { cout << "拷贝构造函数的调用" << endl; d.print(); } int main() { //构造函数的实现 Date d1; //拷贝构造的实现:1 Date d2(d1); d2.print(); //拷贝构造的实现:2 Date d3 = d2; d3.print(); //传值传参的调用 Func(d3); return 0; }
这里我们观察拷贝构造是如何完成的
第一步,类类型的,传值传参
第二步,返回函数
全过程图解
在这个例子中,void Func(Date& d)中的&可以写也可以不写,但加上引用可以避免不必要的拷贝构造函数调用,提高效率。
如果写成void Func(Date d),在调用Func函数时,会调用拷贝构造函数将实参对象拷贝一份传递给函数参数d,可能会有一定的性能开销,尤其是当Date类比较复杂时。
如果写成void Func(Date& d),则是通过引用传递参数,不会调用拷贝构造函数,只是传递了对象的引用,效率更高。综上所述,从性能角度考虑,建议加上引用符号&。
所以可以进行与优化,我们函数依旧采取引用接收,减少拷贝
拷贝构造函数的原理解释
首先我们需要明确一点就是,C++在传值传参的时候会调用拷贝构造
什么是传值传参,顾名思义肯定是传递数值。
如果我们传值传参,但是接收的话不采取引用接收,就会导致一直情况
拷贝构造和指针
class MyClass { public: MyClass(); MyClass(MyClass* M); //打印函数的实现 void print() { cout << _year << "/" << _month << "/" << _day << endl << endl; } private: int _year; int _month; int _day; }; //构造函数 MyClass::MyClass() { _year = 1999; _month = 2; _day = 28; cout << "MyClass构造函数的实现"; cout << _year << "/" << _month << "/" << _day << endl << endl; } //拷贝构造,和指针的联合实现 MyClass::MyClass(MyClass* M) { _year = M->_year; _month = M->_month; _day = M->_day; } int main() { //构造函数的实现 Date d1; //拷贝构造的实现:1 Date d2(d1); d2.print(); //拷贝构造的实现:2 Date d3 = d2; d3.print(); //传值传参的调用 Func(d3); //拷贝构造和指针的联合实现 MyClass M1; MyClass M2 = M1; M2.print(); return 0; }
拷贝构造函数需要加上conts和不加const的区别
这里是有一点难度的
接下来 我们给出一个代码,但是这个代码是一个典型的错误代码:此时我们发现是报错的
这里解决报错的原因需要在拷贝构造函数上面加上const,因为这里存在权限放大的行为,之前我们就说过权限可以缩小,但是不能放大。
这里就产生了权限放大的问题,所以我们需要解决权限放大的问题
接下来我们上正确的代码
class MyClass { public: MyClass(MyClass* M); MyClass(int year, int month, int day); MyClass(const MyClass& M); //打印函数的实现 void print() { cout << _year << "/" << _month << "/" << _day << endl << endl; } private: int _year; int _month; int _day; }; //构造函数 MyClass::MyClass(int year = 1999, int month = 2, int day = 28) { _year = year; _month = month; _day = day; cout << "MyClass构造函数的实现"; cout << _year << "/" << _month << "/" << _day << endl << endl; } //拷贝构造,和指针的联合实现,这里就会导致这里只是一个普通的构造函数 //MyClass::MyClass(MyClass* M) //{ // _year = M->_year; // _month = M->_month; // _day = M->_day; //} //拷贝构造的实现 MyClass::MyClass(const MyClass& M) { _year = M._year; _month = M._month; _day = M._day; } //拷贝构造函数的实现 class Date { public: //构造函数的实现 Date(); //拷贝构造的实现 Date(const Date& d); //打印函数的实现 void print(); private: int _year; int _month; int _day; }; //构造函数的实现 Date::Date() { _year = 2000; _month = 2; _day = 28; cout << "构造函数的实现"; cout << _year << "/" << _month << "/" << _day << endl << endl; } //拷贝构造的实现 //Date::Date(Date* const this, Date& d) Date::Date(const Date& d) { _year = d._year; _month = d._month; _day = d._day; } //打印函数的实现 void Date::print() { cout << _year << "/" << _month << "/" << _day << endl << endl; } void Func(Date& d) { cout << "拷贝构造函数的调用" << endl; d.print(); } Date F() { Date d1; return d1; } MyClass f() { MyClass M1(12, 12, 12); return M1; } int main() { //构造函数的实现 Date d1; //拷贝构造的实现:1 Date d2(d1); d2.print(); //拷贝构造的实现:2 Date d3 = d2; d3.print(); //传值传参的调用 Func(d3); 拷贝构造和指针的联合实现 MyClass M1(2000, 1, 1);//初始化2000 MyClass M2;//默认初始化 1999 MyClass M3 = M1; //M2.print(); M3.print(); //const在拷贝构造函数里面的使用 cout << "拷贝构造const的实现"<<endl; MyClass M4 = f(); Date d4 = F(); return 0; }