例子
class CExample
{
private:
char *pBuffer; //类的对象中包含指针,指向动态分配的内存资源
int nSize;
public:
CExample() {pBuffer=NULL;nSize=0;}
~CExample() {delete pBuffer;}
void Init(int n) { pBuffer=new char[n]; nSize=n;}
};
int main( )
{
CExample theObjone; //对象创建,调用构造函数
theObjone.Init(40);
CExample theObjtwo=theObjone; //对象复制(因初始化),调用拷贝构造函数
(有指针成员,使用默认拷贝构造有缺点(仅复制指针),故要用自定义的)
CExample theObjthree;
theObjthree.Init(60);
theObjthree=theObjone; //对象赋值操作(因非初始化),调用赋值运算符
(有指针成员,使用默赋值符“=”有缺点(旧指针被丢弃),故要重载运算符)
}
注意:"="号的两种不同使用,初始化和赋值
第三行也用到了"="号,该"="在对象声明语句中,表示初始化。更多时候,这种初始化也可用括号()表示。而最后一行的"="号是赋值符,使用默认赋值符"=",是把被赋值对象的原内容被清除,并用右边对象的内容填充。
构造函数 主要用来在创建对象时, 即为对象成员变量赋初始值
拷贝构造函数 形式为X(X&),用来完成基于同一类的其他对象的构建及初始化
一个对象需要通过另外一个对象进行初始化,调用拷贝构造函数。
初始化和赋值的不同含义是构造函数调用的原因。事实上,拷贝构造函数是由普通构造函数和赋值操作符共同实现的
默认拷贝构造函数(浅拷贝)
CExample theObjtwo=theObjone;"用theObjone初始化theObjtwo。
调用默认拷贝构造含, 其完成方式是内存拷贝(浅拷贝),复制所有成员的值。
完成后,theObjtwo.pBuffer==theObjone.pBuffer。 即它们将指向同样的地方,指针虽然复制了,但所指向的空间并没有复制,而是由两个对象共用了。
这样不符合要求,对象之间不独立了,并为空间的删除带来隐患(产生野指针)
自定义拷贝构造函数(深拷贝)
因此可以在构造函数中添加操作来解决指针成员的问题,即自定义的拷贝构造函数
//自定义拷贝构造函数
CExample::CExample(const CExample& RightSides)
{
nSize=RightSides.nSize; //复制常规成员
pBuffer=new char[nSize]; //复制指针指向的内容
memcpy(pBuffer,RightSides.pBuffer,nSize*sizeof(char));
}
这样,定义新对象,并用已有对象初始化新对象时,CExample(const CExample& RightSides)将被调用,而已有对象用别名RightSides传给构造函数,以用来作复制。
原则上,应该为所有包含动态分配成员的类都提供自定义的拷贝构造函数。
注意:内存拷贝函数void *memcpy(void *dest, constvoid *src, size_t n)
默认赋值操作符"="
//赋值操作符重载
CExample & CExample::operator = (const CExample& RightSides)
{
nSize=RightSides.nSize; //复制常规成员
char *temp=new char[nSize];//复制指针指向的内容(默认=的缺点)
memcpy(temp,RightSides.pBuffer,nSize*sizeof(char));
delete []pBuffer; //删除原指针指向内容(避免内存泄露)
pBuffer=NULL; //同时把原指针置为NULL(避免野指针)
pBuffer=temp; //建立新指向
return *this ;
}
"="的缺省(默认)操作只是将成员变量的值相应复制,旧的值被自然丢弃
最后一行的"="表示赋值操作,将对象theObjone的内容复制到对象theObjthree,这其中涉及到对象theObjthree原有内容的丢弃,新内容的复制。
由于对象内包含指针,将造成不良后果:指针的值被丢弃了,但指针指向的内容并未释放(导致内存泄露)。指针的值被复制了,但指针所指内容并未复制(导致新指针指的内容丢了)
即产生两方面的问题:丢弃旧指针(导致内存泄露),仅复制新指针(导致内容丢了)
因此,包含动态分配成员的类除了提供拷贝构造函数外,还应该考虑重载"="赋值操作符号
重载"="赋值操作符