【C++】拿捏运算符重载

运算符重载

概念

当运算符被⽤于类类型的对象时,C++语⾔允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使⽤运算符时,必须转换成调⽤对应运算符重载,若没有对应的运算符重载,则会编译报错

  • 意思就是说,对象进行运算操作的时候,必须使用运算符重载

像内置类型这些我们可以直接比较,如int 和int之间的比较

int a = 10;
int b = 20;
bool c = a < b;

因为他们根据我们的数学基础就能直接比较出来。

  • 但是像类这样的自定义类型,他们的比较规则这些不单一,就需要我们重载运算符,来自定义规则。

语法明细

【概念】:运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似

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

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

  • 根据上面的语法概念,就可以写出==的运算符重载函数
bool operator==(const Date& d1, const Date& d2)

注意事项:

  1. 不能通过连接其他符号来创建新的操作符:比如operator@
    在这里插入图片描述
    2.重载操作符必须有一个类类型(自定义类型)参数

在这里插入图片描述
3.运算符重载可以放在全局,但是不能访问当前类的私有成员变量

  • 可以看到,虽然运算符重载我们写出来了,但是在内部调用当前对象的成员 时却报出了错误,说无法访问private的成员,那此时该怎么办呢?
    在这里插入图片描述
    👉解决办法1:去掉[private],把成员函数全部设置为公有[public]

👉解决办法2:提供公有函数getYear()、getMonth()、getDay()

👉解决办法3:设置友元【不好,会破坏类的完整性】

👉解决办法4:直接把运算符重载放到类内

  • 对于二、三两种解决方案暂时先不考虑,最直观的就是第一种方式,但是第一种方式很危险,我们通常是不希望成员变量被修改的,所以这里测试第4中方式。

在这里插入图片描述

  • 这里为什么说参数多了呢?
  • 还记得我们在类的成员函数说的this指针吗?如果⼀个重载运算符函数是成员函数,则它的第⼀个运算对象默认传给隐式的this指针,因此运算
    符重载作为成员函数时,参数⽐运算对象少⼀个

补充概念:

  1. 重载运算符函数的参数个数和该运算符作⽤的运算对象数量⼀样多。⼀元运算符有⼀个参数,⼆元运算符有两个参数,⼆元运算符的左侧运算对象传给第⼀个参数,右侧运算对象传给第⼆个参数
  2. 也就是说,==因为原本是二元运算符,他的操作数有两个,所以他的重载参数也应该有两个
  3. 运算法重载的形参应该与比较的操作数一一对应,d1 == d2
  4. d1对应第一个形参,d2对应第二个形参,⼆元运算符的左侧运算对象传给第⼀个参数,右侧运算对象传给第⼆个参数。

所以当运算符重载函数放到类内时,就要改变其形参个数,否则就会造成参数过多的现象,在形参部分给一个参数即可,比较的对象就是当前调用这个函数的对象即【this指针所指对象】与【形参中传入的对象】

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

  • 那既然这是一个类内的函数,就可以使用对象.的形式去调用,运行结果如下
bool Date::operator==(const Date& d)
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

在这里插入图片描述

  • .*、 ::、 sizeof 、?: 、. 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
    注意 . *是一个新的运算符,不是. 和 *。

练习巩固

  • 接下来展示三位同学的代码
//Student1
bool operator<(const Date& d)
{
	return _year < d._year
		|| _month < d._month
		|| _day < d._day;
}

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

//Student3
bool operator<(const Date& d)
{
	if (_year < d._year) {
		return true;
	}
	else if (_year == d._year && _month < d._month) {
		return true;
	}
	else if (_year == d._year && _month == d._month && _day < d._day) {
		return true;
	}
	else {
		return false;
	}
}

首先来看一下第一位同学的运行结果
在这里插入图片描述

  • 有的人看到就说了,嗯,没问题,通过!
  • 那这样呢?我们修改了年份和月份,这位同学的逻辑就是只要年和月和天其中一个比d1大,那么就比d1大。就出先了下面问题

在这里插入图片描述

  • 这里说说我们正确的逻辑,我们这里必须从年开始比,我们如果想让d1确实小于d2,那么我们就从年开始比谁小,如果年都直接小于d2了,那么一定比d2小,那么如果年不小于d2,那么这里只有在d1和d2年份相等的情况下,比较月份谁小,同理,如果年份和月份都相等的话,他们只能通过天数来比谁小,把这些d1比d2小的情况列举出来,其余的情况就是d1不比d2小。
  • 所以第二位同学和第三位同学的逻辑是对的,通过。

知道了<和==怎么去重载,我们再来看看<=

  • 有同学说简单,把上面的<改成<=的逻辑就行了。
bool operator<=(const Date& d)
{
	if (_year <= d._year) {
		return true;
	}
	else if (_year == d._year && _month <= d._month) {
		return true;
	}
	else if (_year == d._year && _month == d._month && _day <= d._day) {
		return true;
	}
	else {
		return false;
	}
}

  • 确实是,这种修改很直观。
  • 但是我想说还有优化的代码,我们可以直接复用<和=两个运算符的重载
return (*this < d) || (*this == d);

  • 是不是直接就可以使用我们已经有运算符,这些运算符相互有关联
  • 小于、小于等于都会了,那大于>和大于等于>=呢?不等于!=呢?
bool operator>(const Date& d)  
bool operator>=(const Date& d)  
bool operator!=(const Date& 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);
}

赋值运算符的重载

语法说明及注意事项

Date& operator=(const Date& d)
{
	if (this != &d)		//判断一下是否有给自己赋值
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	return *this;
}

👉【参数类型】:const T&,传递引用可以提高传参效率

  • 前面我们说了,传值调用的时候,形参是实参的拷贝,传的如果是类这样的自定义类型 编译器就会去调用他的拷贝构造。这里我们的目的不是要拷贝一个对象而是重载运算符 ,为了提高效率就传引用。加const则是为了防止当前对象被修改和权限访问的问题,

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

  • 这块重点讲一下,本来对于赋值运算符来说是不需要有返回值的,设想我们平常在定义一个变量的时候为其进行初始化使用的时候赋值运算,也不会去考虑到什么返回值,但是对于自定义类型来说,我们要去考虑这个返回值
Date d1(2023, 3, 27);
Date d2;
Date d3;

d3 = d2 = d1;

  • 这种场景,d1初始化完后给d2赋值,d2赋值又赋值给d3。这时候我们如果d2想通过赋值运算符得到d1的值,重载赋值运算符就要写返回值
  • 因为是为当前对象做赋值,所以应该返回当前对象也就是*this
    👉 【返回值类型】:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 若是返回一个出了当前作用域不会销毁的变量,就可以使用引用返回来减少拷贝构造
    👉 【多方位考虑】:检测是否自己给自己赋值
  • 在外界调用这个赋值重载的时候,不免会有人写成下面这种形式
  • d1 = d1;
  • 那自己给自己赋值其实并没有什么意义,所以我们在内部应该去做一个判断,若是当前this指针所指向的地址和传入的地址一致的话,就不用做任何事,直接返回当前对象即可。若是不同的话才去执行一个赋值的逻辑
if (this != &d)

  • 不仅如此,也可以完成这种【链式】的连续赋值
    在这里插入图片描述
  • 那现在我想问,下面的这两种都属于【赋值重载】吗?
d2 = d1;		//赋值重载

Date d3 = d2;	//赋值重载?

  • 通过调试可以观察到d2 = d1就是我们刚才说的赋值重载,去类中调用了对应的成员函数;但是对于Date d3 =d2来说,却没有去调用赋值重载,而是去调用了【拷贝构造】,此时就会有同学很疑惑? `
    在这里插入图片描述
  • 这里一定要区分的一点是,赋值重载是两个已经初始化的对象才可以去做的工作;对于拷贝构造来说是拿一个已经实例化的对象去初始化另一个未初始化 对象
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值