浅谈c++中的拷贝构造函数

来源:百度文库 编辑:神马文学网 时间:2024/04/29 10:52:57
2007-05-18 13:52
拷贝构造函数,经常被称作X(X&),是一种特殊的构造函数,他由编译器调用来完成一些基于同一类的其他对象的构件及初始化。它的唯一的一个参数(对象的引用)是不可变的(因为是const型的)。这个函数经常用在函数调用期间于用户定义类型的值传递及返回。拷贝构造函数要调用基类的拷贝构造函数和成员函数。如果可以的话,它将用常量方式调用,另外,也可以用非常量方式调用。
在C++中,下面三种对象需要拷贝的情况。因此,拷贝构造函数将会被调用。
1). 一个对象以值传递的方式传入函数体
2). 一个对象以值传递的方式从函数返回
3). 一个对象需要通过另外一个对象进行初始化
以上的情况需要拷贝构造函数的调用。如果在前两种情况不使用拷贝构造函数的时候,就会导致一个指针指向已经被删除的内存空间。对于第三种情况来说,初始化和赋值的不同含义是构造函数调用的原因。事实上,拷贝构造函数是由普通构造函数和赋值操作赋共同实现的。描述拷贝构造函数和赋值运算符的异同的参考资料有很多。
拷贝构造函数不可以改变它所引用的对象,其原因如下:当一个对象以传递值的方式传一个函数的时候,拷贝构造函数自动的被调用来生成函数中的对象。如果一个对象是被传入自己的拷贝构造函数,它的拷贝构造函数将会被调用来拷贝这个对象这样复制才可以传入它自己的拷贝构造函数,这会导致无限循环。
除了当对象传入函数的时候被隐式调用以外,拷贝构造函数在对象被函数返回的时候也同样的被调用。换句话说,你从函数返回得到的只是对象的一份拷贝。但是同样的,拷贝构造函数被正确的调用了,你不必担心。
如果在类中没有显式的声明一个拷贝构造函数,那么,编译器会私下里为你制定一个函数来进行对象之间的位拷贝(bitwise copy)。这个隐含的拷贝构造函数简单的关联了所有的类成员。许多作者都会提及这个默认的拷贝构造函数。注意到这个隐式的拷贝构造函数和显式声明的拷贝构造函数的不同在于对于成员的关联方式。显式声明的拷贝构造函数关联的只是被实例化的类成员的缺省构造函数除非另外一个构造函数在类初始化或者在构造列表的时候被调用。
拷贝构造函数是程序更加有效率,因为它不用再构造一个对象的时候改变构造函数的参数列表。设计拷贝构造函数是一个良好的风格,即使是编译系统提供的帮助你申请内存默认拷贝构造函数。事实上,默认拷贝构造函数可以应付许多情况。
下面举例说明:
如果不主动编写拷贝构造函数和赋值函数,编译器将以“位拷贝”的方式自动生成缺省的函数。倘若类中含有指针变量,那么这两个缺省的函数就隐含了错误。以类String 的两个对象a,b 为例,假设a.m_data 的内容为“hello”,
b.m_data 的内容为“world”。现将a 赋给b,缺省赋值函数的“位拷贝”意味着执行b.m_data = a.m_data。这将造成三个错误:
一是b.m_data 原有的内存没被释放,造成内存泄露;
二是b.m_data 和a.m_data 指向同一块内存,a 或b 任何一方变动都会影响另一方;三是在对象被析构时,m_data 被释放了两次。
拷贝构造函数和赋值函数非常容易混淆,常导致错写、错用。拷贝构造函数是在对象被创建时调用的,而赋值函数只能被已经存在了的对象调用。以下程序中,第三
个语句和第四个语句很相似,你分得清楚哪个调用了拷贝构造函数,哪个调用了赋
值函数吗?
String a(“hello”);
String b(“world”);
String c = a; // 调用了拷贝构造函数,最好写成 c(a);
c = b; // 调用了赋值函数
本例中第三个语句的风格较差,宜改写成String c(a) 以区别于第四个语句。
9.6 示例:类String 的拷贝构造函数与赋值函数
// 拷贝构造函数
String::String(const String &other)
{
// 允许操作other 的私有成员m_data
int length = strlen(other.m_data);
m_data = new char[length+1];
strcpy(m_data, other.m_data);
}
// 赋值函数
String & String::operate =(const String &other)
{
// (1) 检查自赋值
if(this == &other)
return *this;
// (2) 释放原有的内存资源
delete [] m_data;
// (3)分配新的内存资源,并复制内容
int length = strlen(other.m_data);
m_data = new char[length+1];
strcpy(m_data, other.m_data);
// (4)返回本对象的引用
return *this;
}
类String 拷贝构造函数与普通构造函数的区别是:在函数入口处无
需与NULL 进行比较,这是因为“引用”不可能是NULL,而“指针”可以为NULL。
类String 的赋值函数比构造函数复杂得多,分四步实现:
(1)第一步,检查自赋值。你可能会认为多此一举,难道有人会愚蠢到写出 a = a 这
样的自赋值语句!的确不会。但是间接的自赋值仍有可能出现,例如
// 内容自赋值
b = a;

c = b;

a = c;
// 地址自赋值
b = &a;

a = *b;
也许有人会说:“即使出现自赋值,我也可以不理睬,大不了化点时间让对象复制
自己而已,反正不会出错!”
他真的说错了。看看第二步的delete,自杀后还能复制自己吗?所以,如果发现自
赋值,应该马上终止函数。注意不要将检查自赋值的if 语句
if(this == &other)
错写成为
if( *this == other)
(2)第二步,用delete 释放原有的内存资源。如果现在不释放,以后就没机会了,将造成内存泄露。
(3)第三步,分配新的内存资源,并复制字符串。注意函数strlen 返回的是有效字符串长度,不包含结束符‘\0’。函数strcpy 则连‘\0’一起复制。
(4)第四步,返回本对象的引用,目的是为了实现象 a = b = c 这样的链式表达。注意不要将 return *this 错写成 return this 。那么能否写成return other 呢?效果
不是一样吗?
不可以!因为我们不知道参数other 的生命期。有可能other 是个临时对象,在赋
值结束后它马上消失,那么return other 返回的将是垃圾。
如果我们实在不想编写拷贝构造函数和赋值函数,又不允许别人使用编译器生成的
缺省函数,怎么办?
偷懒的办法是:只需将拷贝构造函数和赋值函数声明为私有函数,不用编写代码。
例如:
class A
{ …
private:
A(const A &a); // 私有的拷贝构造函数
A & operate =(const A &a); // 私有的赋值函数
};
如果有人试图编写如下程序:
A b(a); // 调用了私有的拷贝构造函数
b = a; // 调用了私有的赋值函数
编译器将指出错误,因为外界不可以操作A 的私有函数。
(1)拷贝构造函数和赋值函数的使用比较。
String a("zhangbufeng");
String b("cuixiaoyuan");
String c(a);//对象创建时,使用拷贝构造函数
c=b; //c已经被初始化,从而调用了赋值函数。
(2)
我在vc6.0写程序运行了一下:附源程序和运行结果。
在vc6.0中,File->New->C/C++ source File,然后将此段程序输入进去运行即可!
 
#include "iostream.h"
#include "stdio.h"
#include "string.h"
class String
{
public:
String(const char *str = NULL);      // 普通构造函数
String(const String &other);      // 拷贝构造函数
~ String(void);          // 析构函数
String & operator =(const String &other); // 赋值函数
private:
char    *m_String;     //私有成员,保存字符串
};
String::~String(void)
{
cout<<"Destructing"< delete [] m_String;
}
String::String(const char *str)
{
cout<<"Construcing"< if(str==NULL)
{
m_String = new char[1];
*m_String = '\0';
}
else
{
int length = strlen(str);
m_String = new char[length+1];
strcpy(m_String, str);
}
}
String::String(const String &other)
{
cout<<"Constructing Copy"< int length = strlen(other.m_String);
m_String = new char[length+1];
strcpy(m_String, other.m_String);
}
String & String::operator =(const String &other)
{
cout<<"Operate = Function"<//检查自赋值
if(this == &other)
return *this;
//释放原有的内存资源
delete [] m_String;
//分配新的内存资源,并复制内容
int length = strlen(other.m_String);
m_String = new char[length+1];
strcpy(m_String, other.m_String);
//返回本对象的引用
return *this;
}
void main()
{
String a("zhangbufeng");
String b("cuixiaoyuan");
String c(a);
c=b;
}
运行结果如下:Constructing
Constructing
Constructing Copy
Operator = Function
Destructing
Destructing
Destructing