本篇是介绍C++的构造函数的第二篇(共二篇),属于读书笔记,对C++进行一个系统的复习。
复制构造函数
复制构造函数是构造函数的一种,也被称为拷贝构造函数,他只有一个参数,参数类型是本类的引用。默认构造函数(即无参构造函数)不一定存在,但是复制构造函数总会存在。因为只要没有自己写的复制构造函数,就会自动生成一个复制构造函数,它只是实现了对应成员之间的一一对应的复制。大多数时候这样一个自动生成的复制构造函数是够用的,但是当涉及到“深拷贝”的需求时还是要自己设计复制构造函数。
构造函数不能以本类的对象作为唯一参数,以免和复制构造函数相混淆。举例子来说就是, CNum(CNum n){}; 这样的构造函数是不允许的。
为什么C++要有这样的机制来保证复制构造函数一定存在呢,因为在很多种情况下都会需要这样的构造函数。
1)当用一个对象去初始化同类的另一个对象时,会调用复制构造函数。
对基本数据类型有这样的用法:
1 int a = 2;
2 int b = a;
第二个语句中使用一个int变量初始化了另一个int变量。对象也可以有类似的初始化方法,用一个对象去初始化另一个同类对象。
1 CNum n1 = 1;
2 CNum n2 = n1; //调用复制构造函数
注意,第二个语句是一条初始化语句,调用了复制构造函数。如果分开写成CNum n2; n2 = n1;就不会调用复制构造函数而是调用无参构造函数,然后调用对=运算符的重载函数。
2)当把对象作为实参时,会调用复制构造函数。
对象作为参数时的传参方法是传值,所以进入这样的函数时就需要在栈帧中构造一个形参对象。作为形参的对象是使用复制构造函数初始化的,而且调用复制构造函数时使用的参数,就是调用函数时所给的实参。这一种用法和第三种用法正是必须有复制构造函数的原因。
总之传递的参数是对象时就一定会调用复制构造函数。复制构造函数有可能并不是“忠实地”一一对应的复制,因此“形参值总等于实参值”这句话就不一定对了,是否完全相等取决于复制构造函数是怎样编写的。
要说明的是,把对象作为函数的形参显然是效率较低的做法,建议使用对象的引用作为形参。如果不希望实参被修改,可以在形参前面加const修饰。
3)当返回一个对象时,会调用复制构造函数。
函数返回时到底发生了什么呢,或者说return语句到底发生了什么呢?需要被返回的值是存储在被调用函数(callee)的栈帧(stack frame)中的,但函数返回后callee的栈帧已经不再存在,返回值应该在返回之前被拷贝到一个安全的位置才行。如果是基本类型的返回值,直接的做法是把返回值保存到一个寄存器中,而对对象显然不能这样做(如果不了解寄存器是什么可以忽略这一句),但总之需要把需要返回的对象返回到一个合适的位置,这就要用到复制构造函数。
作为函数返回值的对象,是用复制构造函数初始化的,而调用复制构造函数时的实参就是return语句所返回的对象。
类型转换构造函数
除复制构造函数外,只有一个参数的构造函数一般都可以称为类型转换构造函数,它们可以起到类型自动转换的作用。还是CNum的例子:
1 class CNum {
2
3 int num;
4 public:
5
6 CNum(){ num = 0;}
7 CNum(int a){ num = a;}
8 CNum(int a, int b){ num = a + b;}
9 }
第二个构造函数CNum(int a)就是一个类型转换构造函数。
1 CNum n1 = 2; //调用了CNum(int a)
但是实际上如果有这样的语句,结果类似可是过程却完全不一样。
1 CNum n1;
2 n1 = 2;
的二条语句也是合法的,而且实际上还调用了CNum(int a),这要说一下 n1 = 2; 这条语句是怎么实现的,过程就是先用2作为实参调用CNum(int a);生成一个无名的临时对象,然后把临时对象按成员对应复制给n1。这样做的效率显然低于 CNum n1 = 2; 这一条语句。
总之其他类型到对象的转换并不是那么的直接,临时对象的创建需要调用类型转换构造函数,之后再对应赋值。