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

来源:互联网 发布:python hmmlearn库 编辑:程序博客网 时间:2024/06/07 16:36

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

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。

原创粉丝点击