参数传递的俩种方式
1.值传递
实参把它的值赋值给了形参,这个赋值的过程就是值传递
址传递也是值传递,他是实参把地址赋值给了形参,这个过程是一个赋值的过程
2.引用传递
为什么要有拷贝构造函数?
因为我们都知道一个基本数据类型给另一个相同的基本数据类型的赋值很简单,只需要通过赋值运算符一步就可以实现。
但是类的对象结构很复杂,里面有很多的成员变量,无法通过简单的赋值运算符就可以实现,所以把一个已经存在的对象给另外新的一个对象初始化的这个操作封装到了一个函数里面,这个函数就是拷贝构造函数。
拷贝构造的格式
类型(const 类 & other){ }
Person(const Person& other) { cout << "调用拷贝构造函数" << endl; }
通过格式我们可以看出拷贝构造函数与构造函数的不同点之一是他的参数是一个万能引用类型
请回答下面俩个问题
为什么拷贝构造的形参要用const?
拷贝构造的使用时机的第三条是当返回值是值形式的局部对象,那么它会调用拷贝构造函数拷贝一个匿名对象,既然它都是匿名的了,就是能通过它的名字取地址了,那么它肯定是右值。这样的话实参可能是右值也可能是左值,所以必须用万能引用
函数的返回值为右值,不可寻址。
class Person { Person(Person& other) {//缺少const的 cout << "调用拷贝构造函数" << endl; } }; int main() { Person s(text6());//括号调用,用匿名对象去初始化一个新的对象s Person s1 = text6();//隐式调用 Person s2 = Person(text6());//显示调用,函数的返回值是右值,但是拷贝构造函数是万能引用,无所谓 return 0; }
由于拷贝构造函数没有用const关键字,导致只能接收左值,但是函数的返回值是右值,无法接收了。
为什么拷贝构造的形参是引用类型?
拷贝构造是用一个已经存在的对象去初始化新对象,如果实参传递给形参,形参不是引用类型的话,会导致形参充当了一个新的对象,被实参初始化,满足定义,继续拷贝构造,一顿递归,死循环了!!!!!!!!!!
综合上面俩点,拷贝构造的形参必须是万能引用。
什么时候使用拷贝构造函数?
1.用一个已经存在的对象去初始化另一个新的对象
2.实参(对象)以值传递的方式传递给形参
3.以值的方式返回一个局部对象 (它会拷贝一个匿名的对象)
A func(){ A a; return a; } void text(){ A s=func(); }
它会先拷贝一个匿名名的对象假设是a‘ a'再调用拷贝构造函数给s赋值。但实践上编译器做了优化,可以直接用a调用拷贝构造函数去给s初始化
#include <iostream> using namespace std; class Person { public: int a; public: Person() { cout << "无参构造函数" << endl; } Person(int _a) { this->a = _a; cout << "调用有参构造函数" << endl; } Person(const Person& other) { cout << "调用拷贝构造函数" << endl; } ~Person() { cout << "调用析构函数" << endl; } }; //用一个已经存在的对象去初始化一个新的对象 void text1() { Person s1; Person s2(s1);//括号调用 Person s3 = Person(s1);//显示调用 Person s4 = s1;//隐式调用 } //实参以值传递的方式给形参传值 void text2(Person s1) { } void text3() { Person s1; text2(s1); } //以值的方式返回局部变量 Person text4() { Person s1; cout << (int*)&s1 << endl; return s1; } void text5() { Person s2; s2 = text4(); cout << (int*)&s2 << endl; } int main() { text5(); return 0; }
拷贝构造函数与构造函数的相同点与不同点
首先它俩都是构造函数,没有返回值,函数值为类名
构造函数时在创建对象的时候对其成员变量进行赋值操作。拷贝构造函数是用一个已经存在的对象去初始化一个新的对象。
注意:构造函数是赋值,拷贝构造是初始化。
构造函数的调用规则
默认条件下,c++编译器会给一个类至少提供3个函数
1.默认构造函数(无参,无体)
2.默认析构函数(无参,无体)
3.默认拷贝构造函数,对成员变量进行浅拷贝(值拷贝)
规则:
如果用户定义了有参构造函数,系统将不会提供无参构造函数,但是系统会提供默认的拷贝函数。
如果用户定义了拷贝构造函数,系统将不会提供其他构造函数。
浅拷贝
什么是浅拷贝?
拷贝的过程就是普通的传值过程,不会在堆区中额外申请空间
通过拷贝构造,不同对象的指针类型的成员变量指向的都是同一块内存,修改或删除其中一个对象的指针变量,会对其他对象造成影响。
#include <iostream> using namespace std; class Person { public: int *p=NULL; int b; char c; public: Person(int _a,int _b,char _c) { if (_a > 0) { p = new int[_a]; } this->b = _b; this->c = _c; } Person(const Person& other) {//浅拷贝 this->p = other.p;//普通的传值过程 this->c = other.b; this->c = other.c; } ~Person() { delete[]p; } }; int main() { Person s1(3,2,'a');//如果用户定义了拷贝构造,编译器将不会提供其他 Person s2(s1); return 0; }
浅拷贝构造在有指针类型的成员变量的时候,很容易出错
调用析构函数的时候会导致一块区域被释放两次,编译器会报错!!!!!
深拷贝
什么是深拷贝?
拷贝的过程不单单只是传值的过程了,如果有指针类型的成员变量的话,要给它在堆区重新开辟空间,不要使用已经存在的对象的指针变量所指向的堆区空间了
深拷贝构造的代码实现:
Person(const Person& other) { this->n = other.n; this->p = new int[n]; for (int i = 0; i < n;i++) { this->p[i] = other.p[i]; } this->b = other.b; this->c = other.c; }
关于深拷贝和浅拷贝的总结
如果成员变量在堆区开辟空间了就不要使用编译器提供的拷贝构造函数了,因为它是浅拷贝,只是传值。
我们要自己定义一个拷贝构造函数,给成员变量重新开辟空间,重新赋值。
这样可以防止浅拷贝带来的问题