c++类与对象(二)——赋值运算符重载与取地址操作符重载


前言:

本章将通过日期类的实现,深入学习运算符重载的实现方法。本章将完成6个默认成员函数中剩余3个——赋值运算符重载取地址操作符重载的学习。

一.运算符重载

1.运算符重载的概念

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

  • 函数名:关键字operator后面接需要重载的运算符符号
  • 函数原型
  • 返回值类型 operator操作符(参数列表)
  • bool operator==(Date d1,Date d2);

需要注意的是:

  1. 不能通过连接其他符号来创建新的操作符:比如operator@
  2. 重载操作符必须有一个类类型参数
  3. 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
  4. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  5. .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

2.实现Date类

定义一个Date类:

class Date
{
    
    
public:
	//构造函数
	Date(int year = 0, int month = 0, int day = 0)
	{
    
    
		//判断日期是否合法
		//GetMonthDay()获取这个月的天数
		if (month > 0 && month < 13 &&
			(day > 0 && day <= GetMonthDay(year, month)))
		{
    
    
				_year = year;
				_month = month;
				_day = day;
		}
		else
		{
    
    
			cout << "日期非法" << endl;
		}
	}
private:
	int _year;//年
	int _month;//月
	int _day;//日
};

函数接口:

// 类里面短小函数,适合做内联的函数,直接是在类里面定义的
class Date
{
    
    
	// 友元函数声明
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);

public:
	// 构造
	Date(int year = 0, int month = 0, int day = 0);
	void Print() const;
	// 当月的天数
	int GetMonthDay(int year, int month) const;
	
	// 重载日期类与日期类运算==, !=, <, <=, >, >=
	bool operator==(const Date& d) const;
	bool operator!=(const Date& d) const;
	bool operator<(const Date& d) const;
	bool operator<=(const Date& d) const;
	bool operator>(const Date& d) const;
	bool operator>=(const Date& d) const;
	
	// 重载日期类与天运算+=, +, -=, -,
	Date& operator+=(int day);
	Date operator+(int day) const;
	Date& operator-=(int day);
	Date operator-(int day) const;

	// 重载日期类与日期类运算-
	int operator-(const Date& d) const;
	
	// 赋值运算符重载
	Date& operator=(const Date& d);

	// 重载日期类前置++--,后置++--
	Date& operator++();
	// int参数 仅仅是为了占位,跟前置重载区分
	Date operator++(int);
	Date& operator--();
	Date operator--(int);

	//取地址重载
	Date* operator&();
	const Date* operator&() const;

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

inline ostream& operator<<(ostream& out, const Date& d)
{
    
    
	out << d._year << "年" << d._month << "月" << d._day << "日";
	return out;
}

inline istream& operator>>(istream& in, Date& d)
{
    
    
	in >> d._year >> d._month >> d._day;
	return in;
}

在实现运算符重载的时候,有一点需要特别注意

二元运算符的重载函数的参数有两个,规定第一个参数左操作数第二个参数右操作数

前面章节我们讲过成员函数的特性,成员函数有一个自带的参数this,类型为类类型。所以我们可以省略第一个参数,只写第二个参数

所以:

(1)> < >= <= != 重载

先实现两个运算符重载函数,其它的就可以复用已经实现好的运算符。

class Date
{
    
    
public:
	//构造函数
	//...
	bool operator==(const Date& d)
	{
    
    
		return (_year == d._year) && (_month == d._month) && (_day == d._day);
	}
	bool operator<(const Date& d) 
	{
    
    
		return _year < d._year
			|| (_year == d._year && _month < d._month)
			|| (_year == d._year && _month == d._month && _day < d._day);
	}
	bool operator<=(const Date& d) 
	{
    
    
		//函数的复用
		return *this < d || *this == d;
	}

	bool operator>(const Date& d) 
	{
    
    
		//函数的复用
		return !(*this <= d);
	}

	bool operator>=(const Date& d) 
	{
    
    
		//函数的复用
		return !(*this < d);
	}

	bool operator!=(const Date& d) 
	{
    
    
		//函数的复用
		return !(*this == d);
	}
//...
};

(2)+= -= + - 重载

注意:下面四个运算符重载的右操作数都为day天数

class Date
{
    
    
public:
	//...
	//获取当月的天数
	int GetMonthDay(int year, int month) 
	{
    
    
		assert(month > 0 && month < 13);

		int monthArray[13] = {
    
     0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
		//判断是否是闰年的二月
		if (month == 2 && 
		((year % 4 == 0 && year % 100 != 0) || (year % 400) == 0))
		{
    
    
			return 29;
		}
		else
		{
    
    
			return monthArray[month];
		}
	}
	//+= 返回自身的引用,减少拷贝
	Date& operator+=(int day)
	{
    
    
		//判断是否加了负数
		if (day < 0)
		{
    
    
			//复用
			*this -= -day;
			return *this;
		}

		_day += day;
		while (_day > GetMonthDay(_year, _month));
		{
    
    
			_day -= GetMonthDay(_year, _month);
			//进位
			_month++;
			if (_month == 13)
			{
    
    
				_year++;
				_month = 1;
			}
		}
		return *this;
	}
	//-= 返回自身的引用,减少拷贝
	Date& operator-=(int day)
	{
    
    
		//判断是否减了一个负数
		if (day < 0)
		{
    
    
			//复用
			*this += -day;
			return *this;
		}

		_day -= day;
		while (_day <= 0)
		{
    
    
			--_month;
			if (_month == 0)
			{
    
    
				--_year;
				_month = 12;
			}

			_day += GetMonthDay(_year, _month);
		}

		return *this;
	}
	Date operator+(int day) 
	{
    
    
		//拷贝构造
		//因为加不改变自身的值,所以创建临时对象
		Date tmp(*this);
		//复用
		tmp += day;
		return tmp;
	}
	Date operator-(int day)
	{
    
    
		Date tmp(*this);
		tmp -= day;
		return tmp;
	}
//...
};

(3)前置++与后置++重载

前置++与后置++都是一元运算符,这二者的区别是:

  • 前置++++再使用返回++之后的数
  • 后置++先使用再++,返回++之前的数

为了能在重载的时候做出区分,·C++·规定:

  • 后置++重载多增加一个int类型的参数,但调用时该参数不用传递,编译器会自动传递。
class Date
{
    
    
public:
	//...
	//前置++
	Date& operator++()
	{
    
    
		*this += 1;
		return *this;
	}
	//后置++
	// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,
	// 故需在实现时需要先将this保存一份,然后给this + 1
	// 而temp是临时对象,因此只能以值的方式返回,不能返回引用
	Date operator++(int)
	{
    
    
		Date tmp(*this);
		*this += 1;
		return tmp;
	}
	//前置--
	Date& operator--()
	{
    
    
		*this -= 1;
		return *this;
	}
	//后置--
	Date operator--(int)
	{
    
    
		Date tmp(*this);
		*this -= 1;
		return tmp;
	}
//...
};

(4)日期-日期的实现

日期+日期没有意义,但是日期-日期有意义,日期-日期代表两日期相距多少天

class Date
{
    
    
//...
	int operator-(const Date& d)
	{
    
    
		Date max = *this;
		Date min = d;
		int flag = 1;

		if (*this < d)
		{
    
    
			max = d;
			min = *this;
			flag = -1;
		}

		int n = 0;
		while (min != max)
		{
    
    
			++min;
			++n;
		}

		return n * flag;
	}
	//...
}

(5)<< 与 >>重载

Date d1(2023,5,1);
cout<<d1;
  1. <<>>是二元操作符,上文提到二元操作符第一个参数为左操作符,第二个参数为右操作符
  2. 因为类中成员函数第一个参数为this,所以左操作数就成了对象,右操作数变成了cout,这就成了d1<<cout,与平常使用的C++语法习惯不符,所以我们不能将<<>>写到类的成员函数中,而是重载在类外面
  3. 但是类外的函数无法访问类的私有函数,所以我们将重载函数设置为友元函数来实现。
class Date
{
    
    
//...
	//申明友元函数
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
	//...
}
ostream& operator<<(ostream& out, const Date& d)
{
    
    
	out << d._year << "年" << d._month << "月" << d._day << "日";
	return out;
}

istream& operator>>(istream& in, Date& d)
{
    
    
	in >> d._year >> d._month >> d._day;
	return in;
}

2.默认成员函数——赋值运算符重载

与之前的构造函数析构函数等默认成员函数相同,赋值运算符重载也是类的6个默认成员函数之一

赋值运算符重载具有以下特性:

  1. 赋值运算符重载格式:
    参数类型const &T,参数引用可以提高传参效率
    返回值类型T&返回值引用可以提高返回的效率,有返回值目的是为了支持连续赋值,检测是否给自己连续赋值
    返回*this:要复合连续赋值的定义
  2. 赋值运算符只能重载成类的成员函数不能重载成全局函数
class Date
{
    
    
//...
	Date& operator=(const Date& d)
	{
    
    
		if (this != &d)
		{
    
    
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
	//...
}

二.默认成员函数——取地址操作符重载

6个默认成员函数只剩两个——取地址重载与const取地址重载。但是,这两个函数实在没有实现的必要,因为我们自己实现与编译器自动实现出来的效果是一样的。

class Date
{
    
    
	//...
	Date* operator&()
	{
    
    
		return this;
	}
	const Date* operator&()const
	{
    
    
		return this;
	}
	//...
};

#. 补充知识点:const成员

const修饰的成员函数称之为const成员函数,const修饰类成员函数,实际修饰的该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改

例如:

class Date
{
    
    
public:
	//...
	void print()
	{
    
    
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
}

void Test3()
{
    
    
	Date d1(2023, 4, 1);
	d1.print();
	const Date d2(2022, 3, 1);
	d2.print();
}

运行结果:

在这里插入图片描述
这是因为权限放大了:我们不能将const Date &d2传递给形参Date* this
正确写法为:

void print() const
{
    
    
	cout << _year << "年" << _month << "月" << _day << "日" << endl;
}

无法显示的修饰隐含参数*this,所以在函数后面加上const修饰。这样做适合不在成员函数内修改成员变量的函数,对无const修饰的类同样适用。


本文到此结束,码文不易,还请多多支持哦!

猜你喜欢

转载自blog.csdn.net/weixin_67401157/article/details/130527438