您的位置:首页 > 编程语言 > C语言/C++

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

2017-08-08 12:43 246 查看
  直接抛问题,两个同类型的对象可以相互赋值?

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。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: