C++:运算符重载

运算符重载的引入


对于如下类

class Int
{
private:
	int value;
public:
	Int(int x = 0):value(x) {} //构造函数
	Int(const Int & it):value(it.value) {} //拷贝构造函数
	~Int() {} //析构函数
};

如果想在主函数实现Int类型的相加,则需要在类里定义一个Add函数:

int main()
{
	Int a(10),b(10);
	Int c;
	c=a.Add(b);
}

Add函数的功能相当于c.value = a.value + b.value
Add函数如下:(类的成员函数)

//类的成员函数
Int Add(Int x)
{
	int val = this->value + x.value;
	Int tmp(val);
	return tmp;
}

对于如上Add函数,C++编译器在编译时会作如下改写, (加this指针)

//类的成员函数
Int Add(Int * const this, Int x)
{
	int val = this->value + x.value;
	Int tmp(val);
	return tmp;
}

在主函数调用中,c=a.Add(b)会被改写为:

c = Add(&a,b);

该Add函数可以优化,在如上实现的Add函数里,创建了三个对象,如下图示:
在这里插入图片描述
为了防止创建这么多的对象,我们可以做如下优化:(传引用,直接返回)
优化后的Add函数

//类的成员函数
Int Add(Int & x)
{
	return Int(this->value + x.value);
}

这样return时候会直接把对象建立在临时空间,就只创建一个对象
在这里插入图片描述
接下来我们又注意到,Add函数并不改变x与当前对象的状态,即x.value和this->value都是不希望被改变的,因此要用const去修饰Add的参数

//类的成员函数
Int Add(const Int & x) const
{
	return Int(this->value + x.value);
}

第一个const修饰&x, 第二个const修饰该方法是一个常方法,修饰的是*this
在这里插入图片描述
这样,在Add函数里若出现修改x.value或this->value的情况,就会直接报错,Add函数就比较完美的实现了:

//类的成员函数
Int Add(const Int & x) const
{
	return Int(this->value + x.value);
}

以上就实现了两个变量进行相加的函数Add, 但我们知道内置类型使用 ’ + ’ 进行相加,那能不能把Add直接换成+号呢?即想让+号变成个函数名,但是可惜的是,C++遵循了C的要求,运算符不能作为函数名,C++给出了解决办法:在运算符前面加上operator关键字,opertaor与运算符一起使用构成一个函数名,这样就间接地实现了让运算符作为函数名去使用的功能, 这就是运算符的重载

//类的成员函数
Int operator+(const Int & x) const
{
	return Int(this->value + x.value);
}

运算符的重载


运算符的重载实际上是一种特殊的函数重载,必须定义一个函数,并告诉C++编译器,当遇到该重载的运算符时调用此函数。这个函数叫做运算符重载函数(通常为类的成员函数)

类外定义运算符重载函数的一般格式:

返回值类型  所属类::operator运算符(参数表)
{ 函数体 }

operator是关键字,它与重载的运算符一起构成函数名,因重载函数函数名的特殊性,C++编译器可以将这类函数识别出来

例如:

//类的成员函数
Int operator+(const Int & x) const
{
	return Int(this->value + x.value);
}

在主函数里:

int main()
{
	Int a(10),b(20);
	Int c;
	c = a+b;
}

c = a + b;就等价于c = a.operator+(b);编译时会改写成:c = operator+(&a, b);

明白了加法的运算符重载以后,我们可以很容易地写出下面一系列运算符的重载

减法运算符的重载:

//类的成员函数
Int operator-(const Int & x)const
{
	int val = this->value - x.value;
	return Int(val);
}

乘法运算符的重载:

//类的成员函数
Int operator*(const Int &x)const
{
	int val = this->value * x.value;
	return Int(val);
}

除法运算符的重载:

//类的成员函数
Int operator/(const Int &x)const
{
	if(x.value == 0)	exit(EXIT_FAILUE);
	int val = this->value/x.value;
	return Int(val);
}

以上运算符左右都是同类型对象,那如果左右是不同种类型,运算符的重载又该如何写呢?例对如下加法,是自定义类型 + 整型

int main()
{
	Int a(10),b(20),c;
	int x = 10;
	c = a + x;// 自定义类型  +  整型
	//c = a + x; c = a.operator+(x); 编译器改写:c = a.opeartor(&a, x);
}

它的运算符重载函数应该如下:

//类的成员函数
Int operator+(const int x) const
{
	int val = this->value + x;
	return Int(val);
}

注意:函数参数是内置类型时不加引用比较好,因为引用的本质是指针,编译器编译的时候引用是用指针解释的,用指针对内存访问时会访问两次(读地址,读地址里的值),而不加引用访问内置类型时只访问一次内存,所以不加引用比较好。而若是自己定义的类型,如果不加引用,形参实参结合就会调动拷贝构造函数构建对象,等到函数结束,又要调动析构函数析构对象,所以自定义类型加引用好

其实在前面我们已经实现了Int类对象与对象的相加,那么对象与整型相加可以化为对象与对象相加,就是用那个整型作为构造函数的参数去构造一个对象,所以自定义类型 + 整型的构造函数还可以这样写:

//类的成员函数
Int operator+(const int x)  //自定义型 + 整型
{
	return *this + Int(x);
}

若是如下情况:整型 + 自定义类型

int main()
{
	Int a(10),b(20),c;
	int x = 10;
	c = x + a;// 整型  +  自定义型
}

如果把x和a都作为参数传给重载函数,则会出现参数过多的错误,因为重载函数作为类的成员函数还隐含一个this指针,这样加法就有三个参数了,而+是个双目运算符,所以不能这样做!

在这里插入图片描述

可以把重载函数放在类外,使之成为一个全局函数,这样它就没有this指针了,但是全局函数又不能访问类的私有数据成员,因此不能直接x + it.value,但是我们之前已经实现了自定义类型 + 整型, 所以全局函数只需要改变一下+号两边位置即可,如下:

//全局函数
Int operator+(const int x, const Int &it)
{
	return it + x;
}

当在主函数中出现整型 + 自定义的Int类型时,调动的是全局上的operator+函数,该函数把这两个类型的相加转换为自定义类型 + 整型,从而再去调动类里面的自定义类型 + 整型的重载函数

运算符经重载后有两种使用方式:例如对于c = x + a;

  1. 运算符加:c = x + a;
  2. 函数加:c = operator+(x,a);

这两种调动方式是等价的

赋值语句的重载

//类的成员函数
void operator=(const Int &it) //不能定义为常方法,赋值语句要修改当前对象的状态
{
	this->value = it.value;
}

上述代码虽然可以实现简单的赋值,但不能实现类似c=a=b这样的连续赋值,而我们内置类型是可以的,因此要改进上述代码,使之也可实现连续赋值

拆解c=a=b, 如下图,可以知道赋值号的重载函数要返回当前对象
在这里插入图片描述

所以修改后的代码如下:

//类的成员函数
Int operator=(const Int & it)
{
	this->value = it.value;
	return *this;
}

这里可以用引用返回,因为this的生存期不受该重载函数的影响,所以可以用引用返回,这样好处是return时候不会构建一个临时对象了

//类的成员函数
Int & operator=(const Int & it)
{
	this->value = it.value;
	return *this;
}

如果要能实现c=c这样的情况,则可以再加一个if判断

//类的成员函数
Int & operator=(const Int & it)
{
	if(this != &it)
	{	
		this->value = it.value;	
	}
	return *this;
}

扩充:以值返回和以引用返回
对于如下两个函数,fun1以传值返回,fun2以引用返回


Object fun1(Object & it)//以值返回
{
	return it;
}
Object & fun2(Object & it)//以引用返回
{
	return it;
}
int main()
{
	Object obj1(10);
	Object obj2(20);
	Object obj3,obj4;
	
	obj3 = fun1(obj1);
	obj4 = fun2(obj2);
}

在主函数中,调动fun1,fun1return时候,fun1函数生存期结束,会构建一个临时对象返回给主函数;而若是调动fun2,以传引用方式返回,编译器就知道it就是obj2的别名,会直接把obj2返回
即传值返回需要构建一个临时对象作为过渡,而引用返回不会构建临时对象作为过渡

重载++、–前置后置的问题


前置++的格式为:

返回类型  所属类::operator++()
{ 函数体 }

后置++的格式为:

返回类型  所属类::operator++(int)
{ 函数体 }

后置++中的参数int仅用作区分,并无实际意义,可以给变量名,也可以不给变量名只写个int

例如对于如下Money类型++运算符的重载:

class Money
{
	int yuan,jiao;//1元等于10角
public:
	Money operator++();//前置++
	Money operator++(int);//后置++
}

前置++,先加再用

Money & Money::operator++()
{
	Jiao++;
	if(Jiao>=10)//10角等于1元
	{
		Yuan += 1;
		Jiao -= 10;
	}
	return *this;
}

后置++,先用再加

Money Money::operator++(int)//int区分这是后置++
{
	Money temp = *this;
	Jiao++;
	if(Jiao>=10)
	{
		Yuan+=1;
		Jiao -= 10;
	}
	return temp;
}

后置++中,由于要返回对象未加之前的值,所以用一个临时对象保存原始对象,然后再对对象进行++运算,最后返回对象的原始值。后置++不能以引用返回,因为返回的是局部对象,函数结束局部对象就被销毁

不允许重载的运算符


C++允许重载的运算符有42个,如下表:
在这里插入图片描述

C++中不可以重载的运算符有五个,如下表:
在这里插入图片描述

重载运算符的限制


  • 不可臆造新的运算符,只能对C++自身提供的运算符进行重载
  • 不能改变运算符原有的优先级、结合性和语法结构,不能改变运算符操作数的个数
  • 重载运算符含义必须清楚,不能有二义性
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值