输入输出流
正如我以前所说的一样,一个日期类的对象应该具有和系统内建类型一致的外观和感觉--输入/输出支持。C++提供了能够处理标准类型的的输入输出操作的流的对象。例如下列程序
:#include <iostream.h>
main()
{
int i;
cout << "Enter an integer: ";
cin >> i;
cout << "You typed " << i << endl;
return 0;
}
的输出结果为:
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来保护它们不受到无意中的访问。重载公用的运算符可以使得你的对象看起来跟系统内建的数据类型很相似--这增加了可读性和可维护性。