文章目录
1. 重载赋值运算符
C++允许类对象赋值,这是通过自动为类重载赋值运算符实现的。即有一个 Demo类, d1,d2 都是Demo 类对象的实例,c++编译器是允许
d1 = d2;
上面的操作不需要做实现任何函数,编译器会自动生成一个赋值运算符的函数。来完成这个操作。
这种运算符的原型如下
Class_name & Class_name::operator=(const Class_name &);
它接受一个类对象引用参数并且返回一个指向类对象的引用。
例如, Demo类的赋值运算符的原型如下
Demo & Demo::operator=(const Demo&);
对于赋值运算符,需要知道两点:何时调用和有何功能。
2.赋值运算符的功能以及何时使用它
将已有的对象赋给另一个对象时,将使用重载的赋值运算符
Demo d1(1, "d1");
Demo d2;
d2 = d1; //assignment operator= invoked
初始化对象时,并不一定会使用赋值运算符:
Demo d2 = d1; //use copy constructor, possibly assignment, too
这里, d2 是一个新创建的对象,被初始化为d1 的值,因此使用复制构造函数。然而,正如前面指出的,实现时也可能分两步来处理这条语句:使用复制构造函数创建一个临时对象,然后通过赋值将临时对象的值复制到新对象中。
这就是说,初始化总是会调用复制构造函数,而使用=运算符时也可能调用赋值运算符。
与复制构造函数相似,赋值运算符的隐式实现也对成员进行逐个复制。如果成员本身就是类对象,则程序将使用为这个类定义的赋值运算符来复制该成员,但静态数据成员不受影响。
这里有两种情况会用到赋值运算符,
第一种是对象初始化的时候
Demo d1(1, "d1");
Demo d2 = d1; //use copy constructor, possibly assignment, too
第二种是已有的对象赋给另一个对象时
Demo d1(1, "d1");
Demo d2;
d2 = d1; //assignment operator= invoked
3. 实际的一个例子来探讨重载赋值运算符的各种情况
1. 对象初始化的时候?
对象初始化的时候,是一定会调用复制构造函数的。
Demo d1(1, "d1");
Demo d2 = d1; //use copy constructor, possibly assignment, too
这个在之前的文章中有探讨过:
P18-c++复制构造函数深度剖析介绍,详细的例子演示!
但是由于之前的代码并没有定义 operator=()
这个函数,并不知道有没有调用过,但是从理论分析应该能想到,既然肯定会调用复制构造函数,那么我们应该会在复制构造函数里面把该做的工作都实现掉,不会再麻烦到 复制构造函数返回一个临时对象,然后在 让 d2 = temp
, 再去 d2.operator=(temp)
;多增加一次函数调用,因为这两个函数的功能是一样的,都是拷贝。
所以我们增加了自己定义的 operator=()
应该也不会调用,毕竟写编译器的大佬肯定优化的非常好。
equal_overload.cpp
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
#include <iostream>
#include <typeinfo>
#include <string>
using namespace std;
class Demo {
private:
int iAge;
string var_name;
public:
Demo() {
iAge = 0;
var_name = "temp";
cout << "Demo(), this(" << this->var_name << "):" << this << ", this->iAge:" << this->iAge << endl;
}
Demo(int i, string name = "meng-yue") {
this->iAge = i;
this->var_name = name;
cout << "Demo(int i),this(" << this->var_name << "):" << this << ", i:" << i << ", this->iAge:" << this->iAge << endl;
}
Demo(const Demo & d_copy) {
cout << "Demo(const Demo &d),this(" << this->var_name << "):" << this << ", &d_copy(" << d_copy.var_name << "):" << &d_copy << endl;
}
Demo & operator=(const Demo & d_var) {
cout << "operator=(const Demo&d_var),this(" << this->var_name << "):" << this << ", &d_var(" << d_var.var_name << "):" << &d_var;
cout << ", this->iAge:" << this->iAge << ", d_var.iAge:" << d_var.iAge << endl;
}
~Demo() {
cout << "~Demo,this(" << this->var_name << "):" << this << endl;
}
void Show() {
cout << "Show(),this(" << this->var_name << "):" << this << ", this->iAge:" << this->iAge << endl;;
}
};
int main()
{
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
Demo d1(1, "d1");
Demo d2 = d1; //use copy constructor, possibly assignment, too
d2.Show();
cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
return 0;
}
编译运行的结果:
可以看出来确实没有调用,operator=
函数,只是调用了复制构造函数。
book@book-desktop:~/meng-yue/c++/class_dynamic_memory/03$ ./equal_overload
---------------开始--->公众号:梦悦foundation---------------
Demo(int i),this(d1):0xbf87bec0, i:1, this->iAge:1
Demo(const Demo &d),this():0xbf87beb8, &d_copy(d1):0xbf87bec0
Show(),this():0xbf87beb8, this->iAge:-1081622808
---------------结束--->公众号:梦悦foundation---------------
~Demo,this():0xbf87beb8
~Demo,this(d1):0xbf87bec0
book@book-desktop:~/meng-yue/c++/class_dynamic_memory/03$
但是也有一种情况例外,就是对象初始化的时候,使用的是一个函数的返回值,函数的返回值是类对象。可以看另一篇我写的文章。
P18-c++复制构造函数深度剖析介绍,详细的例子演示!
- 第6种,Demo func(),引发了很多思考
2.第二种是已有的对象赋给另一个对象时
这个时候是肯定会调用赋值运算符的。
源码资料路径
4. 深入探讨一些赋值运算符的场景
1. 多个对象相加
之前在重载+
运算符的时候,在下面的这个文章里面,提到一个例子。
P13-c++使用类-01运算符重载详细介绍,详细的例子演示!
实现3个Demo 类的对象相加
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
#include <iostream>
#include <typeinfo>
#include <string>
using namespace std;
class Demo {
private:
int iAge;
string var_name;
public:
Demo() {
iAge = 0;
var_name = "temp";
cout << "Demo(), this(" << this->var_name << "):" << this << ", this->iAge:" << this->iAge << endl;
}
Demo(int i, const string & name = "meng-yue") {
this->iAge = i;
this->var_name = name;
cout << "Demo(int i),this(" << this->var_name << "):" << this << ", i:" << i << ", this->iAge:" << this->iAge << endl;
}
Demo(const Demo & d_copy) {
cout << "Demo(const Demo &d),this(" << this->var_name << "):" << this << ", &d_copy(" << d_copy.var_name << "):" << &d_copy << endl;
}
Demo & operator=(const Demo & d_var) {
cout << "operator=(const Demo&d_var),this(" << this->var_name << "):" << this << ", &d_var(" << d_var.var_name << "):" << &d_var;
cout << ", this->iAge:" << this->iAge << ", d_var.iAge:" << d_var.iAge << endl;
}
Demo operator+(const Demo &d_var) const
{
Demo result;
cout << "operator+(const Demo &d),this(" << this->var_name << "):" << this << ", &d_var(" << d_var.var_name << "):" << &d_var;
cout << ", this->iAge:" << this->iAge << ", d_var.iAge:" << d_var.iAge << endl;
result.iAge = this->iAge + d_var.iAge;
result.var_name = this->var_name + d_var.var_name;
return result;
}
~Demo() {
cout << "~Demo,this(" << this->var_name << "):" << this << endl;
}
void Show() {
cout << "Show(),this(" << this->var_name << "):" << this << ", this->iAge:" << this->iAge << endl;;
}
};
Demo func()
{
Demo d1(1, "d1");
cout << "func()" << endl;
return d1;
}
int main()
{
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
Demo d4(0 , "d4");
Demo d1(11, "d1");
Demo d2(12, "d2");
Demo d3(13, "d3");
d4 = d1 + d2 + d3;
d4.Show();
cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
return 0;
}
我们先看看这个代码运行的结果:
book@book-desktop:~/meng-yue/c++/class_dynamic_memory/03$ ./equal_overload2
---------------开始--->公众号:梦悦foundation---------------
Demo(int i),this(d4):0xbfb82074, i:0, this->iAge:0
Demo(int i),this(d1):0xbfb8206c, i:11, this->iAge:11
Demo(int i),this(d2):0xbfb82064, i:12, this->iAge:12
Demo(int i),this(d3):0xbfb8205c, i:13, this->iAge:13
Demo(), this(temp):0xbfb82054, this->iAge:0
operator+(const Demo &d),this(d1):0xbfb8206c, &d_var(d2):0xbfb82064, this->iAge:11, d_var.iAge:12
Demo(), this(temp):0xbfb8204c, this->iAge:0
operator+(const Demo &d),this(d1d2):0xbfb82054, &d_var(d3):0xbfb8205c, this->iAge:23, d_var.iAge:13
operator=(const Demo&d_var),this(d4):0xbfb82074, &d_var(d1d2d3):0xbfb8204c, this->iAge:0, d_var.iAge:36
~Demo,this(d1d2d3):0xbfb8204c
~Demo,this(d1d2):0xbfb82054
Show(),this(d4):0xbfb82074, this->iAge:0
---------------结束--->公众号:梦悦foundation---------------
~Demo,this(d3):0xbfb8205c
~Demo,this(d2):0xbfb82064
~Demo,this(d1):0xbfb8206c
~Demo,this(d4):0xbfb82074
book@book-desktop:~/meng-yue/c++/class_dynamic_memory/03$
首先这个operator+ 不能返回引用,因为result 一旦执行完毕,内存就被释放了。所以只能返回值。这肯定会增加复制构造函数的开销。
我们从头看看代码的运行过程:
首先创建4个Demo对象,打印出他们的地址和成员变量
Demo(int i),this(d4):0xbfb82074, i:0, this->iAge:0
Demo(int i),this(d1):0xbfb8206c, i:11, this->iAge:11
Demo(int i),this(d2):0xbfb82064, i:12, this->iAge:12
Demo(int i),this(d3):0xbfb8205c, i:13, this->iAge:13
然后执行 d4 = d1 + d2 + d3;
, 这条语句 先执行 d1 + d2
, 相当于 d1.operator(d2);
然后返回 result, 这个地方正常应该是会创建一个临时对象的。
Demo(), this(temp):0xbfb82054, this->iAge:0
operator+(const Demo &d),this(d1):0xbfb8206c, &d_var(d2):0xbfb82064, this->iAge:11, d_var.iAge:12
但是从下面的打印看来,没有创建一个临时对象,因为复制构造函数并没有调用
相当于 (d1+d2).operator(d3)
,计算完,返回的时候,也没有调用复制构造函数
Demo(), this(temp):0xbfb8204c, this->iAge:0
operator+(const Demo &d),this(d1d2):0xbfb82054, &d_var(d3):0xbfb8205c, this->iAge:23, d_var.iAge:13
从接下来的打印中,能看出来,直接省略掉了临时对象的生成,直接调用了赋值运算符完成工作。
operator=(const Demo&d_var),this(d4):0xbfb82074, &d_var(d1d2d3):0xbfb8204c, this->iAge:0, d_var.iAge:36
虽然没有调用复制构造函数,而是直接使用生成的 result临时对象。
operator+(const Demo &d),this(d1d2):0xbfb82054, &d_var(d3):0xbfb8205c, this->iAge:23, d_var.iAge:13
我估计是写编译器的人看到这是一个三个对象相加,或者多个对象相加的过程,所以可以进行这种优化,让c++语言效率更高。因为少好几个复制构造函数的开销。
2. 类型转换和赋值运算符的分析
整型或者double等标准类型可以赋值给一个类对象,例如Demo是一个类。
P15-c++使用类-03类的自动转換和强制类型转换详细介绍,详细的例子演示!
Demo d = 12;
或者
Demo d = 12.2;
允许这么做的前提条件是有一个能够接受这些参数的构造函数,且形参有且只有一个.
初始化的时候,除了调用带一个参数的构造函数还会调用 operator=
吗?
equal_overload3.cpp
int main()
{
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
Demo d1 = 12;
d1.Show();
cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
return 0;
}
编译运行的结果:
可见只是调用了 带一个参数的构造函数,并没有调用赋值运算符。但是之前分享过,新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用, 这里的原因应该也是被编译器进行优化过了。
book@book-desktop:~/meng-yue/c++/class_dynamic_memory/03$ ./equal_overload3
---------------开始--->公众号:梦悦foundation---------------
Demo(int i),this(meng-yue):0xbfeff940, i:12, this->iAge:12
Show(),this(meng-yue):0xbfeff940, this->iAge:12
---------------结束--->公众号:梦悦foundation---------------
~Demo,this(meng-yue):0xbfeff940
book@book-desktop:~/meng-yue/c++/class_dynamic_memory/03$
我们尝试将返回优化关闭掉去,下面的这个结果就比较符合逻辑
先使用构造函数 创建一个临时的对象
Demo(int i),this(meng-yue):0xbfa341a8, i:12, this->iAge:12
地址是 0xbfa341a8
, 紧接着 Demo d1 = 创建的临时对象(temp)
这个时候就会调用复制构造函数了
Demo(const Demo &d),this():0xbfa341b0, &d_copy(meng-yue):0xbfa341a8
复制构造函数传递进来的形参刚好是上面创建的那个临时对象,然后这条赋值语句执行完,这个临时变量就被析构掉了。
book@book-desktop:~/meng-yue/c++/class_dynamic_memory/03$ g++ -fno-elide-constructors -o equal_overload3 equal_overload3.cpp
book@book-desktop:~/meng-yue/c++/class_dynamic_memory/03$ ./equal_overload3
---------------开始--->公众号:梦悦foundation---------------
Demo(int i),this(meng-yue):0xbfa341a8, i:12, this->iAge:12
Demo(const Demo &d),this():0xbfa341b0, &d_copy(meng-yue):0xbfa341a8
~Demo,this(meng-yue):0xbfa341a8
Show(),this():0xbfa341b0, this->iAge:2880260
---------------结束--->公众号:梦悦foundation---------------
~Demo,this():0xbfa341b0
book@book-desktop:~/meng-yue/c++/class_dynamic_memory/03$
那么将12 赋值给已存在的对象呢?
equal_overload4.cpp
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
Demo d1(1, "d1");
d1 = 12;
d1.Show();
cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
return 0;
这个肯定是会导致赋值运算符被调用的
编译运行的结果:
在调用了赋值运算符之后,12生成的临时Demo对象就被析构掉了
book@book-desktop:~/meng-yue/c++/class_dynamic_memory/03$ g++ -o equal_overload4 equal_overload4.cpp
book@book-desktop:~/meng-yue/c++/class_dynamic_memory/03$ ./equal_overload4
---------------开始--->公众号:梦悦foundation---------------
Demo(int i),this(d1):0xbfd683ec, i:1, this->iAge:1
Demo(int i),this(meng-yue):0xbfd683e4, i:12, this->iAge:12
operator=(const Demo&d_var),this(d1):0xbfd683ec, &d_var(meng-yue):0xbfd683e4, this->iAge:1, d_var.iAge:12
~Demo,this(meng-yue):0xbfd683e4
Show(),this(d1):0xbfd683ec, this->iAge:1
---------------结束--->公众号:梦悦foundation---------------
~Demo,this(d1):0xbfd683ec
book@book-desktop:~/meng-yue/c++/class_dynamic_memory/03$
因为这种情况是不会导致复制构造函数调用的,所以关不关闭掉返回优化,都没什么影响
book@book-desktop:~/meng-yue/c++/class_dynamic_memory/03$ g++ -fno-elide-constructors -o equal_overload4 equal_overload4.cpp
book@book-desktop:~/meng-yue/c++/class_dynamic_memory/03$ ./equal_overload4
---------------开始--->公众号:梦悦foundation---------------
Demo(int i),this(d1):0xbfaf4d7c, i:1, this->iAge:1
Demo(int i),this(meng-yue):0xbfaf4d74, i:12, this->iAge:12
operator=(const Demo&d_var),this(d1):0xbfaf4d7c, &d_var(meng-yue):0xbfaf4d74, this->iAge:1, d_var.iAge:12
~Demo,this(meng-yue):0xbfaf4d74
Show(),this(d1):0xbfaf4d7c, this->iAge:1
---------------结束--->公众号:梦悦foundation---------------
~Demo,this(d1):0xbfaf4d7c
book@book-desktop:~/meng-yue/c++/class_dynamic_memory/03$
3. 赋值运算符的返回值是引用还是应该是类对象
我们看Demo类的赋值运算符的原型
Demo & operator=(const Demo & d_var)
因为赋值运算符一般使用场景都是 A = B
会被翻译成 A.operator=(B)
一般不会用到它的返回值,但是毕竟返回对象可能会导致一次复制构造函数调用,而且又用不到,所以最好只改成返回引用
另外返回引用在 d3 = d2 = d1;
这种运算当中效率也比较高!
这里可以看一下对比的结果,返回类对象和返回引用的对比
equal_overload5.cpp
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
Demo d1(1, "d1");
Demo d2(2, "d2");
Demo d3(3, "d3");
d3 = d2 = d1;
cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
return 0;
编译运行结果:
book@book-desktop:~/meng-yue/c++/class_dynamic_memory/03$ ./equal_overload5
---------------开始--->公众号:梦悦foundation---------------
Demo(int i),this(d1):0xbfb2bb88, i:1, this->iAge:1
Demo(int i),this(d2):0xbfb2bb80, i:2, this->iAge:2
Demo(int i),this(d3):0xbfb2bb78, i:3, this->iAge:3
operator=(const Demo&d_var),this(d2):0xbfb2bb80, &d_var(d1):0xbfb2bb88, this->iAge:2, d_var.iAge:1
operator=(const Demo&d_var),this(d3):0xbfb2bb78, &d_var(d2):0xbfb2bb80, this->iAge:3, d_var.iAge:2
---------------结束--->公众号:梦悦foundation---------------
~Demo,this(d3):0xbfb2bb78
~Demo,this(d2):0xbfb2bb80
~Demo,this(d1):0xbfb2bb88
book@book-desktop:~/meng-yue/c++/class_dynamic_memory/03$
这就相当于只是变成了
d3.operator=(d2.operator=(d1) );
因为返回只是引用,不会调用复制构造函数。
但是如果返回的是类对象呢?
equal_overload6.cpp
Demo operator=(const Demo & d_var) {
cout << "operator=(const Demo&d_var),this(" << this->var_name << "):" << this << ", &d_var(" << d_var.var_name << "):" << &d_var;
cout << ", this->iAge:" << this->iAge << ", d_var.iAge:" << d_var.iAge << endl;
return *this;
}
编译运行的结果:
book@book-desktop:~/meng-yue/c++/class_dynamic_memory/03$ g++ -o equal_overload6 equal_overload6.cpp
book@book-desktop:~/meng-yue/c++/class_dynamic_memory/03$ ./equal_overload6
---------------开始--->公众号:梦悦foundation---------------
Demo(int i),this(d1):0xbfd40118, i:1, this->iAge:1
Demo(int i),this(d2):0xbfd40110, i:2, this->iAge:2
Demo(int i),this(d3):0xbfd40108, i:3, this->iAge:3
operator=(const Demo&d_var),this(d2):0xbfd40110, &d_var(d1):0xbfd40118, this->iAge:2, d_var.iAge:1
Demo(const Demo &d),this():0xbfd40100, &d_copy(d2):0xbfd40110
operator=(const Demo&d_var),this(d3):0xbfd40108, &d_var():0xbfd40100, this->iAge:3, d_var.iAge:1463109
Demo(const Demo &d),this():0xbfd400f8, &d_copy(d3):0xbfd40108
~Demo,this():0xbfd400f8
~Demo,this():0xbfd40100
---------------结束--->公众号:梦悦foundation---------------
~Demo,this(d3):0xbfd40108
~Demo,this(d2):0xbfd40110
~Demo,this(d1):0xbfd40118
book@book-desktop:~/meng-yue/c++/class_dynamic_memory/03$
首先d2 = d1的时候,就会调用一次,然后 d3 = (d2 = d1)的时候又会调用一次,白白增加了两次复制构造函数的开销。而且最后这些临时对象还得析构掉,而且这还是没有关闭掉返回优化的时候。
但是发现关闭掉,步骤其实也是一样的
book@book-desktop:~/meng-yue/c++/class_dynamic_memory/03$ g++ -fno-elide-constructors -o equal_overload6 equal_overload6.cpp
book@book-desktop:~/meng-yue/c++/class_dynamic_memory/03$ ./equal_overload6
---------------开始--->公众号:梦悦foundation---------------
Demo(int i),this(d1):0xbf9f82e8, i:1, this->iAge:1
Demo(int i),this(d2):0xbf9f82e0, i:2, this->iAge:2
Demo(int i),this(d3):0xbf9f82d8, i:3, this->iAge:3
operator=(const Demo&d_var),this(d2):0xbf9f82e0, &d_var(d1):0xbf9f82e8, this->iAge:2, d_var.iAge:1
Demo(const Demo &d),this():0xbf9f82d0, &d_copy(d2):0xbf9f82e0
operator=(const Demo&d_var),this(d3):0xbf9f82d8, &d_var():0xbf9f82d0, this->iAge:3, d_var.iAge:1307461
Demo(const Demo &d),this():0xbf9f82c8, &d_copy(d3):0xbf9f82d8
~Demo,this():0xbf9f82c8
~Demo,this():0xbf9f82d0
---------------结束--->公众号:梦悦foundation---------------
~Demo,this(d3):0xbf9f82d8
~Demo,this(d2):0xbf9f82e0
~Demo,this(d1):0xbf9f82e8
book@book-desktop:~/meng-yue/c++/class_dynamic_memory/03$