运算重载符号(C++)

目录

前言

正文

 1. 不能重载的运算符

 2.怎么写运算符重载

 3.一元运算符和二元运算符: 

 4.思考一个问题 

总结

前言

https://blog.csdn.net/qq_42683011/article/details/102087764

基础应用:
重载运算符最需要考虑的即为参数与返回值问题

(这里以operator = 为例):

参数:
        一般地,赋值运算符重载函数的参数是函数所在类的const类型的引用, 如:

MyStr& operator =(const MyStr& str);
加const是因为:

        我们不希望在这个函数中对用来进行赋值的“原版”做任何修改。
        加上const,对于const的和非const的实参,函数都能接受;如果不加,就只能接受非const的实参。
用引用是因为:

        这样可以避免在函数调用时对实参的一次拷贝,提高了效率
注意:
        上面的规定都只是推荐,可以不加const,也可以没有引用,甚至参数可以不是函数所在的对象。

返回值:
        一般地,返回值是被赋值者的引用(但有时返回左值还是右值需要相当的考虑),即*this

MyStr& operator =(const MyStr& str);
        这样在函数返回时避免一次拷贝,提高了效率。

        更重要的,根据赋值运算符的从左向右的结合律, 可以实现连续赋值,即类似a=b=c
        如果返回的是值,则执行连续赋值运算后后头得到的将是一个匿名副本, 为不可更改的右值,就是说是const类型, 再执行=c就会出错。

注意:
        这也不是强制的,完全可以将函数返回值声明为void,然后什么也不返回,只不过这样就无法连续赋值,所以具体的返回值与参数的设定完全取决于需求, 是非常值得设计者考量的。

正文

1. 不能重载的运算符

        C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。函数名字为:关键字operator后面接需要重载的运算符符号。函数原型:返回值类型 operator操作符(参数列表)

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

运算符重载是具有特殊函数名的函数
函数名:operator运算符 //eg:operator==
有几个操作数,就有几个参数
参数:运算符操作数
返回值:运算符运算后的结果

 2.怎么写运算符重载

 下面简单写一下==的运算符重载:

#include<iostream>
using namespace std;
class Data
{
public:
	Data(int year = 1, int month = 1, int day = 1)//自己写的全缺省构造
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Data(const Data& d)//拷贝构造
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	
//private:
	int _year;
	int _month;
	int _day;
};
bool operator==(Data d1, Data d2)//运算符重载函数
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}
int main()
{
	Data d1(2022, 10, 13);
	Data d2(2022, 10, 13);
	if (operator==(d1, d2))
	{
		cout << "==" << endl;
	}
	if (d1 == d2)
等价于if(operator==(d1, d2)),编译器自动处理成对应的运算符重载
	{
		cout << "==" << endl;
	}
	return 0;
}

        发现如果使用运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?且这不就相当于额外写了一个函数,如果两个数相等就返回1,不相等就返回0。

那如果修改成以下代码?发现这样会报错。     

#include<iostream>
using namespace std;
class Data
{
public:
	Data(int year = 1, int month = 1, int day = 1)//自己写的全缺省构造
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Data(const Data& d)//拷贝构造
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	bool operator==(Data d1, Data d2)//运算符重载函数
	{
		return d1._year == d2._year
			&& d1._month == d2._month
			&& d1._day == d2._day;
	}	
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Data d1(2022, 10, 13);
	Data d2(2022, 10, 13);
	if (operator==(d1, d2))
	{
		cout << "==" << endl;
	}
	if (d1 == d2)
	{
		cout << "==" << endl;
	}
	return 0;
}

         这样写会报错,==的参数太多了,是因为成员函数还有一个隐含的指针this。

所以需要进行修改成如下代码:

        作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this。d1默认传给了this,this->_month == d._month,让this和d进行比较。

//bool operator==(Data d1, Data d2)

//if (operator==(d1, d2))

bool operator==(Data d)
运算符重载是具有特殊函数名的函数,编译器其实会处理成(Data* const this,Data d)
	{
		return _year == d._year//d1默认传给了this,
			&& _month == d._month//this->_month = d._month//让this和d进行比较
			&& _day == d._day;
	}

        d1默认传给了this,operator==(Data d)这个整体看成一个成员函数,而调用成员函数就是形式是(d1.函数名),然后对于其参数Data d,传参传入d2进行比较

int main()
{
	Data d1(2022,10,13);
	Data d2(2022, 10, 13);
	//作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
	if (d1.operator==(d2))//operator==(Data d)
	{
		cout << "==" << endl;
	}
	if (d1 == d2)
	{
	cout << "==" << endl;
	}
	return 0;
}

 对于以上代码还可以进行优化:

        因为传值传参会调用拷贝构造,所以最好加上引用,为了防止写反,最好加上const

#include<iostream>
using namespace std;
class Data
{
public:
	Data(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Data(const Data& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}


	bool operator==(const Data& d)
 //这样形参传给实参就不用调用拷贝构造函数了,因为只有自定义类型传值传参就会调用拷贝构造函数
	{
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Data d1(2022, 10, 13);
	Data d2(2022, 10, 13);
	if (d1 == d2)
	{
		cout << "==" << endl;
	}
	return 0;
}

3. 一元运算符和二元运算符: 

在重载一个运算符为成员函数时,其参数表中没有任何参数,这说明该运算符是 ( )

A.无操作数的运算符

B.二元运算符

C.前缀一元运算符

D.后缀一元运算符

A.重载为成员函数时,其函数的参数个数与真实的函数参数个数会减少1个,减少的则通过this指针进行传递,所以无参 则说明有一个参数,故错误

B.无参成员函数相当于有一个参数的全局函数,不能是二元运算符

 C.正确

D.区分前缀后缀时,后缀运算需要加一个int参数

     前置和后置因为都是一样的,所以用int i的参数来区分,(注意i可以不写,形参不写代表着不用这个值或者说不接收)直接写int也可以 规定无参为前置++ ,这里只是改变了函数名的修饰规则以用来区分。

        Data operator++(int i = 0);这样全缺省是错误的,因为i没有实际的意义,只是用来区分前置与后置++,或者说是全缺省和无参的函数在调用时区分不开。
 

4.思考一个问题 

        从下代码发现:当bool operator==(const Data& d1, const Data& d2)和bool operator==(const Data& d)同时存在时可以编译通过,为什么可以同时存在?

#include<iostream>
using namespace std;
class Data
{
public:
	Data(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Data(const Data& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}


	bool operator==(const Data& d)
//这样形参传给实参就不用调用拷贝构造函数了,因为主要自定义类型传值传参就会调用拷贝构造函数
	{
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}
//private:
	int _year;
	int _month;
	int _day;
};
bool operator==(const Data& d1, const Data& d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}
int main()
{
	Data d1(2022, 10, 13);
	Data d2(2022, 10, 13);
	if (d1 == d2)
	{
		cout << "==" << endl;
	}
	return 0;
}

         因为一个是全局函数,一个是类里面的函数,他们的函数名相同,没有关系。但是他会优先调用类里面的重载。可以通过上面代码的调试发现:


类成员函数和全局函数的对比:
        1).类成员函数和全局函数的区别就是,一个是面向对象,一个是面向过程,这同样也是c与c++的区别;
        2).类成员函数==(转成)==>全局函数:增加一个参数,增加的这个参数是代替this指针的;
        3).全局函数==(转成)==>类成员函数:减少一个参数,减少的这个参数通过this指针隐藏。

5. 那么写好了==,>、<、=怎么写了?

        以下以<为例子:bool operator<(const Data& d)

#include<iostream>
using namespace std;
class Data
{
public:
	Data(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Data(const Data& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	bool operator<(const Data& 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;
		}
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Data d1(2022, 9, 14);
	Data d2(2022, 10, 14);

	if (d1 < d2)
	{
		cout << "<" << endl;
	}
	return 0;
}

 注意:=怎么写?依葫芦画瓢如下:

        但是这样写其实不对,像赋值语句j = i,返回值其实是j,但是这样返回值是void,如果需要满足连续赋值k = j = i,就需要对返回值进行修改。

	//d2 = d1; -> d2.operator=(&d2,d1)
	void operator=(const Data& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

注意以下的区分:       

        Data d3(d1); //拷贝构造 -- 一个存在的对象去初始化另一个要创建的对象
        d2 = d1; //赋值重载/复制拷贝 -- 两个已经存在的对象之间赋值

当这样赋值时会报错:

d3 = d2 = d1;
//二元“ = ”: 没有找到接受“void”类型的右操作数的运算符(或没有可接受的转换)

 所以进行修改:

所以应该写成以下这样,需要满足值,因为对于d2 = d1;返回值应该是d2,但是要注意返回值怎么写
d2 = d1; -> d2.operator=(&d2,d1)
	
Data operator=(const Data& d)
但是我们发现这种写法是传值返回,
所有的传值返回和传值传参一样,会生成一个拷贝,
自定义类型传值会调用拷贝构造函数

 进一步进行修改:
        因为如果出了作用域返回值还在,可以用引用返回:注意d2 = d1,把d1赋给d2,返回值是d2,d2默认传给this。this是指针,所以返回*this得到指针本身。

Data& operator=(const Data& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
	return *this;
}

         同样要注意,不排除写错了,自己给自己赋值,所以应该这样写:加上if(this != d),就是避免d2 =d1时写成d2 = d2,d2默认传给this的,d1传给const Data& d的,所以如果d = this就是自己给自己赋值了。同样这样的好处是,只会是权限的缩小,不会有权限的扩大。

	Data& operator=(const Data& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}

        注意:赋值运算符在类中不显式实现时,编译器会生成一份默认的,此时用户在类外再将赋值运算符重载为全局的,就和编译器生成的默认赋值运算符冲突了,故赋值运算符只能重载成成员函数


总结

        运算重载符号(C++)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值