C++中的运算符重载以及类中的赋值运算符重载函数

一丶运算符重载引入

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型和参数列表与普通的函数类似。

1.编写形式

函数函数名字为:

  • 关键字operator后面接需要重载的运算符符号

函数原型:

  • 返回值类型 operator 操作符(参数列表)

进行运算符重载时需要注意的是:

  • 1.参数列表中参数的个数和操作符有关,操作符原本操作几个操作数,那么参数列表就得有几个参数。
  • 2.函数返回值具体是什么取决于重载操作符的功能实现。

2.调用方式

函数调用方式分为两种:显示调用和隐式调用
显式调用: 调用形如函数的格式
隐式调用:直接使用运算符,再加上所需要的操作数

一般情况下我们直接使用隐式调用,因为这样更加的简洁高效.

下面的注意事项中有代码进行演示显示调用和隐式调用.

二丶运算符重载注意事项

注意:

  • 不能通过连接其他符号来创建新的操作符,比如operator@,或是operator+=这种类似的
  • 重载操作符必须有一个类类型参数, 即重载操作符必须包含自定义类型,因为如果只进行内置数据类型的操作,不需要重载操作符
  • 用于内置类型的运算符,其含义不能改变.比如,+不能被重载为operator+实现的功能不是+而是-的操作
  • 作为类成员函数重载时,其形参看起来比操作数数目要少一个,因为成员函数的第一个参数为隐藏的this
  • .* 反引用符 :: 域限定符 sizeof ?: 三目操作符 .解引用符 这5个运算符是不允许重载的

注意事项一:

不能通过连接其他符号来创建新的操作符

//比方说连接+和-
int operator+-(const Date d, int day);
//编译前报错

这种写法不关乎特征标(参数列表)如何,也不关乎返回值如何,只是因为不允许连接C++中原本为实现过的操作符,这种operator±的函数名写法,编译器读到之后就会不允许了,这不合乎语法.而C++中原本存在的操作符,比方说+=, -=,这种操作符是允许重载的,只要重载形式正确,编译器会允许通过(在下面的Date类中会有+=和-=的实现实例).

注意事项二:

重载操作符必须有一个类类型参数, 即重载操作符必须包含自定义类型

一般进行操作符重载,都是作为类内的成员函数,在类内一旦作为成员函数,那么第一个参数默认就是this,即指向类对象本身,即使它不显式地显示出来.

作为内类成员函数进行重载操作符

#include <iostream>

using namespace std;

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	bool operator==(const Date& d)
	{
		return this->_year == d._year
			&& this->_month == d._month
			&& this->_day == d._day;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d3(2024, 4, 14);
	Date d4(2024, 4, 15);

	//作为类成员函数的重载
	//显式
	d3.operator==(d4);
	//在使用时要注意位置次序 有的可以变 有的不能
	//隐式
	d3 == d4;

	//全局式的重载和类类成员函数的重载可以同时存在
	//但在调用时只会调用类内的  而不会发生歧义

	return 0;
}

如果在类外实现重载操作符,即不作为类成员进行重载.那么它就成为一个全局式的重载操作符.此时要求它的参数中必须要有类类型(自定义类型).

全局式重载操作符示例

#include <iostream>

using namespace std;

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
//设置为公有成员进行演示
	int _year;
	int _month;
	int _day;
};
//全局式的重载操作符  重载赋值操作符==
bool operator==(const Date& d1, const Date& d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}

int main()
{
	Date d3(2024, 4, 14);
	Date d4(2024, 4, 15);
	//全局重载
	
	//显式调用
	operator==(d3, d4);
	if (operator==(d3, d4))
	{
		cout << "显式:d3 == d4" << endl;
	}
	else
	{
		cout << "显式:d3 != d4" << endl;
	}

	//隐式调用
	d3 == d4;
	if (d3 == d4)
	{
		cout << "隐式:d3 == d4" << endl;
	}
	else
	{
		cout << "隐式:d3 != d4" << endl;
	}
	return 0;
}

但是这种全局式的重载操作符方式是有缺陷的,它不能直接地访问类内的私有成员.
解决方案:

  • 1.提供这些成员的get和set
  • 2.友元
  • 3.重载为成员函数(一般使用这种方式)

此外,如果全局式的运算符重载函数和作为类成员的运算符重载函数同时存在,编译器在调用的时候会优先调用作为类成员函数的运算符重载函数,编译器允许两者同时存在,且调用时通过编译。


这里我们说的自定义类型统一指的是类类型

从上面两点我们得出,只要进行重载操作符, 必须要包含类类型作为参数类型.这是有一定的道理在里面的.
因为如果只是进行内置类型的操作符运算,那么根本不需要重载操作符. 直接使用它原本的功能就满足了需求.
实现重载操作符是为了满足自定义类型进行运算的需求. 比方说自定义类型和内置类型之间的运算,或者自定义类型和自定义类型之间的运算. 这些运算, 使用操作符原本的功能根本满足不了需求, 所以, 从这一层面上讲, 重载运算符的目的是为了满足自定义类型进行运算的需要.

注意事项三:

用于内置类型的运算符,其含义不能改变。

从实践地角度去实验考证,发现其实可以通过运算符重载改变内置运算符的含义,让‘+’重载后实现‘-’的功能,编译也允许通过。但是我们一般不期望这样写,要符合语法的规范,即不改变内置类型操作符原本的含义,防止调用时出现歧义。

注意事项四:

作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this。

这个不难理解。
首先我们知道类中的只要作为成员函数,其第一个默认的隐式参数即为this,即指向类对象本身;其次,我们又规定了,只要涉及运算符重载,必然在函数的特征标(参数列表)中有一个参数是类类型。
从这两个角度讲,作为类成员函数重载时,其形参看起来比操作数数目少1是十分合理的。

注意事项五:

1.(.*) 反引用符
2.(::) 域限定符
3.(sizeof
4.(?:)三目操作符
5.(.)解引用符
以上这5个运算符不允许重载。

这是语法所规定的,记忆就好。不过上面有一个很不常用的操作符,‘‘ .* ’’,我们简单演示一下它的用法:

#include <iostream>

using namespace std;

class OB
{
public:
	OB(int OB_a, int OB_b)
	{
		this->OB_a = OB_a;
		this->OB_b = OB_b;
	}

	void func()
	{
		cout << "void func()" << endl;
		cout << "OB_a = " << OB_a << endl;
		cout << "OB_b = " << OB_b << endl;
	}

private:
	int OB_a = 1;
	int OB_b = 2;
};

//PtrFunc == void(OB::*)() 
typedef void(OB::* PtrFunc)();	//成员函数指针类型

int main()
{
	//函数指针 void(*ptr)()
	//成员函数要加&才能取到其本身的函数指针
	PtrFunc fp = &OB::func;	//定义成员函数指针fp指向成员函数func

	OB temp(3, 4);
	//.* 
	//这里并不表示temp调用(*fp)
	//若写成temp.(*fp)();  
	//编译不会通过  因为.*是一个整体的组合运算符
	//这里的用法就是这样的  通过对象来调用一个成员函数指针
	(temp.*fp)();

	return 0;
}

具体说明在代码注释中。

三丶类中的赋值运算符重载函数

1. 赋值运算符的重载格式

格式要求:

  • 参数类型:const 类类型& ,即要求传递对象的引用,传递引用可提高传参效率
  • 返回值类型:类类型& ,即要求返回引用,一方面返回引用可以提高效率,另一方面是可以支持连续赋值
  • 函数体中要检测是否为自己给自己赋值
  • return *this要符合连续赋值的要求

拿日期类Date来举例:

class Date
{
public :
Date(int year = 1900, int month = 1, int day = 1)
{
	_year = year;
	_month = month;
	_day = day;
}
Date (const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
//自行实现的重载赋值运算符
Date& operator=(const Date& d)
{
	if(this != &d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	
	return *this;
}

private:
	int _year ;
	int _month ;
	int _day ;
};

2.赋值运算符的规定

I. 赋值运算符只能重载成类的成员函数不能重载成全局函数

代码示例:

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	int _year;
	int _month;
	int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{
	if (&left != &right)
	{
		left._year = right._year;
		left._month = right._month;
		left._day = right._day;
	}
	return left;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员

此时编译前报错:error C2801: “operator =”必须是非静态成员。

II.用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。

赋值运算符重载函数也属于特殊的类内成员函数,它和拷贝构造有着相同的规则限制。

赋值运算符重载函数对于内置类型成员变量是直接赋值的,而对于自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。


class Time
{
public:
	Time()
	{
		_hour = 1;
		_minute = 1;
		_second = 1;
	}

	Time& operator=(const Time& t)
	{
		if (this != &t)
		{
			_hour = t._hour;
			_minute = t._minute;
			_second = t._second;
		}
		return *this;
	}

private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};

int main()
{
	Date d1;
	Date d2;
	d1 = d2;
	return 0;
}

同样,使用编译器默认生成的赋值运算符重载,同样会存在浅拷贝的问题。

也就是说,如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要自行实现,利用深拷贝解决浅拷贝带来的问题。

四丶实现Date类(日期类)

实现的该日期类Date包含一些运算符的重载,以作本节参考。

Date.h

#pragma once

#include <iostream>

class Date
{
public:
	//获取某年中某月的天数
	int GetMonthDay(int year, int month);

	//Ordinary and leap years 平年和闰年  获取某一年的天数
	int GetYearDay(int year);
	
	//获得从xxxx-xx-xx日至xxxx-01-01相差天数
	int ToThisYearDayFunc(Date Big);

	//获得从xxxx-12-31日至xxxx-xx-xx相差天数
	int FromThatYearDayFunc(Date Small);

	Date(int year = 1900, int month = 1, int day = 1);

	Date(const Date& d);

	//赋值运算符重载
	Date& operator=(const Date& d);

	~Date();

	//+=重载  ‘日期+=天数’的格式
	Date& operator+=(int day);

	//‘日期+天数’
	Date operator+(int day);

	//-=重载‘日期-=天数’
	Date& operator-=(int day);

	//‘日期-天数'
	Date operator-(int day);

	//++日期  前置++重载
	Date& operator++();

	//日期++ 后置++重载
	Date operator++(int);

	//--日期  前置--重载
	Date& operator--();

	//日期--  后置--重载
	Date operator--(int);

	//>重载
	bool operator>(const Date& d);

	//==重载
	bool operator==(const Date& d);

	//<重载
	bool operator<(const Date& d);

	//!=重载
	bool operator!=(const Date& d);

	//>=重载
	bool operator>=(const Date& d);

	//<=重载
	bool operator<=(const Date& d);

	//日期-日期 
	int operator-(const Date& d);

	//打印日期
	void DatePrint();

private:
	int _year;
	int _month;
	int _day;
};

Date.cpp

#include "Date.h"

using namespace std;

int Date::GetMonthDay(int year, int month)
{
	static int days[13] = { 0, 31, 28, 31, 30, 31 ,30,31,31,30,31,30,31 };
	int day = days[month];
	if (month == 2
		&& ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
	{
		day += 1;
	}
	return day;
}
//Ordinary and leap years 平年和闰年
int Date::GetYearDay(int year)
{
	int OrdYear = 365;
	int LeapYear = 366;
	//闰年
	if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
	{
		return LeapYear;
	}
	return OrdYear;
}

Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
}

Date::Date(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

//赋值运算符重载
Date& Date::operator=(const Date& d)
{
	if (*this != d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	return *this;
}

Date::~Date()
{

}

//+=重载  ‘日期+=天数’的格式
Date& Date::operator+=(int day)
{
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		++_month;
		if (_month == 13)
		{
			++_year;
			_month = 1;	
		}
	}

	return *this;
}

//‘日期+天数’
Date Date::operator+(int day)
{
	Date temp(*this);
	temp._day += day;
	while (temp._day > GetMonthDay(_year, _month))
	{
		temp._day -= GetMonthDay(temp._year, temp._month);
		++temp._month;
		if (temp._month == 13)
		{
			++temp._year;
			temp._month = 1;
		}
	}
	return temp;
}

//-=重载‘日期-=天数’
Date& Date::operator-=(int day)
{
	if (_day<=day)  
	{	
		while (_day <= day)
		{
			if (_month - 1 > 0)
			{
				_month -= 1;
			}
			else //_month == 1
			{
				_year -= 1;
				_month = 12;
			}
			_day += GetMonthDay(_year, _month);
		}
	}
	_day -= day;
	return *this;
}

//‘日期-天数'
Date Date::operator-(int day)
{
	Date temp(*this);
	if (temp._day <= day)
	{
		while (temp._day <= day)
		{
			if (temp._month - 1 > 0)
			{
				temp._month -= 1;
			}
			else //_month == 1
			{
				temp._year -= 1;
				temp._month = 12;
			}
			temp._day += GetMonthDay(temp._year, temp._month);
		}
	}
	temp._day -= day;
	return temp;
}

//++日期  前置++重载
Date& Date::operator++()
{	//考虑极端情况 12月31类 某月的最后一天
	if (_day == GetMonthDay(_year, _month))
	{
		//考虑月
		if (_month == 12)
		{
			_year++;
			_month = 1;
		}
		else
		{
			_month++;
		}
		_day = 0;
	}
	_day += 1;

	return *this;
}

//日期++ 后置++重载
Date Date::operator++(int)
{
	Date temp(*this);
	++(*this);
	return temp;
}

//--日期  前置--重载
Date& Date::operator--()
{	//考虑极端情况 1月1日  某月的第一天
	if (_day == 1)
	{
		if (_month == 1)
		{
			_year--;
			_month = 12;
		}
		else
		{
			_month--;
		}
		//_month > 1
		//此时_day的原值为1
		_day += GetMonthDay(_year, _month);
	}
	_day -= 1;

	return *this;
}

//日期--  后置--重载
Date Date::operator--(int)
{
	Date temp(*this);
	--(*this);
	return temp;
}

//>重载
bool Date::operator>(const Date& d)
{
	if (_year > d._year)
	{
		return true;
	}
	else if (_year == d._year)
	{
		if (_month > d._month)
			return true;
		else if (_month == d._month)
		{
			if (_day > d._day)
				return true;
		}
	}
	return false;
}

//==重载
bool Date::operator==(const Date& d)
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

//<重载
bool Date::operator<(const Date& d) 
{
	return !((*this) > d) && ((*this)!= d);
}

//!=重载
bool Date::operator!=(const Date& d)
{
	return !((*this) == d);
}

//>=重载
bool Date::operator>=(const Date& d)
{
	return (*this) > d || (*this) == d;
}

//<=重载
bool Date::operator<=(const Date& d)
{
	return (*this) < d || (*this) == d;
}

int Date::ToThisYearDayFunc(Date Big)
{
	//计算2006-1-1至2006-1-14
	int ToThisYearDay = 0;
	int ThisM = Big._month - 1;
	while (ThisM > 0)
	{
		ToThisYearDay += GetMonthDay(Big._year, ThisM);
		ThisM--;
	}
	ToThisYearDay += Big._day;
	ToThisYearDay -= 1;//去掉1月1号 避免重复包含
	return ToThisYearDay;
}

int Date::FromThatYearDayFunc(Date Small)
{
	//计算1999-12-15至1999-12-31
	int FromThatYearDay = 0;
	int ThatM = Small._month;
	while (ThatM < 12)
	{
		ThatM++;
		FromThatYearDay += GetMonthDay(Small._year, ThatM);
	}
	FromThatYearDay += (GetMonthDay(Small._year, Small._month) - Small._day + 1);
	return FromThatYearDay;
}


//日期-日期		以2006-1-14 - 1999-12-15为例子
int Date::operator-(const Date& d)
{
	Date Big;
	Date Small;
	if ((*this) > d)
	{
		Big = (*this);
		Small = d;
	}
	else
	{
		Big = d;
		Small = (*this);
	}

	//结果天数
	int totalday = 0;
	
	int ToThisYearDay = ToThisYearDayFunc(Big);
	
	if (Big._year > Small._year)
	{
		//计算由2000-1-1至2006-1-1 包含一月一号这一天哦
		for (int i = Small._year + 1; i < Big._year; i++)
		{
			totalday += GetYearDay(i);
		}
		int FromThatYearDay = FromThatYearDayFunc(Small);
		totalday = totalday + ToThisYearDay + FromThatYearDay;
	}
	else  //Big._year == Small._year
	{
		int BeforeThisYearDay = ToThisYearDayFunc(Small);
		totalday = ToThisYearDay - BeforeThisYearDay;
	}
	
	return totalday;
}

void Date::DatePrint()
{	//对齐填充暂时不做要求
	cout << _year << 
		"-" << _month << 
		"-" <<  _day << endl;
}

Test.cpp

对于不同的功能进行测试。

#include "Date.h"

using namespace std;

int main()
{
	/*Date d1(1999, 12, 31);
	d1.DatePrint();*/

	/*Date d2(d1);
	d2.DatePrint();

	//赋值运算符重载
	Date d3;
	d3.DatePrint();
	d3 = d1;
	d3.DatePrint();*/

	//+=重载  ‘日期+=天数’的格式
	/*Date d1(2023, 12, 31);
	d1.DatePrint();
	d1 += 1;
	d1.DatePrint();
	d1 += 80;
	d1.DatePrint();*/

	//‘日期+天数’
	/*Date d1(2023, 12, 31);
	Date temp = d1 + 1;
	temp.DatePrint();
	temp = temp + 80;
	temp.DatePrint();*/
	
	//-=重载‘日期-=天数’
	/*Date d1(2024, 1, 1);
	d1 -= 1;
	d1.DatePrint();
	d1 -= 80;
	d1.DatePrint();*/

	//‘日期-天数'
	/*Date d1(2024, 1, 1);
	d1 = d1 - 1;
	d1.DatePrint();
	d1 = d1 - 80;
	d1.DatePrint();*/

	//++日期  前置++重载
	/*Date d1(2023, 12, 31);
	++d1;
	d1.DatePrint();*/

	//日期++ 后置++重载
	/*Date d1(2023, 12, 31);
	d1++;
	d1.DatePrint();*/

	//--日期  前置--重载
	/*Date d1(2024, 1, 1);
	--d1;
	d1.DatePrint();*/

	//日期--  后置--重载
	/*Date d1(2024, 1, 1);
	d1--;
	d1.DatePrint();*/

	/*Date d1(2024, 12, 13);
	Date d2(2024, 12, 12);*/
	/*cout << (d1 > d2) << endl;
	cout << (d1 == d2) << endl;
	cout << (d1 < d2) << endl;*/
	/*cout << (d1 != d2) << endl;
	cout << (d1 >= d2) << endl;
	cout << (d1 <= d2) << endl;*/

	//日期-日期		比如,以2006-1-14 - 1999-12-15为例子
	Date d1(2025, 1, 1);
	Date d2(2024, 4, 16);
	int sub = d1 - d2;
	cout << sub << endl;

	return 0;
}


本博客仅供个人参考,如有错误请多多包含。
Aruinsches-C++日志-4/17/2024
  • 18
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值