😁博客主页😁:🚀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++的拷贝赋值运算符,了解为什么需要拷贝赋值运算符,什么是拷贝赋值运算符,怎样声明、定义、使用拷贝赋值运算符,最后介绍默认的拷贝赋值运算符以及禁用赋值功能。
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁