构造函数、析构函数和拷贝构造函数
我们知道,当我们没有定义构造函数、析构函数和拷贝构造函数时,编译器会帮我们生成一个三个默认的函数,一旦我们定义了构造函数或析构函数或拷贝构造函数时,系统便不再生成,这三类函数不能继承。
构造函数,我们知道构造函数是定义对象的时候调用的,在构造函数里我们一般是进行内存分配和成员初始化,构造函数名字与类名相同同时没有显示的返回值类型,它有隐含的返回值,改值由系统内部使用,构造函数被声明公有的并且是可以重载的,但是不能够被对象显示的调用。
析构函数,与构造函数想对应,析构函数时用来撤销对象时调用的,一般用来释放内存,析构函数名字与类名相同并且前面有一个~,它没有参数和返回值,也不能够进行重载。
拷贝构造函数时对象进行按值传递的过程中调用的,一般是采用引用的方式来定义形参的,例如A(const A &obj);
拷贝构造函数
拷贝构造函数的调用情景
拷贝构造函数是在进行按值传递的过程中调用的,一般可以采用三种方式来调用拷贝构造函数:
1. 第一种方式是赋值操作,例如A obj;A obj1=obj;就会调用拷贝构造函数
2. 第二种方式是类似于函数调用,例如 A obj; A obj1(obj);也会调用拷贝构造函数,类似于第一种方式
3. 第三种方式是函数调用过程中的按值传递,例如上面的fun(A),如果这样调用函数fun(obj)就会产生拷贝构造函数的调用
4. 当函数的返回值是对象,函数执行完成,返回调用者,例如A fun(A tmp);
拷贝构造函数的运用
以上前两种拷贝构造函数的调用,我们就不讨论了,主要来讨论第三种拷贝构造函数的调用:
情景一:A obj1=fun(obj)
以上函数调用的结果是:
1. 第一次定义A obj,会调用构造函数输出第一行,
2. 第二次调用A obj1=fun(obj);首先我们是调用拷贝构造函数来构成对象tmp,所以输出第二行,
3. 进入函数fun后输出第三行,
4. 由于存在返回值所以用返回值来构造对象obj1,这一过程再次调用拷贝构造函数,输出第四行,
5. 然后fun函数执行完成,局部变量tmp出栈,调用析构函数,输出第五行
情景二:fun(obj)
以上函数调用的结果是:
1. 第一次定义A obj,会调用构造函数输出第一行,
2. 第二次调用fun(obj);首先我们是调用拷贝构造函数来构成对象tmp,所以输出第二行,
3. 进入函数fun后输出第三行,
4. 由于存在返回值所以主程序必须接收返回值,于是在主程序中创建无名对象来接收返回值,这一过程再次调用拷贝构造函数,输出第四行,
5. 然后fun函数执行完成,局部变量tmp出栈,调用析构函数,输出第五行
6. 无名对象的作用并无太大作用,所以无名对象在fun函数调用完成后立马消失,输出第六行
情景三:obj=fun(obj);
以上函数调用的结果是:
1. 第一次定义A obj,会调用构造函数输出第一行,
2. 第二次调用fun(obj);首先我们是调用拷贝构造函数来构成对象tmp,所以输出第二行,
3. 进入函数fun后输出第三行,
4. 由于存在返回值拷贝到无名对象中,于是在主程序中创建临时对象来接收返回值,这一过程再次调用拷贝构造函数,输出第四行,
5. 然后fun函数执行完成,局部变量tmp出栈,调用析构函数,输出第五行
6. 临时对象会存在于语句obj=fun(obj)中,执行完语句后临时对象的作用完成,立马消失,输出第六行
浅拷贝和深拷贝
浅拷贝是在默认拷贝构造函数时,调用拷贝构造函数进行数据成员按位赋值时出现的问题,若类中有指针类型的数据,这种方法会产生错误
为了解决浅拷贝带来的问题,我们采用自己编写的拷贝构造函数来为每个指针数据成员申请内存并进行内容复制,而不是采用系统默认的拷贝构造函数进行按位拷贝。