龙鱼家园,个性,交流,技术,专注于网站技术的研究
龙鱼家园 | 拷贝构造函数和赋值函数的必要性和意义
龙鱼家园
自由飞翔
首页
归档
Sign in
<< JSON简介
|
PHP加密函数 >>
拷贝构造函数和赋值函数的必要性和意义
Posted by Admin on September 8, 2010
0 comments
引用链接:
http://hi.baidu.com/%BF%AA%D0%C4de%B9%FB%D7%D3/blog/item/2ea64f318c4cd290a8018e75.html
关于拷贝构造函数和赋值运算符
2009-07-21 16:23
关于拷贝构造函数和赋值运算符
作者:冯明德
重点:包含动态分配成员的类 应提供拷贝构造函数,并重载"="赋值操作符。
以下讨论中将用到的例子:
class CExample
{
public:
CExample(){pBuffer=NULL; nSize=0;}
~CExample(){delete pBuffer;}
void Init(int n){ pBuffer=new char[n]; nSize=n;}
private:
char *pBuffer; //类的对象中包含指针,指向动态分配的内存资源
int nSize;
};
这个类的主要特点是包含指向其他资源的指针。
pBuffer指向堆中分配的一段内存空间。
一、拷贝构造函数
int main(int argc, char* argv[])
{
CExample theObjone;
theObjone.Init40);
//现在需要另一个对象,需要将他初始化称对象一的状态
CExample theObjtwo=theObjone;
...
}
语句"CExample theObjtwo=theObjone;"用theObjone初始化theObjtwo。
其完成方式是内存拷贝,复制所有成员的值。
完成后,theObjtwo.pBuffer==theObjone.pBuffer。
即它们将指向同样的地方,指针虽然复制了,但所指向的空间并没有复制,而是由两个对象共用了。这样不符合要求,对象之间不独立了,并为空间的删除带来隐患。
所以需要采用必要的手段来避免此类情况。
回顾以下此语句的具体过程:首先建立对象theObjtwo,并调用其构造函数,然后成员被拷贝。
可以在构造函数中添加操作来解决指针成员的问题。
所以C++语法中除了提供缺省形式的构造函数外,还规范了另一种特殊的构造函数:拷贝构造函数,上面的语句中,如果类中定义了拷贝构造函数,这对象建立时,调用的将是拷贝构造函数,在拷贝构造函数中,可以根据传入的变量,复制指针所指向的资源。
拷贝构造函数的格式为:构造函数名(对象的引用)
提供了拷贝构造函数后的CExample类定义为:
class CExample
{
public:
CExample(){pBuffer=NULL; nSize=0;}
~CExample(){delete pBuffer;}
CExample(const CExample&); //拷贝构造函数
void Init(int n){ pBuffer=new char[n]; nSize=n;}
private:
char *pBuffer; //类的对象中包含指针,指向动态分配的内存资源
int nSize;
};
CExample::CExample(const CExample& RightSides) //拷贝构造函数的定义
{
nSize=RightSides.nSize; //复制常规成员
pBuffer=new char[nSize]; //复制指针指向的内容
memcpy(pBuffer,RightSides.pBuffer,nSize*sizeof(char));
}
这样,定义新对象,并用已有对象初始化新对象时,CExample(const CExample& RightSides)将被调用,而已有对象用别名RightSides传给构造函数,以用来作复制。
原则上,应该为所有包含动态分配成员的类都提供拷贝构造函数。
拷贝构造函数的另一种调用。
当对象直接作为参数传给函数时,函数将建立对象的临时拷贝,这个拷贝过程也将调同拷贝构造函数。
例如
BOOL testfunc(CExample obj);
testfunc(theObjone); //对象直接作为参数。
BOOL testfunc(CExample obj)
{
//针对obj的操作实际上是针对复制后的临时拷贝进行的
}
还有一种情况,也是与临时对象有关的
当函数中的局部对象被被返回给函数调者时,也将建立此局部对象的一个临时拷贝,拷贝构造函数也将被调用
CTest func()
{
CTest theTest;
return theTest
}
二、赋值符的重载
下面的代码与上例相似
int main(int argc, char* argv[])
{
CExample theObjone;
theObjone.Init(40);
CExample theObjthree;
theObjthree.Init(60);
//现在需要一个对象赋值操作,被赋值对象的原内容被清除,并用右边对象的内容填充。
theObjthree=theObjone; //对一个以存在的对象赋值
return 0;
}
也用到了"="号,但与"一、"中的例子并不同,"一、"的例子中,"="在对象声明语句中,表示初始化。更多时候,这种初始化也可用括号表示。
例如 CExample theObjone(theObjtwo);
而本例子中,"="表示赋值操作。将对象theObjone的内容复制到对象theObjthree;,这其中涉及到对象theObjthree原有内容的丢弃,新内容的复制。
但"="的缺省操作只是将成员变量的值相应复制。旧的值被自然丢弃。
由于对象内包含指针,将造成不良后果:指针的值被丢弃了,但指针指向的内容并未释放(delete)。指针的值被复制了,但指针所指内容并未复制。
因此,包含动态分配成员的类除提供拷贝构造函数外,还应该考虑重载"="赋值操作符号。
类定义变为:
class CExample
{
...
CExample(const CExample&); //拷贝构造函数
CExample& operator = (const CExample&); //赋值符重载
...
};
//赋值操作符重载
CExample & CExample::operator = (const CExample& RightSides)
{
nSize=RightSides.nSize; //复制常规成员
char *temp=new char[nSize]; //复制指针指向的内容
memcpy(temp,RightSides.pBuffer,nSize*sizeof(char));
delete []pBuffer; //删除原指针指向内容 (将删除操作放在后面,避免X=X特殊情况下,内容的丢失)
pBuffer=temp; //建立新指向
return *this
}
三、拷贝构造函数使用赋值运算符重载的代码。
CExample::CExample(const CExample& RightSides)
{
pBuffer=NULL;
*this=RightSides //调用重载后的"="
}
----------------------------------------------------------------------------------
如果不主动编写拷贝构造函数和赋值函数,编译器将以“位拷贝”的方式自动生成缺省的函数。倘若类中含有指针变量,那么这两个缺省的函数就隐
含了错误。
以类String 的两个对象a,b 为例,假设a.m_data 的内容为“hello”,b.m_data 的内容为“world”。
现将a 赋给b,缺省赋值函数的“位拷贝”意味着执行b.m_data = a.m_data。
这将造成三个错误:
1) b.m_data 原有的内存没被释放,造成内存泄露;
2) b.m_data 和a.m_data 指向同一块内存,a 或b 任何一方变动都会影响另一方;
3) 在对象被析构时,m_data 被释放了两次。
类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 的私有函数。
转载结束,以下是个人总结
其实在拷贝构造函数使用最多的地方函数调用与返回,要注意的是什么时候会使用拷贝构造函数,在对象参数传入时,还有就是对象返回时,其实都会建立一个拷贝,其实也就默认调用了拷贝构造函数。这点是需要注意的。
C++的确很烦,特别是面向对象。
龙鱼家园专题
微足迹 记录旅行 分享地方特色
WeiBo
Asp.net技术推荐书
Search
Search
I评论
友情链接
微足迹
资质通鉴
译言
代码发芽网
清清月儿
二频
dotnetblogengine
苹果树下
联系我:
leonardleonard@126.com
© Copyright 2012
Categories
android
asp
Asp.net MVC
CSS
flex
H.264
HTML5
IM
Java
javascript
linux
Memcache
Ogre
php
python
TaoBao
ubuntu
wap
Web
XML
安全
产品
盗墓者
房产
管理
互联网
活动思考
技术
技术 - ASPnet
技术 - C++
技术 - WCF
架构
监控
金融
经济
开源
旅游
美食
苹果
其他
汽车
手机相关
数据结构
数据库
退休计划
新闻
移动
游戏
游戏开发
哲学
知识
专题 - BlogNet
专题 - discuz
专题 - LINQ
装修
Tags
101
aion
apple
baidu
chrome
comet
div
formsauthenticationticket
google
http handler
http module
idc
ihttphandler
il
ip过滤
java安装
jolicloud
link
linq
linux
msvcp90d.dll
openapi
partial class
rss
sns
soa
soap
sql
subsonic
thoughtworks
twitter
ultraedit
vs2008
wcf
web
web2.0
webgame
web架构
windows 7
winform
暗时间
百度快照
比尔盖茨
博客
大众4s
多线程
反射
范美忠
防刷票
房屋租售比
高莽
工具
公积金
共同基金
互联网
化妆品
货币
架构
架构师
健康
交规
教育
金融衍生工具
静态化
开源
科斯定理
利率史与风险溢价
枚举器
啤酒,癌症
前端架构师
乔布斯
日全食
塞班
三维地图
视力
手机
水印
睡眠
土地制度
维基
委托
物联网
压缩
业务逻辑
艺术家
异丙酚
异常
应用防火墙
硬盘
游标
支付
装修
自动抓取
左岸读书