赋值运算,拷贝运算,运算符重载
赋值运算与拷贝运算的区别
如果对象在申明之后进行赋值运算,我们称之为赋值运算。例如:
class1 A("af"); class1 B;
B=A;
此时实际调用的类的缺省赋值函数B.operator=(A);
如果对象在申明的同时马上进行初始化操作,则称之为拷贝运算。例如:
class1 A("af"); class1 B=A;
此时其实际调用的是B(A)这样的浅拷贝操作。
C++与C#对象的内存分配方式的不同
在C++中,对象的实例在编译的时候,就需要为其分配内存大小,因此,系统都是在stack上为其分配内存的。
在C#中,所有类都是reference type,要创建类的实体,必须通过new在heap上为其分配空间,同时返回在stack上指向其地址的reference.
C++中赋值操作的内存情况
class A { public: A() { } A(int id,char *t_name) { _id=id; name=new char[strlen(t_name)+1]; strcpy(name,t_name); } private: char *username; int _id; } int main() { A a(1,"herengang"); A b;
b=a; }
b=a执行的是缺省的赋值运算。所谓缺省的赋值运算,是指对象中的所有位于stack中的域,进行相应的复制。但是,如果对象有位于heap上的域的话,其不会为拷贝对象分配heap上的空间,而只是指向相同的heap上的同一个地址。
执行b=a这样的缺省的赋值运算后,其内存分配如下
因此,对于缺省的赋值运算,如果对象域内没有heap上的空间,其不会产生任何问题。但是,如果对象域内需要申请heap上的空间,那么在析构对象的时候,就会连续两次释放heap上的同一块内存区域,从而导致异常。
解决办法--重载(overload)赋值运算符
class A { public: A() { } A(int id,char *t_name) { _id=id; name=new char[strlen(t_name)+1]; strcpy(name,t_name); } A& operator =(A& a) //注意:此处一定要返回对象的引用,否则返回后其值立即消失! { if(name!=NULL) delete name; this->_id=a._id; int len=strlen(a.name); name=new char[len+1]; strcpy(name,a.name); return *this; } ~A() { cout<<"~destructor"<<endl; delete name; } int _id; char *name; }; int main() { A a(1,"herengang"); A b; b=a; }
如何重载赋值运算符
法I:返回类对象的引用
A& A::operaton=(A &a) { if(this==&a)// 考虑a=a这样的操作。 return *this; if(username!=NULL) //释放自身的堆空间 delete username; _id=a._id; username=new char[strlen(a.username)+1]; if(username!=NULL) strcpy(username,a.usernam); return *this; }
其过程如下:
1 释放掉目标对象原来占有的堆空间
2 申请一块新的堆内存
3 将源对象的堆内存的值深度拷贝给新的堆内存
4 返回目标对象的引用
5 结束。
法II:返回类对象本身
其过程是这样的:
1 释放目标对象原来的堆资源
2 重新申请堆空间
3 拷贝源的值到目标对象的堆空间
4 调用临时对象拷贝构造函数创建临时对象,将临时对象返回(因为函数返回时,会清空栈,在栈中的目标对象也会被清,所以需要临时对象)
5.临时对象结束,调用临时对象析构函数,释放临时对象堆内存
如果第4步,我们没有overload 拷贝函数,也就是没有进行深拷贝。那么在进行第5步释放临时对象的heap 空间时,将释放掉的是和目标对象同一块的heap空间。这样当目标对象B作用域结束调用析构函数时,就会产生错误!
因此,如果赋值运算符返回的是类对象本身,那么一定要overload 类的拷贝函数(进行深拷贝)!
法III:返回void
如果这样的话,他将不支持客户代买中的链式赋值 ,例如a=b=c will be prohibited!
拷贝构造函数
赋值函数最好是对象的引用, 而拷贝函数不需要返回任何。
同时,赋值函数首先要释放掉对象自身的堆空间,然后进行其他的operation。而拷贝函数不需要如此,因为对象此时还没有分配堆空间。
A::A(A &a) { int len=strlen(a.m_username); this->m_username=new char[len+2]; strcpy(m_username,a.m_username); strcat(m_username,"f"); printf("\ndeep copy function"); }
运算符的重载
返回对象本身。因为如果返回引用,会导致擦操作数被意外修改,比如a+b=c,会将c赋给a
class Complex //复数类 { private://私有 double real;//实数 double imag;//虚数 public: Complex(double real=0,double imag=0) { this->real=real; this->imag=imag; } Complex operator+(int x); }; Complex Complex::operator+(int x) { return Complex(real+x,imag); } int main() { Complex com1(5,10),total; total=com1+5; return0; }