关于C++赋值运算符重载的简单瞎掰
总体思路
- 代码编写
- 代码分析改进
代码编写与分析
- 情景再现
存在一个class Test
class Test {
public:
//为方便调试,已将构造函数分四类
//默认构造
Test() {
m_a = 0.0;
m_b = 0;
cout << "Default Constructor" << endl;
}
//普通构造
Test(double a, int b) {
m_a = a;
m_b = b;
cout << "Nomal Constructor" << endl;
}
//拷贝构造
Test(const Test & t) {
this->m_a = t.m_a;
this->m_b = t.m_b;
cout << "Copy Constructor" << endl;
}
//转换构造
Test(double a) {
m_a = a;
m_b = 0;
cout << "Conversion Constructor" << endl;
}
//输出结果
void display() {
cout << "Result = " << m_a << "and" << m_b << endl;
}
//析构函数
~Test() {
cout << "destructor" << endl;
}
private:
double m_a;
int m_b;
};
- 要实现的功能
- 赋值运算符重载
即能完成Test b = Test a
- 赋值运算符重载
版本一
//赋值重载
void operator =(const Test & right) {
this->m_a = right.m_a;
this->m_b = right.m_b;
}
此时主函数:
int main() {
Test a(1.1, 2);
Test b;
Test c;
b = a;
cout << "a : "; a.display();
cout << "b : "; b.display();
return 0;
}
运行结果表明,要求暂时达到,a的值已经赋给b
学习过C和C++我们知道,在C里像
c = b = a
这样的赋值语句不符合语法,但在C++可以。因此我们进一步尝试
将上面主函数修改
int main() {
Test a(1.1, 2);
Test b;
Test c;
c = b = a;
cout << "a : "; a.display();
cout << "b : "; b.display();
return 0;
}
这时编译器报错不予通过。为什么?
首先分析刚刚的例子
在进行到 b = a
时,实则进行的是 b.operator=(const a)
,因此this 指向对象b,right被赋予的是对象a。可知在函数体中已经完成了所需功能,最后函数返回一个void空对象(匿名对象)
返回到 c = b = a
这个过程,由 =
具右结合性(从右向左运行)。因此可等效为c = (b = a)
第一阶段,通过前面的分析我们知道在前段的 b = a
中 a 已经将它其中的值完完本本复制给了 b,函数体执行完毕后高高兴兴地返回了一个void匿名对象(临时变量),为了方便描述就把这个匿名对象叫做 anony。
进入第二阶段 c = anony
。还是转换成 c.operator=(const anony)
,但此时出现了问题,operator=
函数的参数为Test类型,而anony是void。void类型又不能隐式地转换成Test类型。就此编译器报错。
版本二
鉴于上面的分析,已经知道问题出在void类型上。经过修改后结果如下:
//赋值重载
Test operator =(const Test & right) {
this->m_a = right.m_a;
this->m_b = right.m_b;
return *this;
}
int main() {
Test a(1.1, 2);
Test b;
Test c;
c = b = a;
cout << "a : "; a.display();
cout << "b : "; b.display();
cout << "c : "; c.display();
return 0;
}
编译器未报错,程序正常进行,要求也达到。
观察运行结果发现,其中Copy Constructor
被运两次且都在 Assign
前。说明在运行 =
前每次都进行了拷贝对象的操作。读者可自行尝试更长的赋值表达式。因此可知随着表达式长度的增加和对象成员多元程度的上升会严重影响程序的运行效率。
版本三
鉴于对版本二缺点的评价,此次修改可以借助引用来完善。结果如下:
//赋值重载
Test & operator =(const Test & right) {
this->m_a = right.m_a;
this->m_b = right.m_b;
cout << "Assign" << endl;
return *this;
}
运行后发现 Copy Construtor
不再出现,说明拷贝构造函数不会再被引用所调用。
评价:
引用的本质是指针,因此不论类的内部有多复杂亦或是多简单,对于指针来说只需要完成一次赋值即可,另外指针的赋值不需要调用拷贝构造函数,所以减少了不少开销;此外在用户调用的角度上,引用比直接使用指针更加方便,体验更友好。