类的函数成员(四):赋值函数

一.赋值函数是什么?

1.1 运算符的重载

        运算符的重载实际是一种特殊的函数重载,必须定义一个函数,并告诉C++编译器,当遇到该重载的运算符时调用此函数。这个函数叫做运算符重载函数,它通常为类的成员函数。

     定义运算符重载函数的一般格式如下:

返回值类型 类名::operator重载的运算符(参数表)
{
	……
}

        operator是关键字,它与重载的运算符一起构成函数名。因函数名的特殊性,C++编译器可以将这类函数识别出来。

1.2 赋值函数

        赋值函数是一个赋值运算符重载函数,其格式如下:

返回值类型 类名::operator=(参数表)
{
	……
}

二.赋值函数如何实现?

2.1 缺省赋值函数

2.1.1 格式

        如果类中没有给出定义,系统会自动提供缺省赋值函数。缺省的赋值操作格式对所有类是固定的。其格式如下:

A& operator=(const A&);

2.1.2 C++代码示例

#include <iostream>

using namespace std;
 

class CStudent{
public:
	CStudent(int age = 0,int score = 0);
	~CStudent(void);
	
	CStudent(const CStudent &stu);
	
	
	void print_info(void);
private:
	int age;
	int score;
};

CStudent::CStudent(int age,int score)
{
	cout<<"Constructor!"<<endl;
	this->age = age;
	this->score = score;
}

CStudent::~CStudent(void)
{
	cout<<"Desconstructor!"<<endl;
}

CStudent::CStudent(const CStudent &stu)
{
	cout<<"Copy constructor!"<<endl;
	age = stu.age;
	score = stu.score;
}

void CStudent::print_info(void)
{
	cout<<"age: "<<age<<endl;
	cout<<"score: "<<score<<endl;
}

int main(int argc, char** argv) 
{
	CStudent stu(10,80);
	CStudent stu1;
	
	stu1 = stu;
	
	stu1.print_info();
	
	return 0;
}

2.1.3 运行结果

        如下图所示。
        由下图可知:用已存在的对象stu赋值给已存在的对象stu1时,stu的数据成员值拷贝给了stu1对应的数据成员。

2.1.4 汇编代码分析

2.1.4.1 构造函数的汇编代码

        构造函数如下:

CStudent::CStudent(int, int)

        它对应的汇编代码如下:

<+0>:	push   %rbp
<+1>:	mov    %rsp,%rbp
<+4>:	sub    $0x20,%rsp
<+8>:	mov    %rcx,0x10(%rbp)//存储对象stu/stu1的地址到栈中
<+12>:	mov    %edx,0x18(%rbp)//存储实参age的值到栈中
<+15>:	mov    %r8d,0x20(%rbp)//存储实参score的值到栈中
<+19>:	lea    0x86ab6(%rip),%rdx        # 0x488000
<+26>:	mov    0x8b17f(%rip),%rcx        # 0x48c6d0 <.refptr._ZSt4cout>
<+33>:	callq  0x46edd0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc>
<+38>:	mov    0x8b183(%rip),%rdx        # 0x48c6e0 <.refptr._ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_>
<+45>:	mov    %rax,%rcx
<+48>:	callq  0x44d4c0 <_ZNSolsEPFRSoS_E>
<+53>:	mov    0x10(%rbp),%rax//从栈中取出对象stu/stu1的地址
<+57>:	mov    0x18(%rbp),%edx//从栈中取出实参age的值
<+60>:	mov    %edx,(%rax)//对应this->age = age;
<+62>:	mov    0x10(%rbp),%rax//从栈中取出对象stu/stu1的地址
<+66>:	mov    0x20(%rbp),%edx//从栈中取出实参score的值
<+69>:	mov    %edx,0x4(%rax)//对应this->score = score;
<+72>:	add    $0x20,%rsp
<+76>:	pop    %rbp
<+77>:	retq   

        分析如下:

(1)下面几行代码将对象地址和实参值压入了堆栈中:

<+8>:	mov    %rcx,0x10(%rbp)//存储对象stu/stu1的地址到栈中
<+12>:	mov    %edx,0x18(%rbp)//存储实参age的值到栈中
<+15>:	mov    %r8d,0x20(%rbp)//存储实参score的值到栈中

(2)“this->age = age”语句由下面几行代码实现:

<+53>:	mov    0x10(%rbp),%rax//从栈中取出对象stu/stu1的地址
<+57>:	mov    0x18(%rbp),%edx//从栈中取出实参age的值
<+60>:	mov    %edx,(%rax)//对应this->age = age;

(3)“this->score = score”语句由下面几行代码实现:

<+62>:	mov    0x10(%rbp),%rax//从栈中取出对象stu/stu1的地址
<+66>:	mov    0x20(%rbp),%edx//从栈中取出实参score的值
<+69>:	mov    %edx,0x4(%rax)//对应this->score = score;

        PS:其实,这里的rax寄存器已经保存了对象的地址,不需要从栈中重新取地址,<+62>的语句可不要。这也表明了编译器的局限性。

2.1.4.2 main函数的汇编代码

        main函数对应的汇编代码如下:

 <+0>:		push   %rbp
 <+1>:		push   %rbx
 <+2>:		sub    $0x48,%rsp
 <+6>:		lea    0x80(%rsp),%rbp
 <+14>:		mov    %ecx,-0x20(%rbp)
 <+17>:		mov    %rdx,-0x18(%rbp)
 <+21>:		callq  0x40e910 <__main>
 <+26>:		lea    -0x50(%rbp),%rax//取栈偏移50的地址传送给rax,实质是对象stu的地址
 <+30>:		mov    $0x50,%r8d //将80传送给r8d
 <+36>:		mov    $0xa,%edx//将10传送给edx
 <+41>:		mov    %rax,%rcx//将对象stu的地址保存到rcx中,传给构造函数,实质就是this指针
 <+44>:		callq  0x401530 <CStudent::CStudent(int, int)>
 <+49>:		lea    -0x60(%rbp),%rax//取栈偏移60的地址传送给rax,实质是对象stu1的地址
 <+53>:		mov    $0x0,%r8d//将0传送给r8d
 <+59>:		mov    $0x0,%edx//将0传送给edx
 <+64>:		mov    %rax,%rcx//将对象stu1的地址保存到rcx中,传给构造函数,实质就是this指针
 <+67>:		callq  0x401530 <CStudent::CStudent(int, int)>
 <+72>:		mov    -0x50(%rbp),%rax//取出stu.age和stu.score的值,传送给rax
 <+76>:		mov    %rax,-0x60(%rbp)//相当于stu1.age = stu.age,stu1.score = stu.score
 <+80>:		lea    -0x60(%rbp),%rax
 <+84>:		mov    %rax,%rcx
 <+87>:		callq  0x401606 <CStudent::print_info()>
 <+92>:		mov    $0x0,%ebx
 <+97>:		lea    -0x60(%rbp),%rax
 <+101>:	mov    %rax,%rcx
 <+104>:	callq  0x40157e <CStudent::~CStudent()>
 <+109>:	lea    -0x50(%rbp),%rax
 <+113>:	mov    %rax,%rcx
 <+116>:	callq  0x40157e <CStudent::~CStudent()>
 <+121>:	mov    %ebx,%eax
 <+123>:	jmp    0x40172d <main(int, char**)+168>
 <+125>:	mov    %rax,%rbx
 <+128>:	lea    -0x60(%rbp),%rax
 <+132>:	mov    %rax,%rcx
 <+135>:	callq  0x40157e <CStudent::~CStudent()>
 <+140>:	jmp    0x401716 <main(int, char**)+145>
 <+142>:	mov    %rax,%rbx
 <+145>:	lea    -0x50(%rbp),%rax
 <+149>:	mov    %rax,%rcx
 <+152>:	callq  0x40157e <CStudent::~CStudent()>
 <+157>:	mov    %rbx,%rax
 <+160>:	mov    %rax,%rcx
 <+163>:	callq  0x40f630 <_Unwind_Resume>
 <+168>:	add    $0x48,%rsp
 <+172>:	pop    %rbx
 <+173>:	pop    %rbp
 <+174>:	retq   

        分析如下:

(1)如下几行代码实现“CStudent stu”语句:

 <+26>:		lea    -0x50(%rbp),%rax//取栈偏移50的地址传送给rax,实质是对象stu的地址
 <+30>:		mov    $0x50,%r8d //将80传送给r8d
 <+36>:		mov    $0xa,%edx//将10传送给edx
 <+41>:		mov    %rax,%rcx//将对象stu的地址保存到rcx中,传给构造函数,实质就是this指针
 <+44>:		callq  0x401530 <CStudent::CStudent(int, int)>

(2)如下几行代码实现“CStudent stu1”语句:

 <+49>:		lea    -0x60(%rbp),%rax//取栈偏移60的地址传送给rax,实质是对象stu1的地址
 <+53>:		mov    $0x0,%r8d//将0传送给r8d
 <+59>:		mov    $0x0,%edx//将0传送给edx
 <+64>:		mov    %rax,%rcx//将对象stu1的地址保存到rcx中,传给构造函数,实质就是this指针
 <+67>:		callq  0x401530 <CStudent::CStudent(int, int)>

(3)如下几行代码实现“stu1 = stu”语句:

 <+72>:		mov    -0x50(%rbp),%rax//取出stu.age和stu.score的值,传送给rax
 <+76>:		mov    %rax,-0x60(%rbp)//相当于stu1.age = stu.age,stu1.score = stu.score
 <+80>:		lea    -0x60(%rbp),%rax

        注意:rax是64位寄存器,即8字节,而age、score各占4字节空间。所以,<+72>行的代码是同时将age和score的值传送给rax。

2.2 自定义赋值函数

        在某些情况下,缺省赋值函数对类与对象的安全性和处理的正确性还不够,这时就要求类的设计者提供自定义的赋值函数。

2.2.1 格式

        赋值函数的一般形式如下:

class CStudent{
public:	
	CStudent& operator=(const CStudent& stu); 
	
private:
	int age;
	int score;
};

CStudent& CStudent::operator=(const CStudent& stu)
{
	...
}

2.2.2 C++代码示例

#include <iostream>

using namespace std;
 

class CStudent{
public:
	CStudent(int age = 0,int score = 0);
	~CStudent(void);
	
	CStudent(const CStudent &stu);
	
	//赋值函数 
	CStudent& operator=(const CStudent& stu);
	
	void print_info(void);
private:
	int age;
	int score;
};

CStudent::CStudent(int age,int score)
{
	cout<<"Constructor!"<<endl;
	this->age = age;
	this->score = score;
}

CStudent::~CStudent(void)
{
	cout<<"Desconstructor!"<<endl;
}

CStudent::CStudent(const CStudent &stu)
{
	cout<<"Copy constructor!"<<endl;
	age = stu.age;
	score = stu.score;
}

CStudent& CStudent::operator=(const CStudent& stu)
{
	cout<<"Assignment!"<<endl;
	if(this != &stu)
	{
		age = stu.age;
		score = stu.score;
	}
	
	return *this;
}

void CStudent::print_info(void)
{
	cout<<"age: "<<age<<endl;
	cout<<"score: "<<score<<endl;
}

int main(int argc, char** argv) 
{
	CStudent stu(10,80);
	CStudent stu1;
	
	stu1 = stu;
	
	stu1.print_info();
	
	return 0;
}

2.2.3 运行结果

        如下图所示。

        执行"stu1 = stu"时,调用了一次赋值函数。

2.2.4 汇编代码分析

2.2.4.1 main函数汇编代码对比

        下图是使用缺省赋值函数和自定义赋值函数时,main函数的汇编代码对比。

        差异部分代码在红框中,即是“stu1 = stu”语句的汇编实现有区别,其余实现相同。

        差异部分代码如下:

<+72>:	lea    -0x50(%rbp),%rdx//对象stu的地址传送给rdx
<+76>:	lea    -0x60(%rbp),%rax//对象stu1的地址传送给rax
<+80>:	mov    %rax,%rcx
<+83>:	callq  0x401606 <CStudent::operator=(CStudent const&)>

        下面重点分析赋值函数。

2.2.4.2 自定义赋值函数

        赋值函数“CStudent::operator=(CStudent const&)”的汇编代码如下:

<+0>:	push   %rbp
<+1>:	mov    %rsp,%rbp
<+4>:	sub    $0x20,%rsp
<+8>:	mov    %rcx,0x10(%rbp) //将对象stu1的地址压入栈中
<+12>:	mov    %rdx,0x18(%rbp)//将对象stu的地址压入栈中
<+16>:	lea    0x86a12(%rip),%rdx        # 0x48802f
<+23>:	mov    0x8b0ac(%rip),%rcx        # 0x48c6d0 <.refptr._ZSt4cout>
<+30>:	callq  0x46ee40 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc>
<+35>:	mov    0x8b0b0(%rip),%rdx        # 0x48c6e0 <.refptr._ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_>
<+42>:	mov    %rax,%rcx
<+45>:	callq  0x44d530 <_ZNSolsEPFRSoS_E>
<+50>:	mov    0x10(%rbp),%rax//从栈中取出对象stu1的地址,传送给rax
<+54>:	cmp    0x18(%rbp),%rax//if(this != &stu)的实现
<+58>:	je     0x40165c <CStudent::operator=(CStudent const&)+86>
<+60>:	mov    0x18(%rbp),%rax//从栈中取出对象stu的地址,传送给rax
<+64>:	mov    (%rax),%edx//stu.age的值传送给edx
<+66>:	mov    0x10(%rbp),%rax//从栈中取出对象stu1的地址,传送给rax
<+70>:	mov    %edx,(%rax)//stu1.age = stu.age(edx是4字节)
<+72>:	mov    0x18(%rbp),%rax//从栈中取出对象stu的地址,传送给rax
<+76>:	mov    0x4(%rax),%edx//stu.score的值传送给edx
<+79>:	mov    0x10(%rbp),%rax//从栈中取出对象stu1的地址,传送给rax
<+83>:	mov    %edx,0x4(%rax)//stu1.score = stu.score(edx是4字节)
<+86>:	mov    0x10(%rbp),%rax//从栈中取出对象stu1的地址,传送给rax,作为函数返回值
<+90>:	add    $0x20,%rsp
<+94>:	pop    %rbp
<+95>:	retq   

        分析如下:

(1)赋值函数传入了stu和stu1两个对象的地址:

<+8>:	mov    %rcx,0x10(%rbp) //将对象stu1的地址压入栈中
<+12>:	mov    %rdx,0x18(%rbp)//将对象stu的地址压入栈中

        由此可知:

stu1 = stu;

        等价于:

stu1.operator=(stu);

        进一步等价于:

operator=(&stu1,stu);

        所以,“operator=”实质就是一个函数名。

(2)返回值由rax传送,返回对象引用的汇编代码如下:

<+86>:	mov    0x10(%rbp),%rax//从栈中取出对象stu1的地址,传送给rax,作为函数返回值

三.为什么赋值函数要返回对象的引用?

        自定义赋值函数也可以返回void类型,如下:

class CStudent{
public:	
	void operator=(const CStudent& stu); 
	
private:
	int age;
	int score;
};

void CStudent::operator=(const CStudent& stu)
{
	...
}

        以引用返回是为了实现连续赋值,如:

a = b = c;

        它等价于:

a.operator=(b.operator=(c));

        如果赋值函数返回void类型,则变成:

a.operator=(void);

        编译器会报错。

四.赋值函数何时调用?

        赋值函数只能被已经存在的对象调用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/529408.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

TCP 重传、滑动窗口、流量控制、拥塞控制(计算机网络)

重传机制 TCP 针对数据包丢失的情况&#xff0c;会用重传机制解决。 接下来说说常见的重传机制&#xff1a; 超时重传快速重传SACKD-SACK 超时重传 重传机制的其中一个方式&#xff0c;就是在发送数据时&#xff0c;设定一个定时器&#xff0c;当超过指定的时间后&#xff0c…

HarmonyOS实战开发-如何使用 geolocation 实现获取当前位置经纬度

介绍 本示例使用 geolocation 实现获取当前位置的经纬度,然后通过 http 将经纬度作为请求参数,获取到该经纬度所在的城市。通过 AlphabetIndexer 容器组件实现按逻辑结构快速定位容器显示区域。 效果预览 使用说明 1.进入主页,点击国内热门城市,配送地址会更新为选择的城…

javaScript 事件循环 Event Loop

一、前言 javaScript 是单线程的编程语言&#xff0c;只能同一时间内做一件事情&#xff0c;按照顺序来处理事件&#xff0c;但是在遇到异步事件的时候&#xff0c;js线程并没有阻塞&#xff0c;还会继续执行&#xff0c;这又是为什么呢&#xff1f; 二、基础知识 堆&#x…

vivado HDL 例化调试探测流程概述

HDL 例化调试探测流程概述 HDL 例化探测流程涉及在 HDL 设计源代码中直接手动自定义、例化和连接各种调试核组件。下表中显示了 Vivado 工具中此流程所支持的新调试核。 新的 ILA 核相比于传统 ILA v1.x 核具有以下 2 大优势 &#xff1a; • 可搭配集成的 Vivado Log…

FreeRTOS临界段代码保护和任务调度器的挂起与恢复学习

FreeRTOS临界段代码保护和任务调度器的挂起与恢复学习 临界段代码保护 所谓临界段代码保护就是指必须完成运行&#xff0c;不能被打断的代码段。比如需要严格按照时序除初始化的外设&#xff1a;IIC、SPI&#xff0c;再或者因为系统自身需求和用户需求。 FreeRTOS 在进入临界…

发型不满意试试开源AI换发型HairFastGAN;前OpenAI员工Karpathy1000纯C语言写完GPT-2

✨ 1: HairFastGAN 开源AI换发型 HairFastGAN 是一种先进的技术&#xff0c;专门设计用于在不同图片之间传输发型。这意味着如果你有一张人物的图片和另一张你喜欢的发型的图片&#xff0c;HairFastGAN 能够将你喜欢的那个发型复制到人物的头上&#xff0c;而且看起来非常自然…

强化学习MPC——(二)

本篇主要介绍马尔科夫决策&#xff08;MDP&#xff09;过程&#xff0c;在介绍MDP之前&#xff0c;还需要对MP&#xff0c;MRP过程进行分析。 什么是马尔科夫&#xff0c;说白了就是带遗忘性质&#xff0c;下一个状态S_t1仅与当前状态有关&#xff0c;而与之前的状态无关。 为…

深入浅出索引(上)

提到数据库索引&#xff0c;我想你并不陌生&#xff0c;在日常工作中会经常接触到。比如某一个 SQL 查询比较慢&#xff0c;分析完原因之后&#xff0c;你可能就会说“给某个字段加个索引吧”之类的解决方案。但到底什么是索引&#xff0c;索引又是如何工作的呢&#xff1f;今天…

NPW(监控片的)上---基础知识要点精讲

半导体的生产过程已经历经数十年的发展&#xff0c;其中主要有两个大的发展趋势&#xff0c;第一&#xff0c;晶圆尺寸越做越大&#xff0c;到目前已有超过70%的产能是12寸晶圆&#xff0c;不过18寸晶圆产业链推进缓慢&#xff1b;第二&#xff0c;电子器件的关键尺寸越做越小&…

【面试题】如何在级别用户中检查用户名是否存在?

前言 不知道大家有没有留意过&#xff0c;在使用一些app或者网站注册的时候&#xff0c;提示你用户名已经被占用了&#xff0c;比如我们熟知的《英雄联盟》有些人不知道取啥名字&#xff0c;干脆就叫“不知道取啥名”。 但是有这样困惑的可不止他一个&#xff0c;于是就出现了…

转圈游戏——快速幂

目录 题目 思路 代码 题目 思路 每个小朋友移动一次的位置为&#xff0c;移动 q 次的位置则为。那么题目要求移动 &#xff0c;最后的位置为 。 但 的范围是&#xff0c;而总的移动次数是 。时间复杂度是在&#xff0c;因此是一定不能硬算的&#xff0c;肯定会超时。那么该…

苍穹外卖——员工管理,分类管理

内容 新增员工(重点)员工分页查询(重点)启用禁用员工账号编辑员工(重点)导入分类模块功能代码**功能实现&#xff1a;**员工管理、菜品分类管理。 员工管理效果&#xff1a;菜品分类管理效果&#xff1a; 1.新增员工 1.1 需求分析设计 1.1.1产品原型 一般在做需求分析时&a…

【VUE】Vue3+Element Plus动态间距处理

目录 1. 动态间距调整1.1 效果演示1.2 代码演示 2. 固定间距2.1 效果演示2.2 代码演示 其他情况 1. 动态间距调整 1.1 效果演示 并行效果 并列效果 1.2 代码演示 <template><div style"margin-bottom: 15px">direction:<el-radio v-model"d…

知识图谱的实际应用案例分析

知识图谱的实际应用案例分析 一、引言 知识图谱的重要性 在如今这个数据驱动的时代&#xff0c;数据已经成为了新的石油&#xff0c;而知识图谱则是我们提炼这种石油的精炼厂。知识图谱&#xff0c;一种将现实世界中的实体以及这些实体之间的复杂关系进行结构化表示的技术&am…

HarmonyOS开发实例:【状态管理】

状态管理 ArkUI开发框架提供了多维度的状态管理机制&#xff0c;和UI相关联的数据&#xff0c;不仅可以在组件内使用&#xff0c;还可以在不同组件层级间传递&#xff0c;比如父子组件之间&#xff0c;爷孙组件之间等&#xff0c;也可以是全局范围内的传递&#xff0c;还可以是…

C++数据结构与算法——贪心算法中等题

C第二阶段——数据结构和算法&#xff0c;之前学过一点点数据结构&#xff0c;当时是基于Python来学习的&#xff0c;现在基于C查漏补缺&#xff0c;尤其是树的部分。这一部分计划一个月&#xff0c;主要利用代码随想录来学习&#xff0c;刷题使用力扣网站&#xff0c;不定时更…

分享Fork/Join经典案例

shigen坚持更新文章的博客写手&#xff0c;擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长&#xff0c;分享认知&#xff0c;留住感动。 个人IP&#xff1a;shigen 在上一篇的文章java 多线程分治求和&#xff0c;太牛了的文章中&#xff0c;提到…

Docker使用— Docker部署安装Nginx

Nginx简介 Nginx 是一款高性能的 web 服务器、反向代理服务器以及电子邮件&#xff08;IMAP/POP3/SMTP&#xff09;代理服务器&#xff0c;由俄罗斯开发者伊戈尔塞索耶夫&#xff08;Igor Sysoev&#xff09;编写&#xff0c;并在2004年10月4日发布了首个公开版本0.1.0。Nginx…

因为使用ArrayList.removeAll(List list)导致的机器重启

背景 先说一下背景&#xff0c;博主所在的业务组有一个核心系统&#xff0c;需要同步两个不同数据源给过来的数据到redis中&#xff0c;但是每次同步之前需要过滤掉一部分数据&#xff0c;只存储剩下的数据。每次同步的数据与需要过滤掉的数据量级大概在0-100w的数据不等。 由…

鸿蒙HarmonyOS开发实例:【简单时钟】

简单时钟 介绍 本示例通过使用[ohos.display]接口以及Canvas组件来实现一个简单的时钟应用。 效果预览 主页 使用说明 1.界面通过setInterval实现周期性实时刷新时间&#xff0c;使用Canvas绘制时钟&#xff0c;指针旋转角度通过计算得出。 例如&#xff1a;"2 * M…