C++修炼之筑基期第四层 ——透过日期类看运算符重载 赋值运算符重载 取地址操作符重载


例如:



bool operator==(Date d1,Date d2);


### 🌺牛刀小试


定义一个`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;//日
};


#### 🍁==重载


我们都知道`==`是用来比较的运算符,`Date`类对象进行比较该怎么比较呢?我们可以规定,如果两个对象的`年、月、日`都相当则两个对象相等,返回`true`。


***🌼错误示例1***



bool operator==(Date d1, Date d2)
{
return (d1._year == d2._year) &&
(d1._month == d2._month) &&
(d1._day == d2._day);
}
void Test1()
{
Date d1(2023, 4, 1);
Date d2(2023, 3, 2);

//注意此处会有操作符优先级的问题,所以要加小括号
cout << (d1 == d2) << endl;

}


![运行出错](https://img-blog.csdnimg.cn/1fdcb449ac9b4772b21458817b81a180.png)


此处有一个典型的错误:`该函数不是类的成员函数,无法访问类的私有成员!`


有3个办法可以解决:


1. 将类的成员改为`公有`(虽然可以解决,但会破坏类的封装性);
2. 使该函数成为类的`友元函数`(后面的章节再做讲解);
3. 将该函数包含在类中成为类的`成员函数`;


目前我们只能选择`第3种方法`。


***🌼错误示例2***



class Date
{
public:
//构造函数
Date(int year = 0, int month = 0, int day = 0)
{
if (month > 0 && month < 13
&& (day > 0 && day <= GetMonthDay(year, month)))
{
_year = year;
_month = month;
_day = day;
}
}
bool operator==(Date d1, Date d2)
{
return (d1._year == d2._year) && (d1._month == d2._month) && (d1._day == d2._day);
}
private:
int _year;
int _month;
int _day;

};


***🌼特别注意***


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


还记得成员函数有什么特性吗?成员函数有一个自带的参数`this`,类型为`类类型`。因为我们不可能将`this`指针删掉,所以只能`省略第一个参数`。


上一章中曾提到,`为减少拷贝引起的消耗,尽量使用引用的方式传参`。


***🌼正确的做法***



class Date
{
public:
//…
bool operator==(const Date& d)//若不改变形参最好用const修饰
{
return (_year == d._year) &&
(_month == d._month) &&
(_day == d._day);
}
//…
};


### 🌺运算符重载的特性


运算符重载有如下`特性`:


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


### 🌺其它运算符重载的实现


有了上述的`==`作为示例,我们还可以实现`< > <= >= + - ++ --`等一系列操作符的重载。


#### 🍁> < >= <= != 重载


实现两个重载后,其他重载的实现可以复用已经实现的操作符。



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

//…
};


#### 🍁+= -= + - 重载


注意:下列四个运算符的右操作数都为`天数`。



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

//…
};


#### 🍁前置++与后置++重载


`前置+`+和`后置++`都是一元运算符,为了让前置++与后置++能形成正确重载,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;
}
//…
};


#### 🍁日期-日期的实现


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



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;
}
//...

}


#### 🍁<< 与 >>重载


***🌼错误示例***



class Date
{
//…
//使用因为返回,为了适应连续输入或输出的情况
ostream& operator<<(ostream& out)
{
out << _year << “年” << _month << “月” << _day << “日” << endl;
return out;
}
istream& operator>>(istream& in)
{
in >>_year >>_month >>_day;
return in;
}
//…
}


`>> <<` 是二元操作符,上文中提到二元操作符第一个参数为左操作数,第二个参数为右操作数。此时这段代码第一个参数为`this`,也就意味着左操作数变成了`对象`,右操作数变成了`cout`。那么我们使用时只能这样写:



void Test2()
{
Date d1(2023, 4, 1);
d1 << cout;
}


![](https://img-blog.csdnimg.cn/4fbefe629d804c15aa841ed46dcc68e4.png)


虽然能满足需求,但是用起来`感觉怪怪的`。由于我们无法改变`this`的位置,所以只能使用其它办法来实现`<< >>`的重载了。


这里我们只能将重载定义在类的外面才能避开`this`的影响。但是类外的函数又访问不了类的`私有成员`。我们只能通过将重载函数设置为类`友元函数`来实现了。


***🌼正确的做法***



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


## 🌷简单的测试


到这里,我们试着写一个测试函数验证运算符重载是否正确并学会运算符重载的使用。



void Test()
{
Date d1(2023, 4, 1);
Date d2(2022, 3, 1);
cout << d1+1 <<endl;
cout << d2 <<endl;
cout << d1++ << endl;
cout << d2-- << endl;
cout << d1 << endl;
cout << d2 << endl;
cout << (d1 == d2) << endl;
cout << (d1 >= d2) << endl;
cout << (d1 <= d2) << endl;
cout << (d1 != d2) << endl;
cout << (d1+100) << endl;
cout << (d1 - 200) << endl;
}


测试结果:  
 ![运行结果](https://img-blog.csdnimg.cn/0a9624559291476583b24e4a1f44058e.png)


结果证明我们目前实现的运算符重载以及操作符重载还是挺成功的。小伙伴们在自己实现的时候也要记得边写边测试哦~


## 🌷默认成员函数——赋值运算符重载


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


作为与众不同的默认成员函数,其有以下`特性`:


1. 赋值运算符重载格式:  
 参数类型:`const T&`,传递引用可以提高传参效率;  
 返回值类型:`T&`,返回引用可以提高返回的效率,有返回值目的是为了`支持连续赋值`检测是否自己给自己赋值;  
 返回`*this` :要`复合连续赋值`的含义;


***🌼赋值重载***



class Date
{
//…
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
//…
}


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


***🌼错误示例***



class Date
{
//…
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有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 =”必须是非静态成员`。


* 出错原因是:赋值运算符如果`不显式实现`,编译器会生成一个`默认的`。此时用户再在类外自己实现一个`全局的`赋值运算符重载,就和编译器在类中生成的`默认赋值运算符重载冲突`了,故`赋值运算符重载只能是类的成员函数`。


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


这里赋值重载与拷贝构造函数的特性非常相似。


## 🌷默认成员函数——取地址操作符重载


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


***🌼取地址重载***



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


## 🌷const成员


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


***🌼什么情况下需要用const修饰?***


我们可能暂时感受不到`const`修饰的作用,但是遇到如下情况,`const`修饰就非常有必要了。


***🌼举例***



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


![在这里插入图片描述](https://img-blog.csdnimg.cn/384cf8c98f7b49ac90da8c0286dd9686.png)


报错内容为:`“void Date::print(void)”: 不能将“this”指针从“const Date”转换为“Date &”`。


这里是典型的`权限放大`错误,我们不能将`const Date* &d2`传递给形参`Date* this`。


改正的办法为同样用`const`修饰`this`,但具体的写法可不像我们想的那样。



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


因为我们`无法显式的修改thi`s,所以C++规定在函数的后面加上`const`即为修饰`this`。


## 🌷日期类的实现


我们总结上文中的运算符重载,整理一下完整的日期类的实现。此处我们使用多文件的形式实现—>


* `Date.h`文件中进行`头文件包含`、`命名空间展开`、`类的声明`、`内联函数定义`等;
* `Date.cpp`文件中进行对类成员函数的定义。


### 🌺Date.h



#define _CRT_SECURE_NO_DEPRECATE 1
#include
#include<assert.h>
using namespace std;

// 类里面短小函数,适合做内联的函数,直接是在类里面定义的
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;
}


### 🌺Date.cpp



#define _CRT_SECURE_NO_DEPRECATE 1
#include"Date.h"
//构造函数
Date::Date(int year, int month , int day)
{
//判断日期是否合法
if (month > 0 && month < 13 &&
(day > 0 && day <= GetMonthDay(year, month)))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << “日期非法” << endl;
}
}

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

bool Date::operator<(const Date& d) const
{
return _year < d._year
|| (_year == d._year && _month < d._month)
|| (_year == d._year && _month == d._month && _day < d._day);
}

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

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

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

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

//获取当月的天数
int Date::GetMonthDay(int year, int month) const
{
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& 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& 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;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值