赋值运算,拷贝运算,运算符重载

赋值运算与拷贝运算的区别

如果对象在申明之后进行赋值运算,我们称之为赋值运算。例如:
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;
}

 

posted on 2014-12-20 15:05  joannae  阅读(762)  评论(0编辑  收藏  举报

导航