12.2 几个特殊运算符的重载
12.2.1 增量减量运算符
type class_name,,operator ++ (); // 前置增量
type class_name,,operator ++ (int); // 后置增量例:
class X {
int x;
public:
X(int a = 0),x(a) {}
int Get() { return x; }
int operator ++ () { return ++ x; }
int operator ++ (int) { return x ++; }
}
X aX(10);
cout << aX.Get() << endl; // 输出 10
++ aX;
cout << aX.Get() << endl; // 输出 11
i = aX ++;
cout << i << endl; // 输出 11
cout << aX.Get() << endl; // 输出 12
12.2.2 下标运算符
type class_name,,operator [ ] (arg);
例 为 String 类添加一个下标运算符重载:
char& operator [] (int i) { return pStr[i]; }
有了这样的运算符重载,就可以利用以下的方式修改串对象的内容:
String s("The C++ language");
s[5] = ' ';
s[6] = ' ';
s.Show(); // 输出,The C language
注意:这样的定义有可以破坏类的封装性。为安全起见,最好将该函数的返回值改为 char。
函数调用运算符( ( ))、成员选择运算符( ->),new 运算符和 delete 运算符等,重载时也有一些特殊的要求和限制。
但由于它们在实用中极少被重载,且重载和使用时的难度较大,这里就不再介绍了。有兴趣的同学可以参考相应资料。
第 13章 C++ 语言的 I/O 流类
13.1 概述输入输出(简称 I/O,Input / Output) 是指计算机与其外部设备 —— 如,显示器、打印机、磁盘机、键盘,等等 —— 以及其它计算机交换信息的功能。一个语言 I/O 功能的强弱在一定程度上表现了语言整体功能的强弱。
与其它高级语言不同,C++ 语言中没有独立的 I/O 语句,为了方便用户使用,标准中规定了大量的 I/O 函数。利用 OOP
技术,C++ 语言更是提供了一个庞大 I/O 流类。利用该类的对象,程序员可以编写出使用极为简便的、与设备无关的程序;
通过对流类中所定义(重载)的流运算符进行重载,可以使程序员自己定义的类本身也具有 I/O 功能。
13.1.1 流
C++ 语言为了向程序员提供一个统一的 I/O 接口,使程序的设计尽可能地与具体设备无关,在程序员与具体设备之间设置了一个抽象的概念 —— 流 ( Stream)。
C++ 将各式各样的具体设备转化成一个称为流的逻辑设备,
并由流来管理对具体设备的 I/O。而从程序员的角度来看,所有流的行为都是相同的。因此,一个用来向磁盘写信息的操作同样也可以向显示器进行输出。
C++ 中存在两种类型的流 —— 文本 ( Text )流和 二进制
( Binary)流。
13.1.2 文件流是 C++ 语言对逻辑设备的抽象,而 文件 ( File)则是 C++语言对具体设备的抽象。所有的流均具有相同的行为,而不同的文件则具有不同的行为。比如,显示器(文件)只能接受信息,
因此只能对该文件进行写操作;对键盘文件只能进行读操作;
对磁盘文件则可以进行读、写操作。
当程序与一个文件进行读写操作前,必须通过 打开 文件的操作将文件与流联系起来。一旦该联系建立,对流的访问就是对该具体设备的访问。若文件支持随机访问,则打开文件操作还会将一个读指针和一个写指针置于文件的相应位置处,随着程序对文件的访问,系统会自动移动这两个指针,以便为下一次访问作好准备。
在完成了对文件的读写操作后,应当通过 关闭 文件操作将文件与流的联系断开。关闭文件时,系统会自动根据具体情况对流进行“刷新”。当程序正常结束时,系统会自动关闭所有已打开的文件。
13.1.3 缓冲所谓 缓冲 ( Buffer),是指系统在内存中开辟一块叫做 缓冲区的区域,将欲写入文件或从文件中读出的数据暂时存放在该区域中。
一个流可以是缓冲流(带有缓冲区的),也可以是非缓冲流。
对于缓冲流,只有当缓冲区已满(称为 溢出,Overflow),或者当前进入缓冲区的是一个新行符(仅文本流),流才会自动刷新。而非缓冲流则将传给它的数据立即处理掉。
为了提高程序的运行效率,大多数流都是缓冲流。但有时为了满足某些特殊的需要,系统也预定义了一些非缓冲流。
13.2 C++ 的基本流类体系
13.2.1 基本流类体系
ios
istream ostream
iostream
streambuf
注:图中的粗虚线箭头不表示派生关系,它表示一个指针。
13.2.2 预定义的流及流运算符
C++ 语言在头文件 iostream.h 中预定义了 4 个流类的对象:
extern istream cin; // 标准输入流
extern ostream cout; // 标准输出流
extern ostream cerr; // 标准错误流(非缓冲)
extern ostream clog; // 标准错误流这 4 个流叫做“标准”流。这是因此只要程序中嵌入了系统头文件 iostream.h 它们就会被自动创建。缺省时,与 cin 所关联的文件为键盘,其它 3 个流与显示器关联。除 cerr 为非缓冲流外,其余的均为缓冲流。
C++ 为输入和输出流分别重载了,>>”和,<<”运算符,并将前者叫做“提取”运算符;后者叫做“插入”运算符。
在允许 I/O 重定向的系统中(如 DOS),还可以对输入流和输出流进行重定向。但是,错误流不得重定向 。
13.2.3 流的格式化 I/O
所谓格式化 I/O 是指在对数据进行 I/O 前,对其进行某种格式转换,比如,数据的数制、对齐方式等。格式化 I/O 总是针对文本流的。
格式化 I/O 时,若源数据为二进制的,则目标数据与其不仅形式相异,而且所需存储空间也大相径庭。比如,二进制的整型数据 12345 与 1 一样,需占两个字节;而对其进行格式化后,
前者需要 5 个字节,后者仅需 1 个字节。另外,格式化输出时,会将字符 '\n'(回车)转换成 '\n','\r'(回车换行)两个字符;格式化输入时则会做相反的转换。
在对流进行 I/O 时,难免会发生错误。 C++ 语言除了提供各种格式控制外,还预定义了一些错误标志,以尽可能地指出错误的原因。
13.2.3.1 格式控制符类 ios 中说明了一个用于指定 I/O 格式的无名公有枚举型成员

enum {
skipws = 0x0001,left = 0x0002,
right = 0x0004,internal = 0x0008,
dec = 0x0010,oct = 0x0020,
hex = 0x0040,showbase = 0x0080,
showpoint = 0x0100,uppercase = 0x0200,
showpos = 0x0400,scientific = 0x0800,
fixed = 0x1000,unitbuf = 0x2000
};
注意,有的具体实现还定义有一个枚举成员:
stdio = 0x0400
该成员的作用是指示当插入操作完成后立即刷新标准输出流和标准错误流。
由于格式控制符属于无名枚举的成员,所以它们就相当于类
ios 的直接成员。另外,由于这些成员被说明为公有的,所以在程序中可以很方便地访问它们。
ios 中还说明了一个保护的长整型数据成员:
protected:
long x_flags;
该成员用来记录当前的格式。由于格式控制符是按位定义的,
所以 x_flags 的每一位对应一个格式控制符:若某一位的值为
1,则说明对应的格式控制符被选取。例如,设当前 x_flags
的值为 0x0242,则表明当前选取的格式控制为:左对齐、十六进制、数值中的字母大写。
13.2.3.2 格式控制函数类 ios 中说明了多个用于访问 x_flags 的成员函数。下面予以分别介绍。
long ios,,flags(); // 读取 x_flags 的值
long ios,,flags(long); // 设置并读取 x_flags 的值例:
long flg = cout.flags();
cout << flg << endl; // 输出(可能)为 8193
flg = cout.flags(ios::showbase | ios::hex | ios::uppercase);
cout << flg << endl; // 输出为 0X2001
flg = cout.flags(flg); // 恢复原格式控制
cout << flg << endl; // 输出为 704(相当 0x2c0)
long ios,,setf(long); // 设置格式控制
long ios,,setf(long _setbit,long _field);
long ios,,unsetf(long); // 清除格式控制第 1,3 函数的参数即为欲设置的格式控制符的组合;第 2
个函数的 _setbit 为欲设置之格式控制值,_field 用于指定设置的是哪一位。
为了排队格式控制符中的矛盾位、方便使用,C++ 在类 ios
中说明了 3 个静态数据成员,以用作 _field 参数的值:
static const long basefield; // dec | oct | hex
static const long adjustfield; // left | right | internal
static const long floatfield; // scientific | fixed
#include <iostream.h>
void main()
{
int n = 87;
cout << n << '\t'; // 输出 87
cout.setf(ios::showbase | ios::hex | ios::uppercase);
cout << n << '\t'; // 输出 0X57
cout.unsetf(ios,,uppercase);
cout << n << '\t'; // 输出 0x57
cout.setf(ios,,oct,ios,,basefield);
cout << n << endl; // 输出 0127
}
13.2.3.3 设置域宽与填充方式域宽指输出数据的总长度;填充指当域宽大于实际数据的总长度时,空闲位置用指定的字符进行填充。类 ios 中为域宽和填充方式的设置分别定义了两个成员函数:
int ios,,width();
int ios,,width(int);
char ios,,fill();
char ios,,fill(char);
#include <iostream.h>
void main()
{
int n = -87,len;
cout << "12345678901234567890\n";
cout << n << endl;
cout.width(10);
cout << n << endl;
cout.fill('$'); cout.width(10);
cout << n << endl;
cout.setf(ios,,left); cout.width(10);
cout << n << endl;
cout.setf(ios,,internal); cout.width(10);
cout << n << endl;
}
该程序的输出为:
12345678901234567890
-87
-87
$$$$$$$-87
-87$$$$$$$
-$$$$$$$87
应当说明的是,width() 函数仅对其后的一次 I/O 操作有效,
所以在程序中若对输出域宽有要求,在进行输出前必须重新设置域宽,即便本次输出的域宽与上一次的相同也是如此。
13.2.3.4 实数输出格式控制在类 ios 中,除了说明有用于控制实数有格式控制符 ios::fixed
和 ios::scientific 外,还说明有设置实数精度的成员函数:
int ios,,precision();
int ios,,precision(int);
例:
float n = -123.4567;
cout << n << endl; // 输出 -123.456703
cout.setf(ios,,scientific,ios,,floatfield);
cout << n << endl; // 输出 -1.234567e+02
cout.width(15);
cout.precision(2);
cout << n << endl; // 输出 -123.46
13.2.3.5 I/O 操纵符为了便于使用、简化程序的书写,C++ 预定义了 13 个 I/O 操纵符 ( Manipulator):
dec hex oct setbase(int)
ws ends endl flush
resetioflags(long) setioflags(long)
setfill(int) setprecision(int) setw(int)
注意:使用这些操纵符(除 endl)时,必须在程序中嵌入系统头文件 iomanip.h。
例:
cout << hex << 123 << endl; // 输出 0x7b
cout << setbase(16) << 123 << endl; // 输出同上
cout << setw(10) << 123 << endl; // 输出 123
13.2.4 流的错误处理为了及时地提供 I/O 操作时出现的错误,C++ 在 ios 中说明了一个公有枚举成员 io_state:
enum {
goodbit = 0x00,// 操作正常
eofbit = 0x01,// 输入流中已无数据
failbit = 0x20,// 操作失败
badbit = 0x40,// 非法操作
hardfail = 0x80 // 严重错误
}
ios 中还说明了一个保护的数据成员 state。每当进行一次 I/O
操作后,系统会自动为该成员赋以相应的值。
为配合这些错误状态的使用,ios 中还说明了几个成员函数:
int ios,,rdstate(); // 读取当前错误状态
int ios,,good(); // state = 0 为真
int ios,,eof(); // state 中 eofbit 置位时为真
int ios,,fail(); // state 中 failbit,badbit,hardfail
// 任一个置位时为真
int ios,,bad(); // state 中 badbit,hardfail 任一个
// 置位时为真
int ios,,clear(int = 0); // 清除指定错误位例:
cin >> i;
if(ios,,good())
// 使用变量 i 的值
else
ios,,clear();
注意,clear() 不能清除 state
中的 hardfail 位。
#include <iostream.h>
void main()
{
int i,s;
char str[81];
cin >> i;
s = cin.rdstate();
while(s) {
cin.clear();
cin >> str;
cout << "输入错误,重新输入,";
cin >> i;
s = cin.rdstate();
}
cout << i << endl;
}
利用 ios 中重载的运算符 !,可以将上述程序写的更简单:
int ios,,operator ! () { return fail(); }
void main()
{
int i;
char str[81];
while(!(cin >> i)) {
cin.clear();
cin >> str;
cout << "输入错误,重新输入,";
}
cout << i << endl;
}
习题:
20,30