c++中赋值操作符的重载

  直接抛问题,两个同类型的对象可以相互赋值?

class cls
{
public:
    int a;
    char c;

    cls() : a(10), c('g')
    {}

    cls(int a, char c) : a(a), c(c)
    {}

};

int main(void)
{
    cls c1(6, 's');

    cls c2;

    c2 = c1;

    printf("c2.a = %d, c2.c = %c\n", c2.a, c2.c);   

    return 0;
}

  编译运行:
这里写图片描述

  显然c++支持两个同类型的对象可以相互赋值的。

  ”c2 = c1;”注意要跟”cls c2 = c1;”区分开,前者是赋值语句,后者是初始化。对于后者,对象c2会调用类的拷贝构造函数,实现将c1到c2的拷贝。关于拷贝构造函数的使用,在前面http://blog.csdn.net/qq_29344757/article/details/76037255一文中有详细介绍。
  两个同类型的对象可以直接使用”=”(赋值操作符)进行赋值,其实这是类已经实现对赋值操作符的重载。但是在上面的代码中,我们并没有定义赋值操作符重载函数,可见,c++类会默认为我们定义。

  综合前面所学习的可知,一个空的c++类:

class cls
{
};

  编译器会为我们默认定义构造函数、拷贝构造函数、析构函数以及赋值操作符重载函数,也就变成:

class cls
{
public:
    cls();
    ~cls();
    cls(const cls& c);
    cls& operator= ();
};

  那么问题出现,默认的赋值操作符重载函数内部实现的深度拷贝还是浅拷贝?

class cls
{
public:
    int a;
    int *p;

    cls()
    {
        p = new int(0);
        a = 0;
    }

    cls(int a, int b)
    {
        p = new int(b);
        this->a = a;
    }

    ~cls()
    {
        delete p;
    }
};

int main(void)
{
    cls c1(6, 7);
    cls c2;

    c2 = c1;    //调用默认的赋值运算符重载函数

    printf("c2.a = %d, *c2.p = %d\n", c2.a, *c2.p);

    return 0;
}

  编译运行:
这里写图片描述
  堆栈出错,提示重复释放内存。
  显然,跟默认拷贝构造一个样,默认的赋值运算符重载函数也是浅拷贝,所以指针变量c2.p和c1.p是一样的。我们需要自定义赋值运算符重载函数(顺便把拷贝构造函数也自定义):

class cls
{
public:
    int a;
    int *p;

    cls()
    {
        p = new int(0);
        a = 0;
    }

    cls(int a, int b)
    {
        p = new int(b);
        this->a = a;
    }

    //自定义拷贝构造函数
    cls(const cls& c)
    {
        p = new int(*c.p);
    }

    //自定义赋值运算符重载函数
    cls& operator= (const cls& c)
    {
        if (this == &c)
            return *this;

        delete p;
        p = NULL;

        p = new int(*c.p);

        return *this;
    }

    ~cls()
    {
        delete p;
    }
};

int main(void)
{
    cls c1(6, 7);
    cls c2;

    c2 = c1;        //调用了cls& operator= (const cls& c)

    printf("c1.p = %p, c2.p = %p\n", c1.p, c2.p);

    printf("c2.a = %d, *c2.p = %d\n", c2.a, *c2.p);

    return 0;
}

编译运行正常:
这里写图片描述

下来仔细看看自定义的操作符重载函数:

cls& operator= (const cls& c)
{
    if (this == &c)
        return *this;

    delete p;
    p = NULL;

    p = new int(*c.p);

    return *this;
}

(1) 参数const cls& c:加上const原因在于我们不希望此函数对用来进行赋值的c做任何修改,其次有加上const的形参,能接受const和非const的实参,反之只能接收非const的实参
(2) 返回值cls&:返回值是返回被赋值着的引用,即*this,这样可以实现连续赋值,即类似于:

x = y = z;

若不是返回引用”cls& “而是直接是”cls”,那返回的是(*this)的副本,再用这个副本做左值,那么就出错了。
(3)避免自赋值:c/c++的语法并不反对类似”a = a”这样的自赋值语法,所以要在操作符重载加以判断避免自赋值操作,一来为了提高效率,二来避免出错。假设如上代码去掉if判断:

cls& operator= (const cls& c)
{
    delete p;
    p = NULL;

    p = new int(*c.p);

    return *this;
}

而*this跟参数c是同一个对象,那么在执行”delete p;”后也就意味着c.p也被delete了,那执行到”p = new int(*c.p);”就出错了,因为被delete后的p已经是一个野指针,对一个野指针解引用就会Segmentation fault。
(4)为什么要先”delete p;”再执行new
因为原先的p是通过类的构造函数new的,要再new一个空间并初始化为(*c.p)就需要先将原来p给delete,不然将造成内存泄漏。其实在这里可以复用p原型的堆空间,那么代码将改成:

cls& operator= (const cls& c)
{
    if (this == &c)
        return *this;

    *p = *c.p;      //这也是深度拷贝

    return *this;
}

这样改成反而看着简单。
(6)赋值运算符的重载函数只能是类的成员函数,不能是是类的静态函数(因为静态成员函数只能操作类的静态成员),也不能是(友元)全局函数,否则在编译阶段就出错了!假设可以为全局函数,c++类已经默认提供了赋值重载函数了,那么在赋值运算符重载函数(全局函数)和赋值运算符重载函数(类的成员函数)同时存在的情况下,当进行相同类型间的赋值时,编译器就不知道要调用哪一个函数了。再者,假设可以用全局函数重载赋值操作符:

int operator= (int a, cls& c)
{
    //...
}

int main(void)
{
    cls c(5);

    6 = c;      //哥们,这就有点过分了

    return 0;
}

(7) 调用的时机
对比拷贝构造函数和赋值运算符重载函数的代码,可见除了避免自赋值判断之外,赋值运算符重载函数还比拷贝构造函数多了一句delete。一开始我很纳闷,想不出为何,其实那是我忽略了初始化和赋值这两个小玩意。初始化调用的是拷贝构造函数,”p = new int(*c.p);”语句是对象首次动态分配空间中边分配边为该空间初始化的,但是在赋值时调用的是赋值运算符重载函数,”p = newint(*c.p);”是在第二次分配空间的时候变分配边为该空间初始化的,所以需要把上次的new到的空间delete。

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值