第11章 使用类

发布于 2022-03-22  52 次阅读


C++类特性丰富、复杂、功能强大。通过定义用于表示对象的数据的类型以及(通过成员函数)定义可以对数据执行的操作。
本章进一步探讨类的特征,重点是类的设计技术,而不是通用原理。
如果函数使用常规参数而不是引用,将发生什么情况呢?如果忽略了析构函数,又将发生什么情况呢?
本章首先介绍运算符重载,它允许将标准C++运算符(如=和+)用于类对象。然后介绍友元,这种C++机制使得非成员函数可以访问私有数据,最后介绍如何命令C++对类执行自动类型转换。
学习本章后,将对类构造函数和析构函数所其作用有更深入了解。还会知道开发和改进类设计时,需要执行的步骤。
file

11.1 运算符重载

使对象更美观的技术。运算符重载是一种形式的C++多态。
之前介绍过函数重载或者函数多态,旨在让能够用同名的函数来完成相同的基本操作,即使这种操作被用于不同的数据类型。
运算符重载的概念扩展到运算符上,允许赋予C++运算符多种含义。实际上,很多C++(也包括C语言)运算符以及被重载。例如,将*运算符用于地址,将得到存储在这个地址中的值;但将它用于两个数字时,得到的将是它们的乘积。C++根据操作数的数目和类型来来决定采取哪种操作。
C++允许将运算符重载扩展到用户定义的类型。例如,允许使用将两个对象相加。编译器将根据操作数的数目和类型决定使用哪种假发定义。重载运算符可使代码看起来更自然,例如,将两个数组相加是一种常见的运算。通常,需要使用下面这样的for循环来实现:

for( int i=0;i<20;i++)
    evening[i] = sam[i] + janet[i];

但在C++中,可以定义一个表示数组的类,并重载+运算符。于是便可以有这样的语句;
evening = sam + janet; //add two array object
这种简单的加法表示法隐藏了内部机理,并强调了实质,这是OOP的另一个目标。
要重载运算符,需使用被称为运算符函数的特殊形式。运算符函数的格式如下:
operatorop(argument-list)
例如,operator+()重载+运算符。operator*()重载*运算符,不能虚构一个新的符号,例如,@,因为C++中没有@运算符。然而,operator[]()函数将重载[]运算符,因为[]是数组索引运算符。例如,假设有一个Salesperson类,并为它定义了一个operator+()成员函数,以重载+运算符,以便能够将两个Salesperson对象的销售额相加。则如果district2。sid和sara都是Salesperson类对象,便可以编写这样的等式:
district2 = sid + sara;
编译器发现,操作数是Salesperson类对象,因此使用相应的运算符函数替换上述运算符;
district2 = sid.operator+(sara);
然后改函数将隐式地使用sid(因为它调用了方法),而显式的使用sara对象。来计算总和,并且返回这个值。当然最重要的是,可以使用捡便的+运算符表示法,而不必使用笨拙的函数表示法。
虽然C++对运算符重载做了一些限制,但了解重载的工作方式后,这些限制就很容易理解了。

11.2 计算时间:一个运算符重载示例

如果今天早上在Priggs的账户上花费了2小时35分钟,下午又花费了2小时40分钟。则总共花费了多少时间呢?这个歌=示例与加法概念很吻合,但要相加的单位(小时和分钟的混合)与内置类型不匹配。
前面采用定义一个travel_time结构和将这种结构相加的sum()函数来处理类似的情况,现在将其推广,采用一个使用方法类处理加法的Time类。

//mytime0.h --Time class before operator overloading
#ifndef MYTIME0_H_
#define MYTIME0_H_

class Time
{
private:
    int hour;
    int minute;
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类提供了用于调整和重新设置时间、显示时间、将两个时间相加的方法。
下述程序列出了方法定义,当总的分钟超过59时,AddMin()和Sum()方法是如何使用整数除法和求模运算符来调整minutes和hours值的,另外,由于这里只使用了iostream的cout,且只使用了一次,因此使用std::cout比导入整个名称空间更经济。

//mytime0.cpp   --implementing Time methods
#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";
}

Sum()函数的代码,参数是引用,但返回类型不是引用,将参数声明为引用的目的是为了提高效率,如果按值传递Time对象,代码的功能将相同,但传递引用,速度将更快,使用的内存将更少。
然而,返回值不能是引用。因为函数将创建一个新的Time对象(sum),来表示另外两个Time对象的和,返回对象将创建对象的副本,而调用函数可以使用它。然而,如果返回类型为Time &,则引用的将是sum对象,但由于sum对象是局部变量,在函数结束时将被删除,因此引用将指向一个不存在的对象。使用返回类型Time意味者程序将在删除sum之前构造它的拷贝,调用函数将得到该拷贝。

警告:不要返回值指向局部变量或临时对象的引用。函数执行完毕后,局部变量和临时对象将消失,引用将指向不存在的数据。
//usetime0.cpp  --using the first draft of the Time class
#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 << "fixing time = ";
    fixing.Show();
    cout << endl;

    total = coding.Sum(fixing);
    cout << "coding.sum(fixing) = ";
    total.Show();
    cout << endl;

    return 0;
}

file

11.2.1 添加加法运算符

将Time类转换为重载的加法运算符很容易,只需将Sum()的名称改为operator +()即可。这样做是对的,只要把运算符(这里为+)放到operator的后面,并将结果用作方法名即可。在这里,可以在标识符种使用字母、数组或下划线之外的其他字符。

//mytime1.h --Time class before operator overloading
#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 operator+(const Time& t) const;
    void Show() const;
};
#endif
//mytime1.cpp   --implementing Time methods
#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::operator+(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";
}

和Sum()一样,operator+()也是由Time对象调用的,它将第二个Time对象作为参数,并返回一个Time对象。因此,可以像调用Sum()那用来调用operator+()方法:

total = coding.operator+(fixing);   //function notaition
//但将该方法命令为operator+()后,也可以使用运算符表示法:
total = coding + fixing;    //operator notation
//这两种表示法都将调用operator+()方法。注意,在运算符表达式中,运算符左侧的对象(coding)是调用对象,运算符右边的对象是作为参数被传递的对象。

file

总之,operator+()函数的名称使得可以使用函数表示法或运算符表示法来调用它,编译器将根据操作数的类型来确定如何做:

int a,b,c;
Time A,B,C;
c = a + b;  //use int additon
C = A + B;  //use addition as defined for Time objects
//如果t1、t2、t3和t4都是Time对象
t4 = t1 + t2 + t3;  //?
//上述语句会被转换成为:
t4 = t1.operator+(t2+t3);
//然后,函数参数本身被转换成为一个函数调用:
t4 = t1.operator(t2.operator(t3));  //Yes
//上述语句合法吗?是的,函数调用t3.operator+(t3)返回一个Time对象,后者是t2和t3的和,然而,该对象成为函数调用t1.opearator+()的参数,该调用返回t1与表示t2和t3之和的Time对象的和。总之,最后的返回值为t1、t2、和t3之和。

11.2.2 重载限制

多数C++运算符都可用这样的方式及重载。重载的运算符不必是成员函数,但必须值少有一个操作数是用户定义的类型。

  • 1.重载后的运算符必须至少有一个操作数是用户定义的类型,这将防止用户为标准类型重载运算符。因此,不能将减法运算符(-)重载为计算两个double值的和,而不是它们的差。虽然这种限制将对创造性有所影响,但可以确保程序正常运行。
  • 2.使用运算符时不能违反运算符原来的句法规则。例如,不能将求模运算符(%)重载成为使用一个操作数:
    int x;
    Time shiva;
    % x;    //invalid for modulus operator
    % shiva;    //invalod for overloaded operator

    同样,不能修改运算符的优先级。因此,如果将加号运算符重载成将两个类相加,则新的运算符与原来的加号具有相同的优先级。

  • 3.不能创建新运算符。例如,不能定义operator **()函数来表示求幂。
  • 4.不能重载下面的运算符。
    file
    然而,下表中的运算符都可以被重载
  • 5.表中大多数运算符都可以通过成员或非成员函数进行重载,但虾米那的运算符只能通过成员函数进行重载。
    file

file

file

11.2.3 其他重载运算符

还有一些其他的操作对Time类来说是有意义的。例如,可能要将两个时间相减或将时间乘以一个因子,这需要重载减法和乘法运算符。

//mytime2.h --Time class before operator overloading
#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 operator+(const Time &t) const;
    Time operator-(const Time& t) const;
    Time operator*(double n) const;
    void Show() const;
};
#endif
//mytime2.cpp   --implementing Time methods
#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::operator+(const Time &t) const
{
    Time sum;
    sum.minutes = minutes + t.minutes;
    sum.hours = hours + t.hours + sum.minutes / 60;
    sum.minutes %= 60;
    return sum;
}

Time Time::operator-(const Time& t) const
{
    Time diff;
    int tot1, tot2;
    tot1 = t.minutes + 60 * t.hours;
    tot2 = minutes + 60 * hours;
    diff.hours = (tot2 - tot1) / 60;
    diff.minutes = (tot2 - tot1) % 60;
    return diff;
}

Time Time::operator*(double mult) const
{
    Time result;
    long totalminutes = hours * mult * 60 + minutes * mult;
    result.hours = totalminutes / 60;
    result.minutes = totalminutes % 60;
    return result;
}
void Time::Show() const
{
    std::cout << hours << " hours, " << minutes << " minutes";
}
//usetime2.cpp  --using the first draft of the Time class
#include <iostream>
#include "mytime0.h"
int main()
{
    using std::cout;
    using std::endl;
    Time weeding(4,35);
    Time waxing(2, 47);
    Time diff;
    Time total;
    Time adjusted;

    cout << "Weeding time = ";
    weeding.Show();
    cout << endl;

    cout << "waxing time = ";
    waxing.Show();
    cout << endl;

    cout << "total work time = ";
    total = weeding + waxing;
    total.Show();
    cout << endl;

    diff = weeding - waxing;
    cout << "weedomg time  - waxing time  = ";
    diff.Show();
    cout << endl;

    adjusted = total * 1.5;
    cout << "adjusted work time = ";
    adjusted.Show();
    cout << endl;

    return 0;
}

file

11.3 友元

C++控制对类对象私有部分的访问。通常,公有类方法提供唯一的访问途径,但是有时候这种限制太严格,以至于不适合特定的编程问题。在这种情况下,C++提供了另外一种形式的访问权限:友元。
友元有3种:

  • 友元函数;
  • 友元类;
  • 友元成员函数;
    通过让函数成为类的友元,可以赋予该函数与类的成员函数相同的访问权限。下面介绍友元函数。
    介绍如何称为友元前,先介绍为何需要友元。在为类重载二元运算符时(带两个参数的运算符)常常需要友元。将Time对象乘以实数就术语这种情况。
    在前面的Time类示例中,重载的乘法运算符与其他两种重载运算符的差别在于,它使用了两种不同的类型,也就是说,加法和剑法运算符都结合两个Time值,而乘法运算符将一个Time值和一个double值结合在一起,这限制了该运算符的使用方式。左侧的操作数是调用对象,也就是说,下面的语句:
    A = B * 2.75;
    将被转换为下面的成员函数调用:
    A = B.operator*(2.75);
    但下面的语句又如何呢?
    A = 2.75 * B;
    从概念上说,2.75*B应与B*2.75相同,但第一个表达式不对于于成员函数,因为2.75不是Time类的对象,左侧的操作数应是调用对象,但2.75不是对象,因此,编译器不能使用成员函数来替换该表达式。
    解决这个难题的一种方式是,告知每个人,只能按照B*2.75这种格式编写,不能写成2.75*B.这是一种对服务器友好-客户警惕(server-friendly,client-beware)解决方案。于OOP无关。
    然而,还有一种解决方式——非成员函数(记住,大多数运算符都可以通过成员或非成员函数来重载)。非成员函数不是由对象调用的,它使用的所有值(包括对象)都是显式参数。这样,编译器能够将下面的表达式:
    A = 2.75 * B;
    与下面的非成员函数调用匹配;
    A = operator*(2.75,B);
    该函数的原型如下:
    Time operator *(double m,const Time &t);
    对于非成员重载运算符来说,运算符表达式左边的操作数对应于运算符函数的第一个参数,运算符表达式右边的操作数对于运算符的第二个参数。而原来的成员函数则按相反的顺序处理操作数,也就是说,double值乘以Time值。
    使用非成员函数可以按所需的顺序获得操作数(先是double,然后是Time),但引发了一个新问题:非成员函数必能直接访问类的私有数据,至少常规非成员函数不能访问。然而,有一类特殊的非成员函数可以访问类的私有成员,它们被称为友元函数。

    11.3.1 创建友元

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

    friend Time operator*(double m,const Time &t);  //goes in class declaration

    该原型意味者下面两点:

虽然operator*()函数是在类声明中声明的,但它不是成员函数,因此不能使用成员运算符来调用:
虽然operator*()函数不是成员函数,但它与成员函数的访问权限相同。

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

Time operator*(double mult,const Time &t)
{
    Time result;
    long totalminutes = t.hours*mult*60 + t.minutes*mult;
    result.hour = totalminutes/60;
    result.minutes=totalminutes%60;
    return result;
}

有了上述的声明和定义后,下面的语句:

A = 2.75 * B;
//将转换为如下语句,从而调用刚才定义的非成员友元函数;
A = operator*(2.75,B)
//类的友元函数是非成员函数,其访问权限和成员函数相同
友元是否有悖于OOP
你可能会认为友元违反了OOP数据隐藏的原则,因为友元机制允许非成员函数访问私有数据。
然而,这个观点太片面了。相反,应将友元函数看作类的扩展接口的组成部分。例如,从概念上看,double乘以Time和Time乘以double是完全相同的。也就是说,前一个要求有友元函数后,后一个使用成员函数,这是C++语法的结果,而不是概念上的差别。通过使用友元函数和类方法,可以用同一个用户接口表达这两种操作。
另外,只有声明可以决定哪一个函数是友元,因此类声明任然控制了哪些函数可以访问私有数据。总之,类方法和友元只是表达类接口的两种不同机制。

实际上,按下面的方式定义进行修改,可以将这个友元函数编写为非友元函数:

Time operator*(double m,const Time &t)
{
    return t * m;   //use t.operator*(m)
}

原来的版本显式地访问L.minutes和t.hours,所以它必须是友元,这个版本将Time对象t作为一个整体使用,让成员函数来处理私有值,因此不必是友元。然而,将该版本作为作为友元也是一个好主意。最重要的是,它将该作为正式类接口的组成部分,其次,如果以后发现需要函数直接访问私有数据免责只要修改函数定义即可,而不必修改类原型。

如果要为类重载运算符,并将非类的项作为第一个操作数,则可以用友元函数来反转操作数的顺序。

11.3.2 常用的友元:重载<<运算符

一个很有用的类特征是,可以对<<运算符进行重载。使之能与cout一起来显示对象的内容。
与前面示例相比,这种重载要复杂些。
假设trip是一个Time对象,为显示Time的值,前面使用的是Show()。然而,如果可以像下面这样操作更好:

cout << trip; //make cout recognize Time class?

之所以可以这样做,是因为<<是可以被重载的C++运算符之一。实际上,它已经被重载很多次了。最初,<<运算符是C和C++的位运算符,将值中的位左移。ostream类对该运算符进行了重载,将其转换称为一个输出工具。
cout是一个ostream对象,它是智能的,能够识别所有C++基本类型。这是因为对于每种基本类型,ostream类声明中都包含了相对应的重载的ioerator<<()定义。也就是说,一个定义使用int参数,一个定义使用double参数。等等。因此,要使cout能够识别Time对象,一种方法是将一个新的函数运算定义添加到ostream类声明中。但修改iostream文件是个危险是注意,这样做会在标准接口上浪费时间。相反,通过Time类声明来让Time类知道如何使用cout。

1. <<的第一种重载版本

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

cout << trip;
//如果使用一个Time成员函数来重载<<,Time对象将是第一个操作数,就像使用成员函数重载*运算符那样,这意味着必须这样使用<<
trip << cout; //if operator() were a Time member function
//这样会令人迷惑,但通过使用友元函数,可以像下面这样重载运算符。
void operator<<(ostream &os, const Time &t)
{
    os<< t.hours << " hours," << t.minutes << " minutes";
}
//这样可以使用下面的语句;
cout << trip;

file

调用cout<< trip应使用cout对象本身,而不是它的拷贝,因此该函数按引用(而不是按值)来传递该对象,这样,表达式cout<< trip将导致os曾为cout的一个别名;而表达式cerr<< trip将导致os车门华为哦cerr的一个别名。
Time对象可以按值或者按应用来传递,因为这两种形式都使用函数能够使用对象的值。按引用传递使用的内存和时间都比按值传递少。

2.<<的第二重载版本

前面介绍的实现存在一个问题。像下面这样的语句可以正常工作:
cout << trip;
但这种实现不允许像通常那样重新定义的<< 运算符与cout一起使用;

cout << "Trip time: " << trip << "(Tuesday)\n";   //can't do

要理解这样做不可行的原因以及必须如何才能使其可行,首先需要了解关于cout操作的一点知识。

int x = 5;
int y = 8;
cout << x << y;
//C++从左至右读取输出语句,意味者它等同于:
(cout << x) << y;

正如iostream中定义的那样,<< 运算符要求左边是一个ostream对象,显然,因为cout是ostream对象,所以表达式cout<< x满足这种需求。然而,因为表达式cout<< x位于<< y的左侧,所以输出语句也要求该表达式是一个ostream类型的对象。因此,ostream类将operator<<()函数实现为返回一个指向ostream对象的引用。具体地说,它返回一个指向调用对象(cout)的引用。因此表达式(cout << x)本身就是ostream对象cout,从而可以位于<< 运算符的左侧。
可以对友元函数采用相同的方法,只要修改operator<<()函数,让它返回ostream对象的引用即可:

ostream &operator<<(ostream &os,cout Time &t)
{
    os << t.hours << " hours." << t.minutes << " minutes";
    return os;
}

注意,返回类型是ostream &。这意味着该函数返回ostream对象的引用,因为函数开始执行时,程序传递了一个对象引用给它,这样做的最终结果是,函数的返回值就是传递给它的对象。也就是说,下面的语句:

cout << trip;
//将被转换为下面的调用;
operator << (cout,trip);
//而调用返回cout对象。因此,下面的语句可以正常工作:
cout << "Trio time: " << trip << " (Tuesday)\n";  //can do
//将这条语句拆分
//首先下面的代码调用ostream中的<<定义,它显示了字符串并返回cout对象:
cout << "Trip time: "
//执行结果显示字符串并返回cout:
cout << trip <<" (Tuesday)\n";
//接下来,程序使用<<的Time声明显示trip值,并再次返回cout对象。
cout << " (Tuesday)\n";
//现在,程序使用ostream中用于字符串<< 定义来显示最后一个字符串,并结束允许。
//有趣的是,这个operator<<()版本还可用于将输出写入到文件中;
#include<fstream>
...
ofstream fout;
fout.open("xxx.txt");
Time trip(12,40);
fout << trip;
//其中最后一句被转换为这样:
operator << (fout,trip);

类继承属性让ostream引用能够指向ostream对象和ofstream对象。
提示:一般来说,要重载<<运算符来显示c_name的对象,可使用一个友元函数,其定义如下:

ostream &operator<<(ostream &os,const c_nmae &obj)
{
    os << ...;    //display object contents
    return os;
}

警告:只有在类声明中的原型才能使用friend关键字。除非函数定义也是原型,否则不能在函数中使用该关键字。

//mytime3.h --Time class before operator overloading
#ifndef MYTIME0_H_
#define MYTIME0_H_
#include<iostream>

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 n) const;
    friend Time operator*(double m, const Time& t)
    {
        return t * m;
    }
    friend std::ostream & operator<<(std::ostream& os, const Time& t);
    void Show() const;
};
#endif
//mytime3.cpp   --implementing Time methods
#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::operator+(const Time &t) const
{
    Time sum;
    sum.minutes = minutes + t.minutes;
    sum.hours = hours + t.hours + sum.minutes / 60;
    sum.minutes %= 60;
    return sum;
}

Time Time::operator-(const Time& t) const
{
    Time diff;
    int tot1, tot2;
    tot1 = t.minutes + 60 * t.hours;
    tot2 = minutes + 60 * hours;
    diff.hours = (tot2 - tot1) / 60;
    diff.minutes = (tot2 - tot1) % 60;
    return diff;
}

Time Time::operator*(double mult) const
{
    Time result;
    long totalminutes = hours * mult * 60 + minutes * mult;
    result.hours = totalminutes / 60;
    result.minutes = totalminutes % 60;
    return result;
}

std::ostream &operator<<(std::ostream &os, const Time& t)
{
    os << t.hours << " hours." << t.minutes << " minutes";
    return os;
}

void Time::Show() const
{
    std::cout << hours << " hours, " << minutes << " minutes";
}
//usetime3.cpp  --using the first draft of the Time class
#include <iostream>
#include "mytime0.h"
int main()
{
    using std::cout;
    using std::endl;
    Time aida(3, 35);
    Time tosca(2, 48);
    Time temp;

    cout << "Aida and Tosca:\n";
    cout << aida << "; " << tosca << endl;
    temp = aida + tosca;
    cout << " + " << temp << endl;
    temp = aida * 1.17;
    cout << "aida * 1.17 =" << temp << endl;
    cout << "10 * Tosca:" << 10.0 * tosca << endl;

    return 0;
}

file

11.4 重载运算符:作为成员函数还是非成员函数

对于很多运算符来说,可以选择使用成员函数或非成员函数来实现运算符重载。一般来说,非成员函数应是友元函数,这样它才能直接访问类的私有数据。例如,Time类的加法运算符在Time类声明中的原型如下:

Time operator+(const Time &t) const;    //member version
//这个类也可以使用下面的原型:
//nonmember version
friend Time operator+(const Time &t1,const Time &t2);
//加法运算符需要两个操作数,对于成员函数版本来说,一个操作数通过this指针隐式地传递;另一个操作数作为函数参数显式地传递;对于友元版本来说,两个操作数都作为参数来传递。

注意:非成员版本的重载运算符函数所需的形参数目与运算符使用的操作数目相同,而成员版本所需的参数数目少一个,因为其中的一个操作就是被隐式地传递的调用对象。
这两个原型都与表达式T2+T3匹配。其中T2和T3都是Time对象,也就是说,编译器将下面的语句:
T1 = T2 + T3;
转换为下面两个任何一个:
T1 = t2.operator+(T3); //MEMBER FUNCTION
T1 = operator+(T2.T3); //NINMEMBER FUNCTION
记住,在定义运算符时,必须选择其中的一种格式,而不能同时选择这两种格式。因为这两种格式都与同一个表达式匹配,同时定义这两种格式将被视为二义性错误,导致编译错误。
哪种格式是最好呢?对于某些运算符来说,成员函数是唯一合法的选择,在其他情况下,,这两种格式没有太大区别,有时,根据类设计,使用非成员函数版本可能更好(为类定义类型转换时)。


擦肩而过的概率