C++ Primer Plus 学习笔记(第 11 章 使用类)

C++ Primer Plus 学习笔记

第 11 章 使用类

运算符重载

运算符重载是一种形式的 C++ 多态。用户能够定义多个名称相同但特征标(参数列表)不同的函数。这被称为函数重载或函数多态,旨在能够使用同名的函数来完成相同的基本操作,即使这种操作被用于不同的数据类型。运算符重载将重载的概念扩展到运算符上,允许赋于 C++ 运算符多种含义。实际上,很多 C++(也包括 C 语言)运算符已经被重载。例如,将*运算符用于地址,将得到存储在这个地址中的值;但将它用于两个数字时,得到的将的它们的乘积。C++ 根据操作数的数目和类型决定采用哪种操作。
C++ 允许运算符重载扩展到用户定义的类型,例如,允许使用+将两个对象相加。编译器根据操作数的数目和类型决定使用哪种加法定义。例如,将两个数组相加是一种常见的运算,通常,需要使用下面这样的for循环来实现:

for (int i = 0; i < 20; i++)
    evening[i] = sam[i] + janet[i];        // add element by element
// 但在 C++ 中,可以定义一个表示数组的类,并重载 + 运算符。于是可以有以下语句
evening = sam + janet;                     // add two array objects

这种简单的加法表示法隐藏了内部机理,并强调了实质,这是 OOP 的另一个目标。
要重载运算符,需要使用被称为运算符函数的特殊函数形式。运算符函数的格式如下:

operatorop(argument-list)

例如,operator+()重载+运算符,operator*()重载*运算符。op必须是有效的 C++ 运算符,不能虚构一个新的符号。例如,不能有operator@()这样的函数,因为 C++ 中没有@运算符。然而,operator[]()函数将重载[]运算符,因为[]是数组索引运算符。例如,假设有一个Salesperson类,并为它定义了一个operator+()成员函数,以重载+运算符,以便能够将两个Salesperson对象的销售额相加,则如果distric2sidsara都是Salesperson类对象,便可以编写这样的等式:

distric2 = sid + sara;
// 编译器发现,操作数是 Salesperson 类对象,因此使用相应的运算符函数替换上述运算符
distric2 = sid.operator+(sara);

然后,该函数将隐式地使用sid(因为它调用了方法),而显式的使用sara对象(因为它被作为参数传递),来计算总和,将返回这个值。当然最重要的是,可以使用简便的+运算符表示法,而不必使用笨拙的函数表示法。
虽然 C++ 对运算符重载做了一些限制,但了解重载的工作方式后,这些限制就很容易理解了。

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

如果今天早上在Priggs的账户上花费了 2 小时 35 分钟,下午又花费了 2 小时 40 分钟,则总共花了多少时间呢?这个示例与加法概念很吻合,但要相加的单位(小时与分钟的混合)与内置类型不匹配。第 7 章通过定义一个travel_time结构和将这种结构相加的sum()函数来处理类似的情况。现在将其推广,采用一个使用方法来处理加法的Time类。首先使用一个名为Sum()的常规方法,然后介绍如何将其转换为重载运算符。程序请单 11.1 列出了这个类的声明。

程序清单 11.1 mytime0.h

// mytime0.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 Sum(const Time & t) const;
    void Show() const;
};
#endif

Time类提供了用于调整和重新设置时间、显示时间、将两个时间相加的方法。程序清单 11.2 列出了方法定义。请注意,当总的分钟数超过 59 时,AddMin()Sum()方法是如何使用整数除法和求模运算符来调整minuteshours值的。另外,由于这里只使用了iostreamcout,且只使用了一次,因此使用std::cout比导入整个名称空间更经济。

程序清单 11.2 mytime0.cpp

// 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之前构造它的拷贝,调用函数将得到该拷贝。
警告:不要返回指向局部变量或临时对象的引用。函数执行完毕后,局部变量和临时对象将消失,引用将指向不存在的数据。
最后,程序清单 11.3 对Time类中计算时间总和的部分进行了测试。

程序清单 11.3 usetime0.cpp

// usetime0.cpp -- using the frist draft of the time class
// compile usetime0.cpp and mytime0.cpp together
#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;   
}
// 下面是程序清单 11.1、程序清单 11.2 和程序清单 11.3 组成的程序的输出:
planning time = 0 hours, 0 minutes
coding time = 2 hours, 40 minutes
fixing time = 5 hours, 55 minutes
coding.Sum(fixing) = 8 hours, 35 minutes
添加加法运算符

Time类转换为重载的加法运算符很容易,只要将Sum()的名称改为operator+()即可。这样做是对的,只要把运算符(这里为+)放到operator的后面,并将结果用作方法名即可。在这里,可以在标识符中使用字母、数字或下划线之外的其他字符。程序清单 11.4 和程序清单 11.5 反映了这些细微的修改。

程序清单 11.4 mytime1.h

// mytime1.h -- Time class of 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

程序清单 11.5 mytime1.cpp

// mytime1.cpp -- implementing Time methods
#include <iostream>
#include "mytime1.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 notation
// 但将该方法命令为 operator+() 后,也可以使用运算符表示法:
total = coding + fixing;             //operator notation

这两种表示法都将调用operator+()方法。注意,在运算符表示法中,运算符左侧的对象(这里为coding)是调用对象,运算符右边的对象(这里为fixing)是作为参数被传递的对象。程序清单 1l.6 说明了这一点。

程序清单 11.6 usetime1.cpp

// usetime1.cpp -- using the second draft of the time class
// compile usetime1.cpp and mytime1.cpp together
#include <iostream>
#include "mytime1.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 + fixing;
    // operator notation;
    cout << "coding + fixing = ";
    total.Show();
    cout << endl;
    
    Time morefixing(3, 28);
    cout << "more fixing time = ";
    morefixing.Show();
    cout << endl;
    total = morefixing.operator+(total);
    // function notation
    cout << "morefixing.operator+(total) = ";
    total.Show();
    cout << endl;
    
    return 0; 
}
// 下面是程序清单11.4~程序清单11.6组成的程序的输出:
planning time = 0 hours, 0 minutes
coding time = 2 hours, 40 minutes
fixing time = 5 hours, 55 minutes
coding + fixing = 8 hours, 35 minutes
more fixing time = 3 hours, 28 minutes
morefixing.operator+(total) = 12 hours, 3 minutes

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

int a, b, c;
Time A, B, C;
c = a + b;    // use int addition
C = A + B;    // use addition as defined for Time objects

可以将两个以上的对象相加吗?例如,如果t1t2t3t4都是Time对象,可以这样做吗:

t4 = t1 + t2 + t3;    // valid?

为回答这个问题,来看一些上述语句将被如何转换为函数调用。由于+是从左向右结合的运算符,因此上述语句首先被转换成下面这样:

t4 = t1.operator+(t2 + t3);    // valid?

然后,函数参数本身被转换成一个函数调用,结果如下:

t4 = t1.operator+(t2.operator+(t3));    // valid? YES

上述语句合法吗?是的。函数调用t2.operator+(t3)返回一个Time对象,后者是t2t3的和。然而,该对象成为函数调用tl.operator+()的参数,该调用返回t1与表示t2t3之和的Time对象的和。总之,最后的返回值为t1t2t3之和,这正是我们期望的。

重载限制

多数 C++ 运算符(参见表 11.1)都可以用这样的方式重载。重载的运算符(有些例外情况)不必是成员函数,但必须至少有一个操作数是用户定义的类型。下面详细介绍 C++ 对用户定义的运算符重载的限制。
1、 重载后的运算符必须至少有一个操作数是用户定义的类型,这将防止用户为标准类型重载运算符。
因此,不能将减法运算符(-)重载为计算两个double值的和而不是它们的差。虽然这种限制将对创造性有所影响,但可以确保程序正常运行。
2、使用运算符时不能违反运算符原来的句法规则。例如,不能将求模运算符(%)重载成使用一个操作数:

int x;
Time shiva;
% X;         // invalid for modulus operator
% shiva;     // 

同样,不能修改运算符的优先级。因此,如果将加号运算符重载成将两个类相加,则新的运算符与原来的加号具有相同的优先级。
3、不能创建新运算符。例如,不能定义operator*()函数来表示求幂。
4、不能重载下面的运算符。

  • sizeof: sizeof运算符。
  • .:成员运算符。
  • .*:成员指针运算符。
  • :::作用域解析运算符。
  • ?::条件运算符。
  • typeid:一个 RTTI 运算符。
  • const_cast:强制类型转换运算符。
  • dynamic_cast:强制类型转换运算符。
  • reinterpret_cast:强制类型转换运算符。
  • static_cast:强制类型转换运算符。

然而,表 11.1 中所有的运算符都可以被重载。
5、表 11.1 中的大多数运算符都可以通过成员或非成员函数进行重载,但下面的运算符只能通过成员函数进行重载。

  • =:赋值运算符。
  • ():函数调用运算符。
  • []:下标运算符。
  • ->:通过指针访问类成员的运算符。

注意:本章不介绍这里列出的所有运算符,但附录 E 对本书正文中没有介绍的运算符进行了总结。

表 11.1 可重载的运算符

+ - * / % ^
& | ~ ! = <
> += -= *= /= %=
*= &= |= << >> >>=
<<= == != <= >= &&
|| ++ , ->* ->
() [] new delete new[] delete[]
其他重载运算符

还有一些其他的操作对Time类来说是有意义的。例如,可能要将两个时间相减或将时间乘以一个因子,这需要重载减法和乘法运算符。这和重载加法运算符采用的技术相同,即创建operator-()operator*()方法。程序清单 11.7 是新的头文件。

程序清单 11.7 mytime2.h

// mytime2.h -- Time class after operator overloading 
#ifndef MYTIME2_H_
#define MYTIME2_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

然后将新增方法的定义添加到实现文件中,如程序清单 11.8 所示。

程序清单 11.8 mytime2.cpp

// mytime2.cpp -- implementing Time methods
#include <iostream>
#include "mytime2.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.minutes = (tot2 - tot1) % 60;
    diff.hours = (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";
}

完成上述修改后,就可以使用程序清单 11.9 中的代码来测试新定义了。

程序清单 11.9 usetime2.cpp

// usetime2.cpp -- using the third draft of the time class
// compile usetime2.cpp and mytime2.cpp together
#include <iostream>
#include "mytime2.h"

int main()
{
    using std::cout;
    using std::endl;
    Time weeding(4, 35);
    Time waxing(2, 47);
    Time total;
    Time diff;
    Time adjusted; 
    
    cout << "weeding time = ";
    weeding.Show();
    cout << endl;
    
    cout << "waxing time = ";
    waxing.Show();
    cout << endl;
    
    cout << "total word time = ";
    total = weeding + waxing;     // use operator+()
    total.Show();
    cout << endl;
    
    diff = weeding - waxing;      // use operator-()
    cout << "weeding time - waxing time = ";
    diff.Show();
    cout << endl;
    
    adjusted = total * 1.5;       // use operator*()
    cout << "adjusted work time = ";
    adjusted.Show();
    cout << endl;
    
    return 0; 
}
// 下面是程序清单 11.7 ~ 程序清单 11.9 组成的程序得到的输出:
weeding time = 4 hours, 35 minutes
waxing time = 2 hours, 47 minutes
total work time = 7 hours, 22 minutes
weeding time - waxing time = 1 hours, 48 minutes
adjusted work time = 11 hou
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值