【C++ | 拷贝赋值运算符函数】一文了解C++的 拷贝赋值运算符函数

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
⏰发布时间⏰:2024-06-09 16:06:48

本文未经允许,不得转发!!!



在这里插入图片描述

🎄一、为什么需要 赋值运算符函数

如果使用一个同类型对象给当前对象赋值,会调用这个类的赋值运算符函数,而默认的赋值运算符函数只进行浅拷贝,可能无法满足一些类的需求,所以需要自定义赋值运算符函数。

下面例子,演示默认的赋值运算符函数存在的问题:

// g++ 12_Operator=_Date.cpp
#include <iostream>
#include <stdio.h>

using namespace std;

class CDate
{
public:
	CDate(){}							// 无参构造
	CDate(int year, int mon, int day);	// 构造函数声明
	CDate(const CDate& date);			// 拷贝构造函数声明
	~CDate();							// 析构函数声明

	void show()
	{
		//cout << "Date: " << m_year << "." << m_mon << "." << m_day << endl;
		cout << "Date: " << str << endl;
	}

private:
	int m_year;
	int m_mon;
	int m_day;
	char *str;
};

// 构造函数定义
CDate::CDate(int year, int mon, int day)
{
	m_year = year;
	m_mon = mon;
	m_day = day;
	str = new char[64];
	sprintf(str, "%4d.%02d.%02d", year,mon,day);
	cout << "Calling Constructor" << ", this=" << this <<endl;
}

// 拷贝构造函数定义
CDate::CDate(const CDate& date)
{
	m_year = date.m_year;
	m_mon = date.m_mon;
	m_day = date.m_day;
	str = new char[64];
	sprintf(str, "%4d.%02d.%02d", m_year,m_mon,m_day);
	cout << "Calling Copy Constructor" << ", this=" << this <<endl;
}

// 析构函数定义
CDate::~CDate()
{
	cout << "Calling Destructor" << ", this=" << this <<endl;
	delete [] str;
}

int main()
{
	CDate date_1(2024,06,05);
	CDate date_2;
	
	date_2=date_1;	// 调用默认赋值运算符 2024-06-09 13:39:05
	
	return 0;
}

运行结果,因为默认赋值运算符只进行浅拷贝,直接复制了date_1的str指针的值,但是两个对象销毁时,却delete了两次str,导致double free
在这里插入图片描述
清楚问题之后,我们学习一下怎样声明、定义自己的拷贝赋值运算符函数来规避这个问题。


在这里插入图片描述

🎄二、什么是 赋值运算符函数

赋值运算符函数 是重载运算符的一种,关于重载运算符,后面会用其他文章来解释,总之赋值运算符函数的本质也是类成员函数。关于 赋值运算符函数,我们需要了解它是什么时候调用的,其函数原型是怎样的,怎样声明、定义自己的赋值运算符函数。

ANSI C 允许结构赋值, 而 C++允许类对象赋值, 这是通过自动为类重载赋值运算符实现的。这种运算符的原型如下:

类类型 & 类名:operator=(const 类类型 &);
CDate & CDate:operator=(const CDate &); // CDate 类的赋值运算符

怎样声明、定义自己的赋值运算符函数,有下面几个注意点:
1、赋值运算符函数的名称是operator=,其中operator是C++的关键字,专门用于重载运算符。
2、赋值运算符函数只允许一个参数,且是该类对象的引用,const表示不会修改该对象的内容。
3、赋值运算符函数返回值类型是该类对象的引用,一般不使用const修饰,这样可以支持连续赋值date1=date2=date3
4、赋值运算符函数在实现时应当避免将对象赋给自身,可以判断对象地址来实现this==&date
5、如果存在new分配的内容,需要先释放旧的内存

下面以CDate为例,演示声明、定义自己的 赋值运算符函数:

// 在类中声明,下面隐藏了类的其他代码
class CDate
{
public:
	...
	CDate& operator=(const CDate& date);// 赋值运算符函数声明
	...
};

// 赋值运算符函数定义
CDate& CDate::operator=(const CDate& date)
{
	if(this == &date)	// 赋值给自身
		return *this;
	delete [] str;		// 释放旧的数据
	m_year = date.m_year;
	m_mon = date.m_mon;
	m_day = date.m_day;
	str = new char[64];
	sprintf(str, "%4d.%02d.%02d", m_year,m_mon,m_day);
	cout << "Calling operator=" << ", this=" << this <<endl;
	return *this;
}

在这里插入图片描述

🎄三、使用 赋值运算符函数

知道了怎样声明、定义自己的赋值运算符函数后。这一小节,了解何时使用 赋值运算符函数。

将已有的对象赋值给另一个对象时,就会调用 赋值运算符函数。而使用赋值号(=)给对象初始化时则可能调用拷贝构造函数。

这里有两种情况,一种是赋值(对象之前就定义好了),一种是初始化(正在定义某个对象,对象前带有类型)。
下面是a赋值给b:

int a=0;
int b;
b = a; 	// a赋值给b

下面是使用a的值给b初始化:

int a=0;
int b=a;	// 用a的值给b初始化

下面代码演示了怎么声明、定义、使用赋值运算符函数:

// g++ 12_Operator=_Date.cpp
#include <iostream>
#include <stdio.h>

using namespace std;

class CDate
{
public:
	CDate()								// 无参构造
	{
		m_year = m_mon = m_day = 0;
		str = NULL;
	}							
	CDate(int year, int mon, int day);	// 构造函数声明
	CDate(const CDate& date);			// 拷贝构造函数声明
	~CDate();							// 析构函数声明
	
	CDate& operator=(const CDate& date);// 赋值运算符函数声明

	void show()
	{
		//cout << "Date: " << m_year << "." << m_mon << "." << m_day << endl;
		cout << "Date: " << str << endl;
	}

private:
	int m_year;
	int m_mon;
	int m_day;
	char *str;
};

// 构造函数定义
CDate::CDate(int year, int mon, int day)
{
	m_year = year;
	m_mon = mon;
	m_day = day;
	str = new char[64];
	sprintf(str, "%4d.%02d.%02d", year,mon,day);
	cout << "Calling Constructor" << ", this=" << this <<endl;
}

// 拷贝构造函数定义
CDate::CDate(const CDate& date)
{
	m_year = date.m_year;
	m_mon = date.m_mon;
	m_day = date.m_day;
	str = new char[64];
	sprintf(str, "%4d.%02d.%02d", m_year,m_mon,m_day);
	cout << "Calling Copy Constructor" << ", this=" << this <<endl;
}

// 析构函数定义
CDate::~CDate()
{
	cout << "Calling Destructor" << ", this=" << this <<endl;
	delete [] str;
}

// 赋值运算符函数定义
CDate& CDate::operator=(const CDate& date)
{
	if(this == &date)	// 赋值给自身
		return *this;
	delete [] str;		// 释放旧的数据
	m_year = date.m_year;
	m_mon = date.m_mon;
	m_day = date.m_day;
	str = new char[64];
	sprintf(str, "%4d.%02d.%02d", m_year,m_mon,m_day);
	cout << "Calling operator=" << ", this=" << this <<endl;
	return *this;
}

int main()
{
	CDate date_1(2024,06,05);
	CDate date_2, date_3;
	
	date_3 = date_2=date_1;	// 调用赋值运算符函数 2024-06-09 15:00:36
	
	return 0;
}

运行结果如下:
在这里插入图片描述


在这里插入图片描述

🎄四、默认的 赋值运算符函数

与处理拷贝构造函数一样,如果一个类末定义自己的拷贝赋值运算符函数,编译器会为它生成一个合成拷贝赋值运算符(synthesized copy-assignment operator)。

合成的拷贝构造函数会逐个复制非静态成员( 成员复制也称为浅复制)的值到目标对象中。根据成员类型有下面几种情况:
1、如果成员是内置类型,则直接复制;
2、如果成员本身就是类对象,则将使用这个类的拷贝构造函数来复制类对象;
3、如果成员是数组,默认的拷贝构造函数会逐元素地拷贝一个数组类型的成员。

禁用赋值
在C++11的标准中,可以在声明赋值运算符时,在函数参数的右括号后面加=delete,来禁用该类对象的赋值操作,以CDate为例,加了=delete的赋值运算符函数声明如下:

CDate& operator=(const CDate& date) =delete;// 赋值运算符函数声明

有了这个声明后,就不能给CDate对象赋值了。


在这里插入图片描述

🎄五、总结

👉本文主要介绍了C++的拷贝赋值运算符,了解为什么需要拷贝赋值运算符,什么是拷贝赋值运算符,怎样声明、定义、使用拷贝赋值运算符,最后介绍默认的拷贝赋值运算符以及禁用赋值功能。

在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

最近更新

  1. Spring事务管理与Spring AOP详解

    2024-06-10 10:10:02       0 阅读
  2. MTU简介

    2024-06-10 10:10:02       0 阅读
  3. C语言笔记25 •顺序表介绍•

    2024-06-10 10:10:02       0 阅读
  4. 什么是内存泄漏?如何避免?

    2024-06-10 10:10:02       0 阅读
  5. spring-websocket实现(一)

    2024-06-10 10:10:02       0 阅读
  6. apache poi excel export

    2024-06-10 10:10:02       0 阅读
  7. 代码随想录算法训练营刷题复习4 :单调栈

    2024-06-10 10:10:02       0 阅读

热门阅读

  1. 怎么开发vscode插件

    2024-06-10 10:10:02       6 阅读
  2. WebSocket面试常见知识点和面试题

    2024-06-10 10:10:02       4 阅读
  3. 2024前端面试准备4-Vue相关

    2024-06-10 10:10:02       4 阅读
  4. #10 解决Stable Diffusion常见问题和错误

    2024-06-10 10:10:02       4 阅读
  5. 006 CentOS 7.9 elasticsearch7.10.0安装及配置

    2024-06-10 10:10:02       8 阅读