当前位置: 首页 > news >正文

C++ 运算符重载

为什么需要对运算符进行重载:

C++预定义中的运算符+ - * /的操作对象只局限于基本的内置数据类型(int float…),如果我们自定义了一个复数数据类型Complex comlex类有两个对象a和b,a为(3,4),b为(5,-10)。且需要求得a和b相加的值,此时c++中预定义的运算符无法对我们定义的复数类型数据ab进行运算,这个时候我们就需要对 “+” 号这个运算符进行重新定义,赋予其新的功能以满足自身需求,这个过程便称为运算符的重载。

建立一个复数类型Complex

class Complex
{
public:
	//提供应对两种不同情况的两种构造函数
	Complex();//不初始化
	Complex(double r,double i);// 初始化 结构为两个参数 复数
}

运算符重载的实质:

运算符重载的实质就是函数重载或函数多态,运算符重载是C++多态的一种形式.目的是让人能够用同名的函数来完成不同的基本操作,要重载运算符,需要使用被称为[运算符函数]的特殊函数形式

运算符重载的方法是定义一个重载运算符的函数,在需要执行被重载的运算符时,系统就自动调用该函数,以实现相应的运算.从某种程度上看,运算符重载也是函数的重载.但运算符重载的关键并不在于实现函数功能,而是由于每种运算符都有其约定俗成的含义,重载它们应是在保留运算符原有含义的基础上对运算符功能的扩展,而非改变运算符的原有含义

为什么说运算符重载也体现了多态性?
首先,各种数据类型的数据进行“ + ”、“ - ”、“ * ”、“ / ”等运算的区别在于运算过程不同,不同的数据类型对应不同的运算过程,这就恰恰体现了运算过程的多态性,也就是体现出了多态性

运算符重载函数的基本格式:

函数类型 operator 运算符名称 (形参表列)
{
对运算符的重载处理
}

例如,想将+运算符用于Complex类(复数)的加法运算,函数的原型可以是这样的:

ReturnClasstypeName operator + (const ClasstypeName & fpname);

其中:
ReturnClasstypeName: 重载函数返回值的类型说明
operator: 是c++的关键字,专门用于定义重载运算符的函数,operator +就是函数名,表示对运算符+重载

运算符重载函数是带有特殊名称的函数,这个特殊名称是由关键字operator和其后需要被重载的 运算符符号+所构成的.与其他函数一致,运算符重载函数也有一个返回类型(ReturnClasstypeName )和一个参数列表(const ClasstypeName & fpname)


声明运算符重载函数(重载运算符):

运算符的重载有两种方式
一种是 类内重载(运算符重载函数作为类的成员函数)
一种是 类外重载(运算符重载函数作为类的友元函数)


类内重载
类内重载即将运算符重载函数作为类的成员函数的运算符重载函数
如下方程序示例,在类中声明一个类内运算符重载函数(普通函数):

需在类内声明重载运算符函数:

class Complex
{
public:
	//提供应对两种不同情况的两种构造函数
	Complex();//不初始化
	Complex(double r,double i);// 初始化 结构为两个参数 复数

	//在基类中声明运算符重载函数
	
	Complex operator  +  (Complex& d);
	返回类型  关键字 重载符号 (参数列表) 

	返回类型: 重载运算符后经过自定义的算法计算后算出的返回值的类型(结果值的类型),通常为本类类型
	关键字: operator 译为"操作" 为重载运算符的关键字 
	重载符号:此符号在我们对它进行重载之后 只有在主函数中此符号两边的操作数的任意一方为自定义的类型,则调用此重载运算符
	参数列表: 参数列表中的形参为运算符的右操作数 形式为某类型的参数 一般以本类的对象作为传入参数 
	
	注意:
	//todo我们在声明Complex类的时候,对” + “运算符进行了重载,使得这个类在用户编程的时候可以完全不考虑
	//todo函数是如何实现的,直接使用" + "进行计算即可
	void print();

private:
	double real;
	double imag;
};

声明运算符重载情况下的类构造函数

//类构造函数一
Complex::Complex()//当构造类对象时未初始化 real和imag的值默认为0
{
	real = 0;
	imag = 0;
}

//类构造函数二
Complex::Complex(double r,double i)//当构造类对象时初始化参数为两个值 real和imag的值默认为初始化写入的两个参数值
{
	real = r;
	imag = i;
}

注意: 重载+运算符的目的是使用+运算符把两个Complex自定义的复数对象进行相加操作

需在类外定义 类内运算符重载函数(普通函数):

Complex Complex::operator+(Complex& d)//输入的参数就是被加数
{
	Complex c;
	c.real = real + d.real;
	c.imag = imag + d.imag;
	return c;

	//更精炼的运算符重载函数
	return Complex(real + d.real, imag + d.imag);
	此时的real和imag是 + 号左边,也就是启用这个运算符重载函数的类对象的成员参数
	d.real和d.imag是 + 号右边,被传入此重载函数的对象的成员函数
	
	//运算符重载函数有两个操作参数,但由于重载函数是Complex类中的成员函数,有一个参数是隐含着的,运算符函数是用this指针
	隐式的访问类对象的成员 显示访问如下:
	
	main函数中
	c3=c1 + c2;
	
	1.return Complex(real + c2.real, imag + c2.imag);//其中c2为传入的类对象
	2.return Complex(this->real + c2.real,this-> imag + c2.imag);
	3.return Complex(c1.real + c2.real,c1.imag + c2.imag);
}

值输出函数

void Complex::print()
{	
	cout << "(" << real << "," << imag << "i)\n";
}
print函数会根据被调用的对象的不同,根据不同对象的成员函数的成员函数值不同而进行参数值打印
例如 c3.print(); 正常情况下 c3要访问自己的两个参数real和imag应该在建立对象后写成 c3.real和c3.imag;
但由于real和imag是类的私有成员,不允许类对象直接进行访问,而类的成员函数可以访问,所以要设立一个类成员函数print(),
对每个对象的值进行类内打印显示,主函数中如果想打印对象的值,只需用对象名调用此类内打印函数即可,这便是类的多态性和封装性的体现。

调用运算符重载函数的主函数:

int main()
{
	Complex c1(3, 4), c2(5, -10), c3;
	c3 = c1 + c2;//todo此时的+运算符已经被重载 因为"+"操作符两边的操作数均为用户自己定义的类型 则进行运算符的重载
	
	//todoc1+c2,编译系统把他解释为: c1.operator+(c2) 
	//第一个对象c1调用重载运算符函数 第二个对象作为函数实参传入重载运算符函数
	//也就是说 "+"号左边是重载运算符对象本身 右边是操作数,获取两边的参数后,再根据重载的运算符的不同找到不同的重载运算符函数进行返回值的计算
	//c3的构造函数是类的第一个构造函数,是未进行初始化的对象,用于接收重载运算符函数计算后的 Complex类返回值 ,这个返回值必须是Complex类的(复数类)
	
	cout << "\n\n运算符两边操作数皆为用户自定义的类型时\n";
	cout << "c1=";
	c1.print();//调用类内参数打印函数print()打印每个对象名下的参数(real和imag)
	cout << "c2=";
	c2.print();
	cout << "c1+c2=";
	c3.print();

//todo如果重载运算符两边的操作数均不是用户自定义的类型 则此运算符不被看作重载运算符, 看作为C++的标准操作符进行计算)
	cout << "\n\n运算符两边操作数皆不为用户自定义的类型时\n";
	int a = 2,b = 3;
	int c = a + b;
	cout << c << endl;//结果为 "	5	" 
	return 0;
}

运算符重载是类内重载时(运算符重载函数作为类的普通成员函数),以上述代码为例:c1+c2相当于c1对象调用+方法并且传入的参数是c2对象

运行结果:
在这里插入图片描述


类外重载
类外重载即将运算符重载函数作为类的友元函数
友元函数不属于这个类 但是这个函数是这个类的亲戚,可以访问此类的所有访问级别的成员变量

重载为友元函数的运算符重载函数的定义格式如下:

friend 函数类型 operator 运算符名称 (形参表列)
{ 
        对运算符的重载处理
}

定义一个友元函数只要在普通函数前加一个 friend 关键字即可,注意: 友元函数的使用会破坏类的封装性,因此原则上尽量将运算符函数作为成员函数

在类中声明 类外重载函数(友元函数):

class Complex
{
public:
	Complex();
	Complex(double r,double i);
	
	todo将运算符函数声明为友元函数 

	friend Complex operator+(Complex& c, Complex& d);
	友元函数-这个函数不属于这个类 但是这个函数是这个类的亲戚,可以访问此类的所有访问级别的成员变量
	友元函数的使用会破坏类对封装,因此原则上尽量将运算符函数作为成员函数
	void print();
private:
	double real;
	double imag;
};

在类外定义 类外运算符重载函数(友元函数)

Complex operator + (Complex& c, Complex& d)
{
	return Complex(c.real + d.real, c.imag + d.imag);
}

运行结果:在这里插入图片描述
运算符重载函数什么时候声明为成员函数,什么时候声明为非成员(友元)函数

首先,我们要明白这句话:对于成员函数来说,一个操作数通过this指针隐式的传递,(即c1本身),另一个操作数作为函数的参数显示的传递(&d==c2)对于友元函数(非成员函数)来说,运算符重载函数中的两个操作数都是通过参数来传递的

当重载友元函数时,将没有隐含的参数this指针(没有类对象调用)。这样,对于双面运算符,友元函数有2个参数,对于单目运算符,友元函数有一个参数。

1.一般来说,单目运算符重载为类的成员函数双目运算符重载为类的友元函数(咳咳,一般情况下)

2.双目运算符不能将 =,(),[],->重载为类的友元函数

3.如果运算符的第一次操作数要求为隐式转换则必须为友元函数

4.当最左边的要求为类对象,而右边的是一个内置类型,则要为友元函数


可重载运算符/不可重载运算符

可重载运算符

在这里插入图片描述
注意:
等号=运算符重载,只能使用成员函数进行重载
这里我们重载=主要是进行深拷贝操作(当然这里也可以自己写一个拷贝构造函数来进行)


不可重载运算符
在这里插入图片描述


运算符重载的规则

1.为了防止用户对标准类型(int,double…)进行运算符重载,C++规定 重载后的运算符操作对象必须至少有一个是用户定义的类型(也就是说,重载运算符的操作数参数不能全部为c++的标准类型,这样约定是为了防止用户修改用于标准类型结构的运算符性质,如果重载运算符两边的操作数均不是用户自定义的类型 则此运算符不被看作重载运算符,看作C++的标准操作符进行计算)

比如说现在有两个数:int number1,int number2,那么number1+number2 求的是两个数的和。
但是如果你重载以后让着两个数相加为他们的乘积,这肯定是不合乎逻辑的
可能重载以后会有二义性,导致程序不知道该执行哪一个(是自带的的还是重载后的函数)

2.使用运算符不能违反运算符原来的句法规则,如不能将% (运算符)重载为一个操作符(&)

例如:
int index;
%index;这种是不被允许的

3.不能对操作符的语法(优先级,结合性,操作数个数,语法结构,语义) 进行颠覆

4.不能创建一个新的运算符,例如不能定义operator** (···)来表示求幂(不能重载新的自定义操作符)

5.不能进行重载的运算符:成员运算符,作用域运算符,条件运算符,sizeof运算符,typeid(一个RTTI运算符),const_cast、dynamic_cast、reinterpret_cast、static_cast强制类型转换运算符

6.大多数运算符可以通过成员函数和非成员函数(友元函数)进行重载但是下面这四种运算符只能通过成员函数进行重载
= 赋值运算符,**()函数调用运算符,[ ]下标运算符,->**通过指针访问类成员的运算符

7.重载运算符的函数不能有默认的参数(没搞明白,暂时不管)

8.重载不能改变运算符的结合性(没搞明白,暂时不管)

9.重载不能改变运算符运算对象(操作数)的个数。(比如+号有两个操作数)

10.只能为用户自定义的类型进行操作符的重载


扩展练习:

利用运算符重载进行分数有理数 + - * \的运算

class Rational
{
public:
	Rational(int num, int denom);//num =分子 denom=分目
	Rational operator + (Rational& rhs); //rhs = right hand side =操作符右手边的参数
	Rational operator - (Rational& rhs);
	Rational operator * (Rational& rhs);
	Rational operator / (Rational& rhs);

	void print();
private:
	void normralize();//对分数进行简化处理
	int numerator;//分子
	int denominator;//分母

};

Rational::Rational(int num, int denom)
{
	numerator = num;
	denominator = denom;
	normralize();
}


//todonormalize() 对分数的简化操作包括
//1.只允许分子为负数,如果分母为负数则把负数部分转移到分子部分如 -1/2 == -1/2 
//2.利用欧几里得算法(辗转求余原理)将分数进行简化 2/10	=>  1/5;
void Rational::normralize()
{
	//确保分母为正
	if (denominator < 0)
	{
		numerator = -numerator;
		denominator = -denominator;
	}

	//欧几里得算法 abs-求绝对值的函数 需要包含stdlib.h头文件
	int a = abs(numerator);
	int b = abs(denominator);

	//不断求余数 最后得到最大公约数
	while (b > 0)
	{
		int t = a % b;
		a = b;
		b = t;
	}
	numerator /= a;
	denominator /= a;
}

//todo分数的+法
//a   c    a*d    c*b	  a*d+c*b
//- + - = ----- + ---- = -----------
//b   d    b*c	b*c	     b*c
Rational Rational::operator+(Rational& rhs)
{
	int a = numerator;
	int b = denominator;
	int c = rhs.numerator;
	int d = rhs.denominator;
	
	int e = a * b + c * d;
	int f = b * d;

	return Rational(e, f);
}

//todo分数的-法
//a   c    a   -c
//- - - =  - +  -
//b   d    b	d
Rational Rational::operator-(Rational& rhs)
{
	rhs.numerator = -rhs.numerator;
	return operator+(rhs);
}

//todo分数的*法
//a   c     a*c
//- * - =  -----
//b   d    b*d
Rational Rational::operator*(Rational &rhs)
{
		int a = numerator;
		int b = denominator;
		int c = rhs.numerator;
		int d = rhs.denominator;
		
		int e = a * c;
		int f = b * d;
		return Rational(e, f);
}
//todo分数的/法
//a    c       a   d
//-  /  -   =  - *  -
//b    d      b	    c
Rational Rational::operator/(Rational &rhs)
{
	int t = rhs.numerator;
	rhs.numerator = rhs.denominator;
	rhs.denominator = t;
	return operator *(rhs);
}

void Rational::print()
{
	if (numerator % denominator == 0)
		cout << numerator / denominator;
	else
		cout << numerator << "/" << denominator;
}

int main()
{
	Rational f1(2, 16);
	Rational f2(7, 8);

	Rational res = f1 + f2;
	f1.print();
	cout << "+";
	f2.print();
	cout << "=";
	res.print();
	cout << "\n";

	res = f1 - f2;
	f1.print();
	cout << "-";
	f2.print();
	cout << "=";
	res.print();
	cout << "\n";

	res = f1 * f2;
	f1.print();
	cout << "*";
	f2.print();
	cout << "=";
	res.print();
	cout << "\n";

	res = f1/f2;
	f1.print();
	cout << "/";
	f2.print();
	cout << "=";
	res.print();
	cout << "\n";
	return 0;
}

结果:
在这里插入图片描述
笔记:

abs-求绝对值的函数 需要包含stdlib.h头文件

int a = abs(numerator);

不断求余数 最后得到最大公约数

while (b > 0)
{
	int t = a % b;
	a = b;
	b = t;
}

可以在重载运算符函数中调用重载运算符函数

Rational Rational::operator/(Rational &rhs)
{
	int t = rhs.numerator;
	rhs.numerator = rhs.denominator;
	rhs.denominator = t;
	return operator *(rhs);
}

来自:

https://blog.csdn.net/lishuzhai/article/details/50781753
https://www.runoob.com/cplusplus/cpp-overloading.html

相关文章:

  • 购买Microsoft Technet订阅,免费获得微软几乎所有的产品序列号“用于评估”,...
  • C++ 操作符重载
  • 客户端调用webservice的两种方式
  • C++ 多继承
  • redmine 主题thems-默认主题
  • C++ 虚继承
  • C++ 错误处理和调试(编写代码前的准备工作)
  • 将整型数字转换为大写汉字的自定义函数,如转换为'壹贰
  • C++ assert函数与捕获异常
  • WCF开发日志 -- OEA里面的WCF设计
  • C++ 内存分配
  • C++ 动态内存管理
  • IOS学习资源
  • C++ 从函数或方法返回动态内存(函数指针与指针函数)
  • PHP实现多web服务器共享SESSION数据-session数据写入mysql数据库
  • Java 实战开发之spring、logback配置及chrome开发神器(六)
  • Java到底能干嘛?
  • MySQL用户中的%到底包不包括localhost?
  • vue从创建到完整的饿了么(18)购物车详细信息的展示与删除
  • 机器学习中为什么要做归一化normalization
  • 七牛云 DV OV EV SSL 证书上线,限时折扣低至 6.75 折!
  • 一、python与pycharm的安装
  • 自制字幕遮挡器
  • 阿里云API、SDK和CLI应用实践方案
  • 关于Kubernetes Dashboard漏洞CVE-2018-18264的修复公告
  • 微龛半导体获数千万Pre-A轮融资,投资方为国中创投 ...
  • (3)Dubbo启动时qos-server can not bind localhost22222错误解决
  • (C语言)fgets与fputs函数详解
  • (Java实习生)每日10道面试题打卡——JavaWeb篇
  • (Redis使用系列) Springboot 使用redis的List数据结构实现简单的排队功能场景 九
  • (附源码)springboot“微印象”在线打印预约系统 毕业设计 061642
  • (附源码)springboot电竞专题网站 毕业设计 641314
  • (附源码)计算机毕业设计SSM疫情社区管理系统
  • (力扣记录)1448. 统计二叉树中好节点的数目
  • ***微信公众号支付+微信H5支付+微信扫码支付+小程序支付+APP微信支付解决方案总结...
  • .net 调用php,php 调用.net com组件 --
  • @Documented注解的作用
  • [ Linux Audio 篇 ] 音频开发入门基础知识
  • [04]Web前端进阶—JS伪数组
  • [AndroidStudio]_[初级]_[修改虚拟设备镜像文件的存放位置]
  • [BZOJ 3282] Tree 【LCT】
  • [bzoj1324]Exca王者之剑_最小割
  • [C#]扩展方法
  • [EFI]DELL XPS13 9360电脑 Hackintosh 黑苹果efi引导文件
  • [Java、Android面试]_10_Java中==与equal()方法的区别?重写equal()方法?
  • [JavaEE系列] wait(等待) 和 notify(唤醒)
  • [LeetCode] 93. Restore IP Addresses 复原IP地址
  • [LeetCode]284. Peeking Iterator(C++,类,暴力)
  • [SDOI2016]生成魔咒
  • [SpringBoot系列]消息中间件解决方案
  • [SQL开发笔记]DELETE 语句:删除表中的行
  • [TFF联邦学习]送给想要了解TFF的盆友
  • [UVA 11825] Hackers' Crackdown
  • [vim]Python编写插件学习笔记3 - 命令行参数
  • [安卓] 8、VIEW和SURFACEVIEW游戏框架