类和对象(中下)

本文详细介绍了C++中的运算符重载,特别是针对自定义类型如Date类的重载,包括等于运算符(==)、小于运算符(<)及其相关逻辑运算符的实现。此外,还讨论了赋值运算符重载的注意事项,强调了赋值运算符只能作为类的成员函数重载,并解释了其作用和返回值的重要性。最后,提到了const成员函数和取地址运算符重载的概念。
摘要由CSDN通过智能技术生成

5.赋值运算符重载

5.1 运算符重载

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

函数名字为:关键字operator后面接需要重载的运算符符号。

函数原型:返回值类型 operator操作符(参数列表)

注意

  1. 不能通过连接其他符号来创建新的操作符:比如operator@重载操作符必须有一个类类型参数

  2. 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义

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

  4. .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//写一个公有的函数避免在类外不能使用的情况——麻烦
	//int GetYear()
	//{
	//	return _year;
	//}

	//直接写进类里,但是参数过多
	bool operator==(const Date& x)
	{
		return _year == x._year
			&& _month == x._month
			&& _day == x._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

//bool DateEquel(Date x1, Date x2)
//bool func(Date x1, Date x2)
//bool riqixiangdeng(Date x1, Date x2)
//{
//	return x1._year == x2._year
//		&& x1._month == x2._month
//		&& x1._day == x2._day;
//}

//函数的类型是运算之后的返回值决定的
//bool operator==(Date x1, Date x2)//第一个参数是左操作数,第二个操作数是右操作数
//bool operator==(const Date& x1, const Date& x2)
//{
私有的在类外面不能直接访问
//	return x1._year == x2._year
//		&& x1._month == x2._month
//		&& x1._day == x2._day;
//}

//int operator-(Date x1, Date x2)
//{}

int main()
{
	Date d1(2022, 7, 23);
	Date d2(2022, 7, 24);

	// 内置类型可以直接使用运算符运算,编译器知道要如何运算
	// 自定义类型无法直接使用运算法,编译器也不知道要如何运算。想支持,自己实现运算符重载即可
	
	//cout << operator==(d1,d2) << endl;//可以写自定义函数,可读性差
	cout << d1.operator==(d2) << endl; // -> d1.operator==(&d1, d2)
	
	cout << (d1 == d2) << endl;//编译器会自动转换成cout << operator==(d1,d2) << endl;

	自定义类型:
	//d1 < d2;
	//d1++;
	//d1 + 100;
	//Date d3(2022, 10, 1);
	//d3 - d2;

	return 0;
}

class Date
{
public:
	int GetMonthDay(int year, int month)
	{
		//静态区——每次访问同一个
		static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

		//if (month == 2 && IsLeapYear(year))
		if (month == 2
			&& ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
		{
			return 29;//闰年
		}
		else
		{
			return days[month];
		}
	}

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

	bool operator==(const Date& x)
	{
		return _year == x._year
			&& _month == x._month
			&& _day == x._day;
	}

	bool operator<(const Date& x);
	bool operator>(const Date& x);
	bool operator>=(const Date& x);
	bool operator<=(const Date& x);
	bool operator!=(const Date& x);

	// d1 += 100;
	Date& operator+=(int day)
	{
		_day += day;
		while (_day > GetMonthDay(_year, _month))
		{
			_day -= GetMonthDay(_year, _month);
			++_month;
			if (_month == 13)
			{
				_month = 1;
				_year++;
			}
		}

		return *this;//this指针就是当前对象
	}

	// d1 + 100;日期加100天还是日期所以类型是date
	Date operator+(int day)
	{
		Date ret(*this);
		ret._day += day;
		while (ret._day > GetMonthDay(ret._year, ret._month))
		{
			ret._day -= GetMonthDay(ret._year, ret._month);
			++ret._month;
			if (ret._month == 13)
			{
				ret._month = 1;
				ret._year++;
			}
		}

		return ret;
	}

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

int main()
{
	Date d1(2022, 7, 23);
	//Date d2(2022, 7, 24);
	//d1 == d2;
	//d1 < d2;
	Date ret = d1 + 50;
	//Date ret(d1 + 50);
	/*d1 += 50;*/
	//d1++;

	/*int i = 10;
	i + 50;//i不变
	i += 50;//i才变*/

	//Date d3(20202, 10, 1);
	//d3 - d2;

	return 0;
}

 
 

5.2 赋值运算符重载

  1. 赋值运算符重载格式
    参数类型:const T&,传递引用可以提高传参效率

返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值,检测是否自己给自己赋值

返回*this :要复合连续赋值的含义

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 ;
};
  1. 赋值运算符只能重载成类的成员函数,不能重载成全局函数
    原因
    赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数

《C++ prime》中说明我们可以重载赋值运算符。不论形参的类型是什么,赋值运算符都必须定义为成员函数

  1. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。
    注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值

6.日期类的实现和前置++和后置++重载

Date.cpp

#define _CRT_SECURE_NO_WARNINGS 1

#include "Date.h"

void Date::Print()
{
	cout << _year << "/" << _month << "/" << _day << endl;
}

// 任何一个类,只需要写一个> == 或者 < ==重载 剩下比较运算符重载复用即可
//类作为一个整体,上下都会搜索,所以谁复用谁的顺序无所谓
bool Date::operator== (const Date& d)
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

// d1 != d2  复用就可以
bool Date::operator!=(const Date& d)
{
	return !(*this == d);
}

// d1 > d2
bool Date::operator>(const Date& d)
{
	if ((_year > d._year)//年大就大
		|| (_year == d._year && _month > d._month)//年相等,月大就大
		|| (_year == d._year && _month == d._month && _day > d._day))//年月想等,天大就大
	{
		return true;
	}
	else
	{
		return false;
	}
}

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);
}



// d2 += d1 += 100  自己的值也要改变,返回值就是自己
Date& Date::operator+=(int day)
{
	if (day<0)
	{
		return *this -= -day;
	}

	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		++_month;//天数超过,月就++
		if (_month == 13)
		{
			_year++;//月数超过,年就++
			_month = 1;
		}
	}

	return *this;//this指针就是当前的自己
}

// d1 + 100  自己不改变,返回值是另一个对象
Date Date::operator+(int day)
{
	//Date ret(*this);
	Date ret = *this;//拷贝构造,不是赋值
	ret += day;

	return ret;
}

//反过来复用——+=会变低效,拷贝构造会变多
//Date Date::operator+(int day)
//{
//	Date ret = *this;
//	// ...
//	ret._day += day;
//	while (ret._day > GetMonthDay(ret._year, ret._month))
//	{
//		//...
//	}
//
//	return ret;
//}
//
 d1 += 100
//Date& Date::operator+=(int day)
//{
//	*this = *this + day;
//
//	return *this;
//}

Date& Date::operator++() // 前置,返回++之后的
{
	//*this += 1;
	//return *this;

	return *this += 1;
}

Date Date::operator++(int) // 后置,返回++之前的
{
	Date tmp(*this);
	*this += 1;

	return tmp;
}


// // 后置--

Date Date::operator--(int)

{

	Date ret(*this);

	*this -= 1;

	return ret;

}



// 前置--

Date& Date::operator--()

{

	*this -= 1;

	return *this;
	//return *this -= 1;

}


// d1 - d2

// 日期-日期 返回天数
Date Date::operator-(int day)
{
	Date ret = *this;//拷贝构造
	ret -= day;
	return ret;
}


Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		return *this += -day;
	}


	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

//简单写法——不复用
//深度复用
int Date::operator-(const Date& d)
{
	int flag = 1;
	Date max = *this;
	Date min = d;
	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}
	int n = 0;
	while (min != max)
	{
		++min;
		++n;
	}

	return n*flag;
}


//void Date::operator<<(ostream& out)//out就是cout的别名
//{
//	out << _year << "-" << _month << "-" << _day << endl;
//}

 

Date.h

#pragma once

#include<iostream>
#include<assert.h>
using namespace std;


// 一个到底可以重载哪些运算符?——> 哪些运算符对这个类型有意义
class Date
{
	//友元函数 - -在这个函数内部可以使用Date对象访问私有保护成员
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& out, Date& d);

public:
	// 获取某年某月的天数
	// 会频繁调用,所以直接放在类里面定义作为inline
	int 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;
	}

	//检查日期,防止使用不合法的日期
	bool CheckDate()
	{
		if (_year >= 1
			&& _month > 0 && _month <13
			&& _day >0 && _day <= GetMonthDay(_year, _month))
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	// 构造会频繁调用,所以直接放在类里面定义作为inline
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;

		1.
		//if (!this->CheckDate())
		//{
		//	Print();
		//	cout << "刚构造的日期非法" << endl;
		//}

		//2.
		assert(CheckDate());
	}

	void Print();

	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);

	Date operator+(int day);
	Date& operator+=(int day);

	// ++d1;
	// d1++;
	// 直接按特性重载,无法区分
	// 特殊处理,使用重载区分,后置++重载增加一个int参数跟前置构成函数重载进行区分
	Date& operator++(); // 前置
	Date operator++(int); // 后置

	Date& operator--(); // 前置
	Date operator--(int); // 后置

	Date operator-(int day);
	Date& operator-=(int day);

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

	/*void operator<<(ostream& out);*/


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

 /*ostream& operator<<(ostream& out, const Date& d);*/

//流插入重载
inline ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}//写成inline声明和定义不分离

//流提取重载
inline istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	assert(d.CheckDate());
	return in;
}

test.cpp


//菜单
void TestDate2()
{
	const char* WeeDayToStr[] = { "周一", "周二", "周三", "周四", "周五", "周六", "周天" };

	Date d1, d2;
	int day = 0;
	int option = 0;
	do{
		cout << "*****************************" << endl;
		cout << " 1.日期加、减天数  2.日期减日期" << endl;
		cout << " 3.日期->周几    -1.退出" << endl;
		cout << "*****************************" << endl;

		cin >> option;
		cout << "请选择:";
		if (option == 1)
		{
			cout << "请一次输入日期及天数(减天数需要输入负数):";
			cin >> d1 >> day;
			cout <<"日期加减天数后的日期:"<< d1 + day << endl;
		}
		else if (option == 2)
		{
			cout << "请依次输入两个日期:";
			cin >> d1 >> d2;
			cout << "相差的天数:" << d1 - d2 << endl;

		}
		else if (option == 3)
		{
			cout << "请输入日期:";
			cin >> d1;
			Date start(1, 1, 1);
			int n = d1 - start;
			int weekDay = 0; // 周一
			weekDay += n;
			//weekDay += 9;
			//cout << "周" << weekDay % 7 + 1 << endl;
			cout << WeeDayToStr[weekDay % 7] << endl;
		}
		else
		{
			cout << "无此选项,请重新选择" << endl;
		}

	
	} while (option != -1);
	
}


int main()
{
	TestDate2();
	return 0;
}
int i = 0;
double d = 1.1;
cout << i;//cout.operator<<(i);
cout << d;//cout.operator<<(d);
  1. 库里面写好了运算符重载
  2. 自动识别类型,它们构成函数重载

运算符重载:让自定义类型对象可以用运算符。转换成调用这个重载函数
函数重载:支持函数名相同的函数同时存在

两者虽然都用了重载这个词,但是它们之间没有必然联系。

 
 

7.const成员

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

void TestDate6()
{
	Date d1(2022, 7, 25);
	const Date d2(2022, 7, 25);
	d1.Print();//&d1 ——> Date*
	d2.Print();//&d2 ——> const Date*

	d1 < d2;
	d2 < d1;
}

int main()
{
	TestDate6();
	return 0;
}


const本身不能被修改但是有一次初始化的机会
Date*传Date*可以
const Date*传Date*属于权限的放大,不可行
void Date::Print()//Date* const this
{
	cout << _year << "/" << _month << "/" << _day << endl;
}

bool Date::operator<(const Date& d)///Date* const this
{
	return !(*this >= d);
}
修改:
void Date::Print() const  //const修饰的是this指针指向的内容
{
        this ->_year = 1;//报错,不能被修改
	cout << _year << "/" << _month << "/" << _day << endl;
}

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

 

第一个也没有问题,因为Date* 传const Date*属于权限的缩小
const修饰的是this指针指向的内容,也就是保证了成员函数内部不会修改成员变量
const对象和非const对象都可以调用这个成员函数
 
 

8.取地址及const取地址操作符重载

这两个默认成员函数一般不用重新定义 ,编译器默认会生成。

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比
如想让别人获取到指定的内容!

class A
{
public:
	// 他们是默认成员函数,我们不写编译器会自动生成,自动生成就够用了,所以一般是不需要我们自己写的
	// 特殊场景:不想让别人取到这个类型对象的地址
	A* operator&()
	{
		return nullptr;//返回空指针或者设为私有
	}

	const A* operator&()const
	{
		return nullptr;
	}

	void Print() const
	{
		//_year = 1;
		cout << _year << "/" << _month << "/" << _day << endl;
	}

	/*void Print()//构成函数重载
	{
	_year = 1;
	cout << _year << "/" << _month << "/" << _day << endl;
	}*/
private:
	int _year;   // 年
	int _month; // 月
	int _day;   // 日
};

int main()
{
	A d1;
	const A d2;
	d1.Print();//权限的缩小
	d2.Print();//权限的平移

	cout << &d1 << endl;
	cout << &d2 << endl;

	return 0;
}
评论 31
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hey pear!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值