【面向对象】运算符重载、友元函数

来源:互联网 发布:win7网络位置类型设置 编辑:程序博客网 时间:2024/06/05 11:30

如果今天早上在Priggs账户上花费了2小时35分钟,下午又花费了2小时40分钟,则总共花了多少时间呢?这个示例与加法概念很吻合,但要相加的单位(小时与分钟的混合)与内置类型不匹配。我们介绍一种解决办法——运算符重载。

先来看如下Time类的声明:

#ifndef MYTIME_H_#define MYTIME_H_#include <iostream>using namespace std; 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 operator + (const Time & t) const; Time operator - (const Time & t) const; Time operator * (double mult) const; friend Time operator * (double m, const Time & t) { return t * m; }friend ostream & operator << (ostream & os, const Time & t); }; #endif
下面的代码是对Time中方法的实现:

#include "mytime.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::operator + (const Time & t) const{Time sum; sum.minutes = minutes + t.minutes; sum.hours = hours + t.hours + sum.minutes / 60;sum.minutes %= 60;  }Time Time::operator - (const Time & t) const{Time diff; int tot1 = minutes + 60 * hours; int tot2 = t.minutes + 60 * t.minutes; diff.hours = (tot1 - tot2) / 60; diff.minutes = (tot1 - tot2) % 60; return diff; }Time Time::operator * (double mult) const {Time result; long totalminutes = hours * mult * 60 + minutes * 60; result.hours = totalminutes / 60; result.minutes = totalminutes % 60; return result; }ostream & operator << (ostream & os, const Time & t){os << t.hours << " hours, " << t.minutes << " minutes."; return os; }
我们对以上程序作如下说明:

1. 运算符重载限制

多数C++运算符都可以用如上的方式进行重载。重载的运算符不必是成员函数(有些例外情况),但必须至少有一个操作数是用户定义的类型。下面详细介绍C++对用户定义的运算符重载的限制。

(1) 重载后的运算符必须至少有一个操作数是用户定义的类型,这将防止用户为标准类型重载运算符。因此,不能将减法运算符(-)重载为计算两个double值的和,而不是他们的差。虽然这种限制将对创造性有所影响,但可以确保程序正常运行。

(2) 使用运算符时不能违反运算符原来的句法规则。例如,不能将求模运算符(%)重载成使用一个操作数。同样,不能修改运算符的优先级。因此,如果将加号运算符重载成将两个类相加,则新的运算符与原来的加号具有相同的优先级。

(3) 不能定义新的运算符。例如,不能定义operator ** ()函数来表示求幂。

(4) 不能重载下面的运算符:

sizeof :sizeof运算符。

. :成员运算符。

.* :成员指针运算符。

:: :作用域解析运算符。

?: :条件运算符。

typeid :一个RTTI运算符。

const_cast :强制类型转换运算符。

dynamic_cast :强制类型转换运算符。

reinterpret_cast :强制类型转换运算符。

static_cast :强制类型转换运算符。

一言以蔽之,带点的运算符不能重载。
(5) 大多数运算符都可以通过成员或非成员函数进行重载,但下面的运算符只能通过成员函数进行重载:

= :赋值运算符。

() :函数调用运算符。

[] :下标运算符。

-> :通过指针访问类成员的运算符。

2. 友元函数

在上面的程序中,如果我们不定义函数friend Time operator * (double m, const Time & t);那么,我们进行计算A = B * 2.75;将被转换为A = B.operator(2.75);但是语句A = 2.75 * B呢?从概念上讲,这两个表达式计算结果是相同的,但2.75*B不对应于成员函数,因为2.75不是Time类型的对象。记住,左侧的操作数应是调用对象,但2.75不是对象。因此,编译器不能使用成员函数调用来替换该表达式。

解决这一问题的一种方法是告知用户不能写成2.75*B,这是一种对服务器友好-客户警惕的(server-friendly, client-beware)解决方案。另一种解决方法是使用非成员函数(记住,大多数运算符都可以通过成员函数来重载)。

使用非成员函数可以按所需的顺序获得操作数(先是double,然后是Time),但这引发了一个新问题:非成员函数不能直接访问类的私有数据,至少常规的非成员函数不能访问。然而,有一类特殊的非成员函数可以访问类的私有成员,它们被称为友元函数。

(1) 创建友元

创建友元函数的第一步是将其原型放在类声明中,并在原型声明前加上关键字friend:

friend Time operator * (double m, const Time & t);  // goes in class declaration
该原型意味着下面两点:

虽然operator*()函数是在类声明中声明的,但它不是成员函数,因此不能使用成员运算符来调用;

虽然operator*()函数不是成员函数,但它与成员函数的访问权限相同。

第二步是编写函数定义。因为它不是成员函数,所以不要使用Time::限定符。另外,不要在定义中使用关键字friend。

(2) 友元是否有悖于OOP

乍一看,您可能会认为友元违反了OOP数据隐藏的原则,因为友元机制允许非成员访问私有数据。然而,这个观点太片面了。相反,应将友元函数看作类的扩展接口的组成部分。例如,从概念上看,double乘以Time和Time乘以double是完全相同的。也就是说,前一个要求有友元函数,后一个使用成员函数,这是C++句法的结果,而不是概念上的差别。通过使用友元函数和类方法,可以用同一个用户接口表达这两种操作。另外请记住,只有类声明可以决定哪一个函数是友元,因此类声明仍然控制了哪些函数可以访问私有数据。总之,类方法和友元只是表达类接口的两种不同机制。

(3) 常用的友元:重载<<运算符

要使用Time类知道使用cout,必须使用友元函数。这是什么原因呢?因为下面这样的语句使用两个对象,其中第一个是ostream类对象(cout):

cout << trip; 
如果使用一个Time成员函数来重载<<,Time对象将是第一个操作数,就像使用成员函数重载*运算符那样。这意味着必须这样使用<<:

trip << cout;
这样会令人迷惑。

0 0