运算符重载
运算符重载是一种形式的C++多态。之前介绍过的函数重载(定义多个名称相同但特征标不同的函数)让程序员能够用同名的函数来完成相同的基本操作,即使这些操作被用于不同的数据类型。
运算符重载将重载的概念扩展到运算符上,允许赋予C++运算符多种含义。(比如将*运算符用于地址,将得到存储在这个地址中的值,但将它用于两个数字时,得到的是它们的乘积)。C++根据操作数的数目和类型来决定采用哪种操作
C++允许将运算符重载扩展到用户定义的类型。
要重载运算符,需使用被称为运算符函数的特殊函数形式,格式如下:
operator op(argument-list)
例如,operator +()重载+运算符,operator *()重载*运算符。op必须是一个有效的C++运算符,不能虚构一个新的符号
计算时间:一个运算符重载示例
如果在Priggs的账户上花费了2小时35分钟,下午又花费了2小时40分钟,则总共花了多少时间呢?这个与加法概念相似,但要相加的单位(小时与分钟的混合)与内置类型不匹配。
首先我们先使用常规方法,然后介绍如何将其转换为重载运算符
mytime0.h
#ifndef MYTIME0_H_
#define MYTIME0_H_
class Time
{
private:
int hours;
int minutes;
public:
Time();
Time(int h, int m = 0);
void AddMin(int m);
void AddHr(int h);
void Reset(int h = 0, int m = 0);
Time sum(const Time& t) const;
void Show()const;
};
#endif
Time类提供了用于调整和重新设置时间、显示时间、将两个时间相加的方法
mytime0.cpp
#include<iostream>
#include"mytime0.h"
Time::Time()
{
hours = minutes = 0;
}
Time::Time(int h, int m )
{
hours = h;
minutes = m;
}
void Time::AddMin(int m)
{
minutes += m;
hours += minutes / 60;
minutes %= 60;
}
void Time::AddHr(int h)
{
hours += h;
}
void Time::Reset(int h, int m )
{
hours = h;
minutes = m;
}
Time Time::sum(const Time& t)const
{
Time sum;
sum.minutes = minutes + t.minutes;
sum.hours = hours + t.hours + sum.minutes / 60;
sum.minutes %= 60;
return sum;
}
void Time::Show()const
{
std::cout << hours << " hours, " << minutes << " minutes";
}
不要返回指向局部变量或临时对象的引用。函数执行完毕后,局部变量和临时对象将消失,引用将指向不存在的数据
usetime0.cpp
#include<iostream>
#include"mytime0.h"
int main()
{
using std::cout;
using std::endl;
Time planning;
Time coding(2, 40);
Time fixing(5, 55);
Time total;
cout << "planning time = ";
planning.Show();
cout << endl;
cout << "coding time = ";
coding.Show();
cout << endl;
cout << "fixing time = ";
fixing.Show();
cout << endl;
total = coding.sum(fixing);
cout << "coding.Sum(fixing) = ";
total.Show();
cout << endl;
return 0;
}
程序最终的输出是
添加 加法运算符
将Time类转换为重载的加法运算符很容易,只要将sum()的名称改为operator +()即可。
像sum()一样,operator +()也是由Time对象调用的,它将第二个Time对象作为参数,并返回一个Time对象,因此可以像调用sum()那样来调用operator +()方法
total = coding.operator +(fixing);
也可以使用运算符表示法:
total = coding + fixing
这两种表示法都将调用operator +()方法。在运算符表示法中,运算符左侧的对象(这里为coding)是调用对象,运算符右边的对象(这里是fixing)是作为参数被传递的对象
operator +()函数的名称使得可以使用函数表示法或者运算符表示法来调用它。编译器将根据操作数的类型来决定如何做:
int a,b,c;
Time A,B,C;
c = a + b; //use int addition
C = A + B; //use addtion as defined for Time objects
重载限制
多数C++运算符都可以用这样的方式重载,重载的运算符不必是成员函数,但必须至少有一个操作数是用户定义的类型
1.重载后的运算符必须至少有一个操作数是用户定义的类型,这将防止用户为标准类型重载运算符
2.使用运算符时不能违反运算符原来的句法规则(例如不能将%运算符重载成使用一个操作数),同样也不能修改运算符的优先级
3.不能创建新运算符
4.不能重载下面的运算符
- sizeof:sizeof运算符
- . :成员运算符
- .* :成员指针运算符
- :: :作用域解析运算符
- ? : :条件运算符
- typeid:一个RTTI运算符
- const_cast:强制类型转换运算符
- dynamic_cast:强制类型转换运算符
- reinterpret_cast:强制类型转换运算符
- static_cast:强制类型转换运算符
表中的大多数运算符都可以通过成员或非成员函数进行重载,但 下面的运算符只能通过成员函数进行重载
- =,赋值运算符
- (),函数调用运算符
- [ ],下标运算符
- ->,通过指针访问类成员的运算符
友元
C++控制对类对象私有部分的访问。通常共有类方法提供唯一的访问途径,但是有时候这种限制太严格,C++提供了另外一种形式的访问
友元有3种
友元函数;友元类;友元成员函数;
通过让函数成为类的友元,可以赋予该函数与类的成员函数相同的访问权限,这里先介绍友元函数
创建友元
创建友元函数的第一步是将其原型放在类声明中,并在原型声明前加上关键字friend:
friend Time operator*(double m, const Time & t);
该原型意味着:
- 虽然operator*()函数是在类声明中声名的,但它不是成员函数,因此不能使用成员运算符来调用
- 虽然operator*()函数不是成员函数,但它与成员函数的访问权限相同
第二步是编写函数定义。因为它不是成员函数,所以不要使用Time::限定符,另外不要在定义中使用关键字friend,定义如下:
Time operator*(double m ,const Time & t)
{
Time result;
long totalminutes = t.hours * m * 60 + t.minutes *m;
result.hours = totalminutes / 60;
result.minutes = totalminutes % 60;
return result;
}
总之类的友元函数是非成员函数,其访问权限与成员函数相同。
常用的友元:重载<<运算符
可对<<运算符进行重载,使之能与cout一起来显示对象的内容
1.<<的第一种重载版本
要是Time类知道使用cout,必须使用友元函数。因为下面这样的语句使用两个对象,其中第一个是ostream类对象(cout):
cout<<trip;
如果使用一个Time成员函数来重载<<,Time对象将是第一个操作数,则必须这样使用<<:
trip<<cout;
看起来很奇怪
但通过使用友元函数,可以像下面这样重载运算符
void operator<<(ostream &os, const Time &t)
{
os<<t.hours<<"hours,"<<t.minutes<<"minutes";
}
这样就可以使用下面看起来正常的语句
cout<<trip;
新的Time类声明使operator<<()函数称为Time类的一个友元函数,但该函数不是ostream类的友元
2.<<的第二种重载版本
前面介绍的实现存在一个问题,像下面这样的语句可以正常工作
cout<<trip;
但不允许像通常的cout与<<那样
cout<<"Trip time: "<<trip<<"Tuesday"; //can't do it
所以对operator<<()函数进行修改,让它返回ostream对象的引用即可:
ostream & operator<<(ostream & os, const Time &t)
{
os<<t.hours<<"hours,"<<t.minutes<<"minutes";
return os;
}
注意返回类型是ostream&,即该函数返回ostream对象的引用
一般来说,要重载<<运算符来显示c_name的对象,可使用一个友元函数,其定义如下 :
ostream & operator<<(ostream & os, const c_name &obj)
{
os<<...;
return os;
}
重载运算符:作为成员函数还是非成员函数
对于很多运算符来说,可以选择使用成员函数或非成员函数来实现运算符重载。
例如重载+运算符需要两个操作数。对于成员函数版本来说,一个操作数通过this指针隐式地传递,另一个操作数作为函数参数显式地传递;对于友元函数版本来说,两个操作数都作为参数来传递
非成员函数重载”+“
Num operator+(const Num &m1,const Num &m2)
{
Num temp;
temp.n = m1.n + m2.n;
return temp;
}
成员函数重载”+“
Num Num::operator +(const Num &t)
{
Num temp;
temp.n = n + t.n;
return temp;
}