第14章 运算符重载
第14章 在对象中使用运算符——运算符重载
在数学运算中有+、-、*、/等运算符,那么在 C++中,对象之间可不可以使用这些运算符呢?当然可以,在对象中使用运算符称为运算符重载,这样做可以使程序更加简单易懂,思路更加清晰,可读性、可维护性和可移植性等性能更好。
C++在运算符重载方面的高超技艺是很精彩的,请慢慢欣赏吧。
14.1 计算全班所有科目的总平均分的例题详解
【例 14-1】假设已知全班各个单科的平均分,请计算全班所有科目的总平均分,要求在编程中使用运算符重载。
1. 用心分析人想通,机随人行写提纲
对于此题,人的做法是:将全班各个单科的平均分相加,然后除以科目数,就可以得到全班所有科目的总平均分了。
2. 对号入座写文章,头脑运行改文章
按提纲和运算符重载的知识可知,该题的运算有两个:一个是加法,另一个是除法。暂且设该题只对加法进行运算符重载,则要设计一个全班各个单科的平均分的类,然后,在主程序中有多少科目就声明多少个这种类对象,对若干个对象使用运算符重载相加,再求平均分并打印输出结果。
在类的设计中,应配备符合上述要求的成员函数和数据成员,需要的数据成员有:全班各个单科的平均分 ave 和全班的所有科目数 s,假设科目数 s 为 2。另外,将此题的构造函数设计成带参数的。
程序代码如下:
//ex14-1-1.cpp
#include<iostream>
using namespace std;
class Ave//声明全班各个单科的平均分类
protected:
double ave;//全班各个单科的平均分
int s;//科目数
public:
Ave(double ave1,int s1);//声明类 Ave 的带参数的构造函数
Ave avevalue(Ave b);//声明求平均分成员函数
Ave operator+(Ave &a);//声明运算符重载“+”成员函数,用不用引用均可
void display(Ave c);//声明打印输出成员函数
Ave::Ave(double ave1,int s1)//定义带参数的构造函数
{
ave=ave1;//用形参初始化平均分和科目数
s=s1;
}
Ave Ave::operator+(Ave &a)//定义运算符重载“+”作为成员函数
{
Ave a1(0,2);//为返回运算结果,声明一个 Ave 的对象 a1,
//对于此题:形参的 0 可为任意值,2 却是一定的,
//用此段或用下面的/* */注释段代替均正确
al.ave=ave+a.ave; //①某个对象的平均分+另一个对象的平均分
return a1; //②返回对象 a1
/*a.ave=ave+a.ave;//①某个对象的平均分+另一个对象的平均分
return a;//②返回对象 a*/
}
Ave Ave :: avevalue(Ave b)//定义计算全班所有科目总平均分的成员函数,
//形参是求得全班各个单科平均分的合计分的对象 b
{
b.ave=b.ave/b.s;//所有平均分的合计分/科目数=总平均分
return b;
}
void Ave::display(Ave c)//定义打印输出成员函数
{
cout<<c.ave<<endl;
}
void main()
{
Ave e1(80.6,2);//定义 3 个 Ave 类型的对象,并初始化,
//第 1 个形参是全班某科的平均分,第 2 个形参是科目数
Ave e2(87.9,2.0);
Ave e3(0,2.0);
e3=e1+e2;//两个对象相加,即全班所有单科的平均分相加为合计分
e3=e3.avevalue(e3);//调求总平均分成员函数
e3.display(e3);
}
}
3. C++帮助且裁判,人定胜机凯歌扬
调试程序并修改,上机实验记录如表14-1所示。
| 出错现象 | 出错原因 | 解决办法 |
| 'ave': cannot access protected member declared in class 'Ave' int __thiscall\nAve::avevalue(class Ave)': overloaded function differs only by return type from 'class Ave__thiscall Ave::avevalue(class Ave)' see declaration of 'avevalue' | Ave avevalue(Ave b)\n{ b.ave=b.ave/b.s;\n} 通过观察声明句,得知没加上返回类型 Ave,即把 avevalue 函数作为普通函数来定义了,则不能访问保护成员 ave | 将 Ave avevalue(Ave b)改为:\nAve::Ave avevalue(Ave b) |
| syntax error : missing '; before identifier 'avevalue' | Ave::Ave avevalue(Ave b)\n因为把作用域区分符“::”加在了两个 Ave 之间 | 将 Ave::Ave avevalue(Ave b)改为:\nAve Ave ::avevalue(Ave b)\n此错纠正\n注意:紧挨着作用域定义符::的是其所属的类型名,则第 1 个 Ave 是该函数返回的数据类型(类类型) |
| 'argument': truncation from 'const double' to 'float' | 变量从双精度型到浮点型,被截去了数据 | 把 ave 的数据类型改为双精度型,此错纠正 |
| 'avevalue': undeclared identifier | 在调用 avevalue 成员函数前,未加上对象名 | 在语句“e3=avevalue(e3);”中加上对象名 e3,即 e3=e3.avevalue(e3); |
| 'Ave::operator+': must return a value | operator+没有返回值 | 在 operator+函数成员的最后一句加上:\nreturn a.ave; |
| '__thiscall Ave::Ave (const class Ave &)': cannot convert parameter 1 from 'double' to 'const class Ave &'\nReason: cannot convert from 'double' to 'const class Ave'\nNo constructor could take the source type, or constructor overload resolution was ambiguous no legal conversion of return value to return type 'class Ave *' | 从双精度型参数转换为类 Ave 的数据 | 将 Ave Ave::operator+((Ave &a) 中原来的语句“return a.ave;”改为:\nreturn a1;\n此错纠正 |
运行结果:
84.25
结果正确。
14.2 勤奋实践,总结评分
14.2.1 习题
- 编写两个一维数组对应元素相加的程序,运用运算符重载“+”来编写。
- 编写一个检查数组下标越界的程序。
- 运用运算符重载“+”来编写角度类的对象相加。
- 编写对类的对象进行自加和自减的程序。
- 运用运算符重载“-”来编写两个复数的减法程序。
6.计算全校各班高考所有科目的总分和总平均分,并输出显示获得冠军、亚军和季军的班级的名称、总分和总平均分,请用运算符重载实现。
- 计算某工厂合格品的月总产量,请运用运算符重载编程计算并显示。
- 计算某工厂全部产品的月合格率,请运用运算符重载编程计算并显示。
9.某顾客在商场购买了几种商品,请用运算符重载编程计算并显示出该顾客应付的总金额。
10.在某次运动大赛上,要统计出分别获得金、银、铜牌最多的省队,并输出显示这些状况,请用运算符重载实现这些操作。
14.2.2 思考题
- 设计两个直角坐标类对象之间的加法和减法运算的程序。
2.请用运算符重载计算全班成绩中优秀、良好、中等、合格和不合格各档次所占的百分比,并公布出来,以促进全班同学更加努力地学习。
14.2.3 总结评分,继续提高
浏览、体验了例题、习题和思考题之后,说说自己的体会和收获,并认真总结,按表 14-2 给自己评判一下可以得多少分。
| 成绩 | A | B | C | D | 不足的原因 | 改进的方法 | 备注 |
| C++知识 | 完全理解和会用 | 基本理解和会用 | 肤浅理解和会用 | 基本不理解和不会用 | |||
| 程序原创(独立编程)的比例(%) | ≥90 | 80~89 | 60~79 | <60 |
不论得分多少,大家都要总结经验,扬长避短,勤奋努力,不断提高。
最后浏览一下本章的第4节,可以先考考自己,然后再浏览,以便使重点知识掌握得更牢固。
14.3 运算符重载知识
前面大致了解了 C++ 通过运算符重载,使对象之间的运算变得简单明了,使其真正接近于日常生活中的数学运算,那么什么时候用运算符重载,又怎样运用它呢?下面就来细细浏览吧。
14.3.1 在什么情况下用到运算符重载
当在对象之间进行+、-、*、/等运算时,就可以用运算符重载。
14.3.2 关于运算符重载
1. 运算符重载的含义是什么
对象之间使用+、-、*、/等运算符号进行运算,就是运算符重载。
所谓重载即重新定义,重新加载,所以运算符重载就是重新定义 C++ 中原有的大部分运算符。
2. 运算符重载的应用范围
从以下两方面对运算符重载的应用范围予以说明。
1)参与运算符重载的数据是类,但是,类不能直接参与任何操作,而只能由用这些类定义的对象来参与操作,也就是说,对象之间才可以应用运算符重载来进行运算。以前讲的所有数据类型,除了类之外,其余都不能单独(即没有类的对象参数加入重载)参与运算符重载,如int、double、指针、数组等数据类型均不能单独参与运算符重载。
2)可以重载哪些运算符?表 3-7 中的 C++运算符,除了成员访问运算符“..”、类的作用域区分符“::”、成员指针访问运算符“.*”、条件运算符“?:”和长度运算符 sizeof 外,其余的运算符均可以用于运算符重载。
3. 怎样声明运算符重载
声明和定义运算符重载就是声明和定义函数——运算符重载函数。
运算符重载的声明格式为:
函数返回的数据类型 operator 要重载的运算符([形参数据类型 1 形参 1,...,形参数据类型 n 形参 n]); 而函数的声明格式为:
函数返回的数据类型 函数名([形参数据类型 1 形参 1,...,形参数据类型 n 形参 n]);
比较二者,运算符重载声明与函数声明的写法基本一样,只是用 “operator 要重载的运算符” 代替了 “函数名”。
4. 怎样定义运算符重载
定义运算符重载的方法是写一个函数,将重新定义的运算写在函数中,既然是函数,那么写法基本还像以前一样,即运算符重载的定义格式为:
函数返回的数据类型 operator 要重载的运算符([形参数据类型 1 形参 1,...,形参数据类型 n 形参 n])
{
对运算符重新定义的运算
}
其中,“对运算符重新定义的运算”的具体内容包括以下两部分。
1)进行具体操作的表达式。即把原来写在程序中做这类运算的功能语句搬到此处,但参与运算的参数均是对象,或最起码有一个以上的对象再加上普通变量、常量,如例 14-1 中的句 $ ^{①} $。
2)返回运算结果的语句,因为运算完后,必须有结果,所以,必须要有此类返回语句,如例 14-1 中的句②。
返回值(运算结果)的数据类型不一定是对象,也不一定与参与运算的形参的类型相同(例 14-1 就是相同的,而例 14-2 就不相同,如 double Ave::operator/(Ave f),形参的数据类型是类 Ave,返回值则是双精度数据类型)。
以上两部分进行了运算符重载的具体运算操作,并返回了运算结果,这样就将运算符重载定义好了,以后就可以像写数学公式一样使用了。
5. 怎样使用运算符重载
使用运算符重载时首先要定义,然后才能使用。在使用时,就像日常生活中使用数学运算符一样,把对象看成数据,然后把运算写成数学公式的形式就可以了,即
存放重载运算结果的对象=某对象1 运算符 某对象2 运算符 … 某对象n;
运算符重载不能改变运算符的优先级别和结合性,也不能改变运算符操作数的个数。
【例 14-2】将例 14-1 的求平均分的成员函数用运算符重载“/”这个成员函数代替,并且该函数的返回值的数据类型与形参对象的数据类型不同,请编写程序实现。
//ex14-2-1.cpp
#include<iostream>
using namespace std;
class Ave
{
protected:
double ave;
int s;
public:
Ave(double ave1, int s1);
Ave operator+(Ave a);
double operator/(Ave f);
void display(double ave2);
};
Ave::Ave(double ave1, int s1)
{
ave=ave1;
s=s1;
}
Ave Ave::operator+(Ave a)
{
Ave a1(0,2);
a1.ave=ave+a.ave;//某个对象的平均分+另一个对象的平均分
return a1;
}
double Ave ::operator(Ave f)//运算符重载//作为一个成员函数
//且返回值与形参的数据类型不一致
{
ave=ave/f.s;
return ave;
}
void Ave ::display(double ave2)
{
cout<<ave2<<endl;
}
void main()
{
double ave1;
Ave e1(80.6,2);
Ave e2(87.9,2);
Ave e4(0,2);
Ave e3=e1+e2;
ave1=e3/e4;
e4.display(ave1);
}
调试记录如下。
运行程序,出现如下错误提示:
error C2511: 'display': overloaded member function 'void (double)' not found in 'Ave'
E:\C++\hc++\C++BOOK\十四章\ex14-2-1.cpp(5): see declaration of 'Ave'
E:\C++\hc++\C++BOOK\十四章\ex14-2-1.cpp(51): error C2664: 'display': cannot convert parameter 1
from 'double' to 'class Ave'
No constructor could take the source type, or constructor overload resolution was ambiguous
执行 cl.exe 时出错.
因为“void display(Ave c);”,改为“void display(double ave2);”。
再运行程序,又出现如下错误提示:
error LNK2001: unresolved external symbol "public: void _thiscall Ave::display(double)" (?display@Ave@@QAEXN@Z)
Debug/ex14-2-1.exe : fatal error LNK1120: 1 unresolved externals
因为 “void display(double ave2)”, 改为 “void Ave ::display(double ave2)”, 此错纠正。
运行结果:
结果正确。
14.3.3 如何运用运算符重载
掌握在什么情况下用运算符重载,并对运算符重载的知识加以运用。
【例 14-3】将例 14-1 的求平均分的成员函数用运算符重载 “/” 这个成员函数代替,并且该函数的返回值的数据类型与形参对象的数据类型相同,请编写程序实现。
程序代码如下:
//ex14-3-1.cpp
#include<iostream>
using namespace std;
class Ave
{
protected:
double ave;
int s;
public:
Ave(double ave1,int s1);
Ave operator+(Ave a);
Ave operator/(Ave f);
void display(Ave c);
};
Ave::Ave(double ave1,int s1)
{
ave=ave1;
s=s1;
}
Ave Ave::operator+(Ave a)
{
Ave a1(0,2);
al.ave=ave+a.ave;//某个对象的平均分+另一个对象的平均分 return a1;
}
Ave Ave::operator/(Ave f)//运算符重载“/”作为一个成员函数
{
f.ave=ave/f.s;
return f;
}
void Ave::display(Ave c)
{
cout<<c.ave<<endl;
}
void main()
{
Ave e1(80.6,2);
Ave e2(87.9,2);
Ave e4(0,2);
Ave e3=e1+e2;
e4=e3/e4;
e4.display(e4);
}
运行结果:
84.25
结果正确。
14.4 运算符重载的亮点
1. 在什么情况下用运算符重载的亮点
当在对象之间进行+、-、*、/等运算时,就可以用运算符重载。
2. 运算符重载具体知识的亮点
1)对象之间使用+、-、*、/等运算符号进行运算,就是运算符重载。
2)参与运算符重载的数据是类,但是,类不能直接参与任何操作,而只能由用这些类定义的对象来参与操作,也就是说,对象之间才可以应用运算符重载来进行运算,以前讲的所有数据类型,除了类之外,其余都不能单独(即没有类的对象参数加入重载)参与运算符重载。
3)声明和定义运算符重载就是声明和定义函数——运算符重载函数。
运算符重载的声明格式为:
函数返回结果的数据类型 operator 要重载的运算符([形参数据类型 1 形参 1,...,形参数据类型 n 形参 n]);
运算符重载声明与函数声明写法基本一样,只是用“operator 要重载的运算符”代替了“函数名”。
4)定义运算符重载的方法是写一个函数,将重新定义的运算写在函数中。
3. 运用运算符重载的亮点
1)掌握在什么情况下用运算符重载。
2)使用运算符重载时首先要定义,然后才能使用。在使用时,就像日常生活中使用数学运算符一样,把对象看成数据,然后把运算写成数学公式的形式就可以了,即
存放重载运算结果的对象=某对象1 运算符 某对象2 运算符 … 某对象n;
3)运算符重载不能改变运算符的优先级别和结合性,也不能改变运算符操作数的个数。