您的位置:首页 > 编程语言 > C语言/C++

C++初学者指南 第九篇(5)

2010-09-21 00:00 176 查看
必备技能9.5:创建并使用拷贝构造函数
正如我们在前面看到的那样,当一个对象被传递给函数或者从函数返回的时候,都会生成该对象的副本。缺省情况下,该副本是对原来对象的逐位拷贝而生成的。这种缺省的操作方式在大多数情况下是可以接受的。但是在那些不能被接受的情况下,我们可以通过为类定义拷贝构造函数来明确指定应该如何进行该类对象的复制。拷贝构造函数是一种特殊的重载的构造函数。它通常在生成对象副本的时候被自动调用。
让我们先来看看为什么需要明确定义拷贝构造函数呢?缺省情况下,当把一个对象传递给一个函数的时候,生成该函数参数的操作是对原来的对象进行逐位拷贝。然而,在某些情况下,这种和原对象完全一样的副本不是我们想要的。例如,如果原对象用到了一些资源,比如一个打开的文件,那么这样生成的副本也和原对象一样使用同样的资源。因此,如果副本对资源做了改动,那么对于原对象来说这些资源也被改动了!
更进一步来说,当函数结束的时候,副本会被销毁,这将导致析构函数被调用,进而导致原对象所需要的资源被释放。
当函数返回对象的时候同样的情况也会发生。编译器会自动生成一个临时对象来保存函数返回对象的值。(这个是自动完成的,超出了我们程序的控制)。这个临时对象在函数一旦返回给调用函数之后就会超出作用域,导致对象的析构函数被调用。然而,析构函数就有可能销毁了调用者想使用的一些资源。这就会导致问题的发生。
导致这些问题的核心就是在生成对象副本的时候进行的是逐位拷贝的操作。为了避免这些问题的发生,我们需要明确定义在生成对象副本的时候应该如何操作,以避免那些意外的副作用。我们就是通过拷贝构造函数来实现这点的。
在我们继续讨论拷贝构造函数的用法之前,我们必须知道,在C++中定义了两种方式来完成把一个对象的值赋给另外一个对象。第一个就是赋值,第二个就是初始化。初始化在以下三种情况下会发生:
l 当我们使用一个对象来明确地初始化另外一个对象的时候,比如在声明对象的时候。
l 当需要生成对象的副本,传给一个函数的时候。
l 当需要创建临时对象的时候(大多数情况下,就是作为返回值)

拷贝构造函数只能用于对象的初始化。它不能应用于赋值。

拷贝构造函数的通用形式如下:
类名(const 类名 &ob)
{
//body of constructor
}
其中,obj是对一个对象的引用,用来对另外的一个对象进行初始化。例如,假设一个类叫做MyClass,并且y是这个类的一个对象,那么下面的语句将会调用MyClass类的拷贝构造函数:
MyClass x = y; //用y显示地来初始化x
func1(y); //y作为函数的实参
y=func2(); // y用来接受函数的返回值
在上面的前两种情况中,会把对y对象的引用传递够拷贝构造函数。第三种情况会把对函数func2()返回对象的引用传递给拷贝构造函数。因此,当一个对象被作为参数,或者由函数返回,或者在初始化的时候被用到,使用的都是拷贝构造函数来生成对象的副本。
请牢记,当把一个对象赋值给另外对象的时候,拷贝构造函数是不会被调用到的。例如,下面的代码不会调用拷贝构造函数:

MyClass x;

MyClass y;

x = y; // 这里不会用到拷贝构造函数
再次说明一下,赋值操作是由赋值运算符来处理的,而不是拷贝构造函数。
下面的程序演示了拷贝构造函数的使用:

/* 当为函数出入对象的时候会调用拷贝构造函数 */
#include <iostream>
using namespace std;
class MyClass
{
int val;
int copynumber;
public:
//普通的构造函数
MyClass(int i)
{
val = i;
copynumber = 0;
cout << " Inside normal constructor. \n";
}
//拷贝构造函数
MyClass (const MyClass &o)
{
val = o.val;
copynumber = o.copynumber + 1;
cout << " Inside copy constructor. \n";
}
~MyClass ()
{
if ( copynumber == 0)
{
cout << "Destruction original. \n";
}
else
{
cout << "Destructing copy " << copynumber << "\n";
}
}
int getval()
{
return val;
}
};
void display(MyClass ob)
{
cout << ob.getval() << "\n";
}
int main()
{
MyClass a(10);
display(a);
return 0;
}


上面程序的输出如下:

Inside normal constructor.

Inside copy constructor.

10

Destructing copy 1

Destruction original.

上面过程的执行过程是这样的:当在main()函数中创建对象a的时候,通过普通的构造函数为其copynumber赋值为0。接着,a被传递给了函数display()。此时会调用拷贝构造函数来生成一个a的副本。在此过程中,拷贝构造函数增加了copynumber的值。当函数display()返回的时候,对象ob就超出其作用域了。这导致析构函数会被调用。最后,当main()函数返回的时候,a也超出其作用域了。

我们还可以尝试对上面的程序进行修改。例如,创建一个返回MyClass类对象的函数,观察拷贝构造函数是在何时被调用的。
练习:

1. 缺省的拷贝构造函数是如何生成对象的副本的?

2. 当把一个对象赋值给另外的一个对象时会调用拷贝构造函数,对吗?

3. 为什么我们需要明确定义类的拷贝构造函数呢?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: