C++初学解惑(5)——构造函数(中)
三、复制构造函数
1.存在的理由
厨师做烹饪的时候总要往锅里加入各式各样的调料,调料的种类、数量在相当大的程度上就决定了菜肴的口感;经验丰富的厨师总是擅长于根据顾客的品味差异来调节调料的投入,以迎合顾客的喜好。我们在炮制对象的时候亦如此:通过重载不同具有参数表的构造函数,可以按我们的需要对新创建的对象进行初始化。譬如,对于复数类Complex,我们可以在创建时指定实部、虚部,它通过“投入”的两个double参数来实现;而对于整型数组类IntArray,我们亦可以在构造函数中“投入”一个int值作为数组对象的初始大小,如果需要,我们还可以再“撒进”一个内部数组作为数组各元素的初始值,等等。嗯,总之,就是说我们可以通过向类的构造函数中传入各种参数,从而在对象创建之初便根据需要可对它进行初步的“调制”。假使我们已经做好了一道菜,呃,不,是已经有了一个对象,比如说,一个复数类Complex的对象c,现在我们想新创建一个复数对象d,使之与c完全相等,应该怎么办?噢,有了上节的经验,我知道你会兴冲冲地走到电脑,敲出类似于下面的代码:
Complex d(c.getRe(), c.getIm()); // getRe()与getIm()分别返回复数的实、虚部
很好,它可以正确的工作,不是吗?不过,再回过头两欣赏几眼之后,你是否和我一样开始觉得似乎这样有些冗长?而且,应该看到复数类是一个比较简单的抽象数据类型,它提供的公有接口可以让我们访问到它所有的私有成员变量,所以我们才得以获得c的所有的“隐私”数据来对d进行初始化;但有相当多的类,是不能也不该访问到它所有的私有对象的,退一步说,就算可以完全访问,我们的构造函数参数表也未必总能精细地、一丝不苟地刻画对象,例如对于IntArray类的某个对象,例如a,可能它会包含一千个元素,但我们不可能写
IntArray copy_of_a(a.size(), a[0], a[1], a[2], ..., a[999]); // 足够详细的刻画,同时也足够愚蠢
唔,看来我们错就错在试图用数量有限的辅助参数来刻画我们的对象,而它往往是不合适的。要想以对象a来100%地确定对象b,就必须让对象b掌握对象a的100%的信息,也就是说,构造函数中所传入的参数应该包含对象a的100%的信息。谁包含了“对象a的100%的信息”呢?看上去似乎是一道难题,哦,我们似乎被前面各个杂乱的参数表弄得头晕脑胀,但我们不应该忘却从简单的方面考虑问题的法则,包含“对象a的100%的信息”的家伙,不应该是一群数量巨大的变量,不应该是一组令人恐惧的参数,它应该就是...就是对象a本身。
啊哈,真是废话。别管这么多,我们现在要让复数d初始化之后立刻等于复数c,就可以写
Complex d(c); // 嗯...完美的表示
“等等...”你也许会提醒说,“你好像还没有定义与之对应的构造函数。”
这是一个非常好心的提醒,不过我可以带着愉悦的心情告诉你:C++对于“以与自己同类型的对象为作为唯一参数传入的构造函数”已经做了默认的定义,对于这个默认的构造函数所创建的对象,将与作为参数传入的对象具有完全相同的内容:实际上,这个过程一般是以“位拷贝”的方式进行的,即是将参数对象所占的那一块内存的内容“一股脑”地拷贝到新创建的对象所处的内存区块中,这样做的结果,便使得新创建的对象与参数对象内有完全相同的成员变量值,无论是公有的还是私有的,因而,我们也就得以以一个已存在的对象来初始化一个新的对象,当然,两者应该是同一类型的。
你也许会对“为何C++要默认提供这样一个构造函数”感兴趣。实质上,不仅仅是上面的“声明对象并初始化”的例子要使用到这个特性,在一些更普遍的情况、同时也是我们所不注意的情况中,必须要使用到这个功能,例如:进行值传递的函数调用。考虑下面的代码片断:
void swap(int a, int b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int x = 1, y = 2;
swap(x, y);