P19-c++重载赋值运算符深度剖析介绍,详细的例子演示!

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++复制构造函数深度剖析介绍,详细的例子演示!

  1. 第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$

猜你喜欢

转载自blog.csdn.net/sgy1993/article/details/113796051
今日推荐