分类: C/C++
2008-08-01 17:02:58
int compare(const Date& d2) const;
Date::compare 类似于strcmp-如果当前对象(*this)在d2之前,它返回一个负整数;如果这两个日期相同,则返回0;否则返回一个正整数(参见 Listing 2 中的函数实现和
Listing 3 中的示例程序)。就像你们都很熟悉的C标准库中的qsort一样,你也可以使用Date::compare来对日期进行排序,就好像你使用strcmp对字符串进行排序一样。下面是一个可传递给qsort的比较函数(下个月的代码封装将包括qsort):
#include "date.h"
int datecmp(const void *p1, const void *p2)
{
const Date
*d1p = (const Date *) p1,
*d2p = (const Date *) p2;
return d1p->compare(*d2p);
}
大多数时候,拥有相关的运算符是更方便的,例如:
if (d1 < d2)
// do something appropriate..
使用Date::compare来添加一个"小于"运算符是非常容易的--只要在类的定义里插入下面这个内联成员函数就可以了:
int operator<(const Date& d2) const
{return compare(d2) < 0};
每一个表达式:d1 < d2出现的地方,都会被编译器翻译成函数调用的形式:
d1.operator<(d2)
Listing 4 中类的定义中拥有六个相关的操作符,Listing 5中展示了更新之后的示范程序。
既然函数Date::interval 的功能类似减法(它给出两个日期的差),把它重命名为Date::operator-就是件很自然的事情了。在做这个事情之前,我们仔细研究一下下列语句的语音:
a = b - c;
无论变量是什么类型,下述语句总是成立的:
a 是一个由减法产生的明确的对象,并且 b - c == - (c - b)
我们使用下列约定俗成的习惯,即一个正的日期对象的所有数据成员都是正的,反之亦然(不允许符号的混合)。在
Listing 7 中我用 Date::operator- (const Date&)代替了Date::interval,前者为每一个数据成员增加了正确的符号并且返回重新构造过的类的对象。
Listing 6 中重新定义的类中还包括了一个一元的"-"运算符函数,它的名字还是 Date::operator-,但是没有任何参数。编译器将把下列的语句
1 - d2;
-d1;
分别替换为:
d1.operator-(d2); // Calls Date::operator-(const Date&)
d1.operator-(); // Calls Date::operator-()
Listing 8 中有一个使用了新的成员函数的简单示例程序。
正如我以前所说的一样,一个日期类的对象应该具有和系统内建类型一致的外观和感觉--输入/输出支持。C 提供了能够处理标准类型的的输入输出操作的流的对象。例如下列程序
:#include
的输出结果为:
Enter an integer: 5
You typed 5
cout 是 C 流库中提供的 output 流(类ostreom)而cin是C 流库中提供的input流(类istreom),它们分别与标准输出和标准输入相关。当编译器看到下面的表达式:
cout << "Enter an integer: "
它将用如下语句代替:
cout.operator<<("Enter an integer: ")
上述语句调用了成员函数ostream::operator<<(const char *)。同样的,表达式
cout << i
调用了函数
ostream::-operator<<(int)。
endl 是一个特殊的流指示,它输出一个换行符并清空输出缓冲区。Output行可以连在一起:
cout << "You typed " << i
因为 ostream::operator<< 返回一个到 stream 自身的引用。上述语句变成了
(cout.operator<<("You typed ")).operator<<(i)
为了适应 Date 对象的输出,你需要一个全局函数,该函数将要输出的内容发送给一个给定的输出流,并且返回到那个流的引用:
ostream& operator<<(ostream& os, const Date& d)
{
os << d.get_month() << ''/''
<< d.get_day() << ''/''
<< d.get_year();
return os;
}
这当然不能是一个成员函数,因为流(并非正在被输出的对象)总是出现在流插入符号的左边。
为了提高效率,通常会赋予 operator<< 进入到一个对象的私有数据成员的权限(大多数的类的实现都提供了相关的I/O操作符,因此在这种情况下打破封装的边界似乎是比较安全的)。为了穿破对私有数据成员访问的限制,你需要在类的声明中加入如下语句,把operator<< 声明为 Date 的友元:
friend ostream& operator<<(ostream&, const Date&);
Listing 9中展示了新的类的声明,并且包括了输入函数operator>>的声明。Listing 10中展示了这些函数的实现,Listing 11中有一个简单的示例程序。
C 中的类定义了一个作用域。这就是为什么函数Date::compare不会和一个叫做compare的全局函数发生冲突的原因(即使它们参数和返回值的类型都相同)。现在考虑实现文件中的一个数组dtab[]。dtab的静态存储类型使它对文件来说是private的。但是它实际上属于整个类,而不是这个文件。如果我想要传递Date的成员函数到多个文件中,我不得不将需要访问dtab的函数传递到同一个文件中。
一个更好的办法是使dtab成为类的静态成员。静态成员属于整个类,而不是一个单独的对象。这意味着只有一个dtab的拷贝存在,它被所有类的对象所共享。使函数isleap成为static则允许你不需要和一个对象相关就能调用它,比如,你只需要这样写:
isleap(y);
而不需要这样写:
d.isleap(y);
要想使isleap对任何调用者都可用,使它为 public,用如下方式调用:
Date::isleap(y);
最后,我将重新定义缺省构造函数,用当前日期初始化类的对象。最后的类的定义、实现和示例程序分别参见
Listing 12 -
Listing 14。
在上面两个部分中,我试图说明C 是如何支持数据抽象--使用者的产物--自定义的数据类型。构造函数使得当你声明一个对象的时候能够自动对它进行初始化。你可以通过声明类的成员为private来保护它们不受到无意中的访问。重载公用的运算符可以使得你的对象看起来跟系统内建的数据类型很相似--这增加了可读性和可维护性。