- 内容提要
- 前言
- 第 1 章 预备知识
- 第 2 章 开始学习 C++
- 第 3 章 处理数据
- 第 4 章 复合类型
- 第 5 章 循环和关系表达式
- 第 6 章 分支语句和逻辑运算符
- 第 7 章 函数——C++的编程模块
- 第 8 章 函数探幽
- 第 9 章 内存模型和名称空间
- 第 10 章 对象和类
- 第 11 章 使用类
- 第 12 章 类和动态内存分配
- 第 13 章 类继承
- 第 14 章 C++中的代码重用
- 第 15 章 友元、异常和其他
- 第 16 章 string 类和标准模板库
- 第 17 章 输入、输出和文件
- 第 18 章 探讨 C++新标准
- 附录 A 计数系统
- 附录 B C++保留字
- 附录 C ASCII 字符集
- 附录 D 运算符优先级
- 附录 E 其他运算符
- 附录 F 模板类 string
- 附录 G 标准模板库方法和函数
- 附录 H 精选读物和网上资源
- 附录 I 转换为 ISO 标准 C++
- 附录 J 复习题答案
17.2 使用 cout 进行输出
正如前面指出的,C++将输出看作字节流(根据实现和平台的不同,可能是 8 位、16 位或 32 位的字节,但都是字节),但在程序中,很多数据被组织成比字节更大的单位。例如,int 类型由 16 位或 32 位的二进制值表示;double 值由 64 位的二进制数据表示。但在将字节流发送给屏幕时,希望每个字节表示一个字符值。也就是说,要在屏幕上显示数字−2.34,需要将 5 个字符(−、2、.、3 和 4),而不是这个值的 64 位内部浮点表示发送到屏幕上。因此,ostream 类最重要的任务之一是将数值类型(如 int 或 float)转换为以文本形式表示的字符流。也就是说,ostream 类将数据内部表示(二进制位模式)转换为由字符字节组成的输出流(以后会有仿生移植物,使得能够直接翻译二进制数据。我们把这种开发作为一个练习,留给您)。为执行这些转换任务,ostream 类提供了多个类方法。现在就来看看它们,总结本书使用的方法,并介绍能够更精密地控制输出外观的其他方法。
17.2.1 重载的<<运算符
本书常结合使用 cout 和<<运算符(插入(insertion)运算符):
在 C++中,与 C 一样,<<运算符的默认含义是按位左移运算符(参见附录 E)。表达式 x<<3 的意思,将 x 的二进制表示中所有的位向左移动 3 位。显然,这与输出的关系不大。但 ostream 类重新定义了<<运算符,方法是将其重载为输出。在这种情况下,<<叫作插入运算符,而不是左移运算符(左移运算符由于其外观(像向左流动的信息流)而获得这种新角色)。插入运算符被重载,使之能够识别 C++中所有的基本类型:
- unsigned char;
- signed char;
- char;
- short;
- unsigned short;
- int;
- unsiged int;
- long;
- unsigned long;
- long long(C++11);
- unsigned long long(C++11);
- float;
- double;
- long double。
对于上述每种数据类型,ostream 类都提供了 operator<<( ) 函数的定义(第 11 章讨论过,名称中包含运算符的函数用于重载该运算符)。因此,如果使用下面这样一条语句,而 value 是前面列出的类型之一,则 C++程序将其对应于有相应的特征标的运算符函数:
例如,表达式 cout<<88 对应于下面的方法原型:
该原型表明,operator<<( ) 函数接受一个 int 参数,这与上述语句中的 88 匹配。该原型还表明,函数返回一个指向 ostream 对象的引用,这使得可以将输出连接起来,如下所示:
如果您是 C 语言程序员,深受%类型说明符过多、说明符类型与值不匹配时将发生问题等痛苦,则使用 cout 非常简单(当然,由于有 cin,C++输入也非常简单)。
1.输出和指针
ostream 类还为下面的指针类型定义了插入运算符函数:
- const signed char *;
- const unsigned char *;
- const char *;
- void *。
不要忘了,C++用指向字符串存储位置的指针来表示字符串。指针的形式可以是 char 数组名、显式的 char 指针或用引号括起的字符串。因此,下面所有的 cout 语句都显示字符串:
方法使用字符串中的终止空字符来确定何时停止显示字符。
对于其他类型的指针,C++将其对应于 void *,并打印地址的数值表示。如果要获得字符串的地址,则必须将其强制转换为其他类型,如下面的代码片段所示:
2.拼接输出
插入运算符的所有化身的返回类型都是 ostream &。也就是说,原型的格式如下:
(其中,type 是要显示的数据的类型)返回类型 ostream &意味着使用该运算符将返回一个指向 ostream 对象的引用。哪个对象呢?函数定义指出,引用将指向用于调用该运算符的对象。换句话说,运算符函数的返回值为调用运算符的对象。例如,cout << “potluck”返回的是 cout 对象。这种特性使得能够通过插入来连接输出。例如,请看下面的语句:
表达式 cout << “We have”将显示字符串,并返回 cout 对象。至此,上述语句将变为:
表达式 cout<<count 将显示 count 变量的值,并返回 cout。然后 cout 将处理语句中的最后一个参数(参见图 17.4)。这种设计技术确实是一项很好的特性,这也是前几章中重载<<运算符的示例模仿了这种技术的原因所在。
图 17.4 拼接输出
17.2.2 其他 ostream 方法
除了各种 operator<<( ) 函数外,ostream 类还提供了 put( ) 方法和 write( ) 方法,前者用于显示字符,后者用于显示字符串。
最初,put( ) 方法的原型如下:
当前标准与此相同,但被模板化,以适用于 wchar_t。可以用类方法表示法来调用它:
其中,cout 是调用方法的对象,put( ) 是类成员函数。和<<运算符函数一样,该函数也返回一个指向调用对象的引用,因此可以用它将拼接输出:
函数调用 cout.put('I') 返回 cout,cout 然后被用作 put('t') 调用的调用对象。
在原型合适的情况下,可以将数值型参数(如 int)用于 put( ),让函数原型自动将参数转换为正确 char 值。例如,可以这样做:
第一条语句将 int 值 65 转换为一个 char 值,然后显示 ASCII 码为 65 的字符。同样,第二条语句将 double 值 66.3 转换为 char 值 66,并显示对应的字符。
这种行为在 C++ 2.0 之前可派上用场。在这些版本中,C++语言用 int 值表示字符常量。因此,下面的语句将'W'解释为一个 int 值,因此将其作为整数 87(即该字符的 ASCII 值)显示出来:
然而,下面这条语句能够正常工作:
因为当前的 C++将 char 常量表示为 char 类型,因此现在可以使用上述任何一种方法。
一些老式编译器错误地为 char、unsigned char 和 signed char 3 种参数类型重载了 put( )。这使得将 int 参数用于 put( ) 时具有二义性,因为 int 可被转换为这 3 种类型中的任何一种。
write( ) 方法显示整个字符串,其模板原型如下:
write( ) 的第一个参数提供了要显示的字符串的地址,第二个参数指出要显示多少个字符。使用 cout 调用 write( ) 时,将调用 char 具体化,因此返回类型为 ostream &。程序清单 17.1 演示了 write( ) 方法是如何工作的。
程序清单 17.1 write.cpp
有些编译器可能指出该程序定义了数组 state1 和 state3 但没有使用它们。这不是什么问题,因为这两个数组只是用于提供数组 state2 前面和后面的数据,以便您知道程序错误地存取 state2 时发生的情况。下面是程序清单 17.1 中程序的输出:
注意,cout.write( ) 调用返回 cout 对象。这是因为 write( ) 方法返回一个指向调用它的对象的引用,这里调用它的对象是 cout。
这使得可以将输出拼接起来,因为 cout.write( ) 将被其返回值 cout 替换:
还需要注意的是,write( ) 方法并不会在遇到空字符时自动停止打印字符,而只是打印指定数目的字符,即使超出了字符串的边界!在这个例子中,在字符串“kansas”的前后声明了另外两个字符串,以便相邻的内存包含数据。编译器在内存中存储数据的顺序以及调整内存的方式各不相同。例如,“Kansas”占用 6 个字节,而该编译器使用 4 个字节的倍数调整字符串,因此“Kansas”被填充成占用 8 个字节。由于编译器之间的差别,因此输出的最后一行可能不同。
write( ) 方法也可用于数值数据,您可以将数字的地址强制转换为 char *,然后传递给它:
这不会将数字转换为相应的字符,而是传输内存中存储的位表示。例如,4 字节的 long 值(如 560031841)将作为 4 个独立的字节被传输。输出设备(如显示器)将把每个字节作为 ASCII 码进行解释。因此在屏幕上,560031841 将被显示为 4 个字符的组合,这很可能是乱码(也可能不是,请试试看)。然而,write( ) 确实为将数值数据存储在文件中提供了一种简洁、准确的方式,这将在本章后面进行介绍。
17.2.3 刷新输出缓冲区
如果程序使用 cout 将字节发送给标准输出,情况将如何?由于 ostream 类对 cout 对象处理的输出进行缓冲,所以输出不会立即发送到目标地址,而是被存储在缓冲区中,直到缓冲区填满。然后,程序将刷新(flush)缓冲区,把内容发送出去,并清空缓冲区,以存储新的数据。通常,缓冲区为 512 字节或其整数倍。当标准输出连接的是硬盘上的文件时,缓冲可以节省大量的时间。毕竟,不希望程序为发送 512 个字节,而存取磁盘 512 次。将 512 个字节收集到缓冲区中,然后一次性将它们写入硬盘的效率要高得多。
然而,对于屏幕输出来说,首先填充缓冲区的重要性要低得多。如果必须重述消息“Press any key to continue”以便使用 512 个字节来填充缓冲区,实在是太不方便了。所幸的是,在屏幕输出时,程序不必等到缓冲区被填满。例如,将换行符发送到缓冲区后,将刷新缓冲区。另外,正如前面指出的,多数 C++实现都会在输入即将发生时刷新缓冲区。也就是说,假设有下面的代码:
程序期待输入这一事实,将导致它立刻显示 cout 消息(即刷新“Enter a number:”消息),即使输出字符串中没有换行符。如果没有这种特性,程序将等待输入,而无法通过 cout 消息来提示用户。
如果实现不能在所希望时刷新输出,可以使用两个控制符中的一个来强行进行刷新。控制符 flush 刷新缓冲区,而控制符 endl 刷新缓冲区,并插入一个换行符。这两个控制符的使用方式与变量名相同:
事实上,控制符也是函数。例如,可以直接调用 flush( ) 来刷新 cout 缓冲区:
然而,ostream 类对<<插入运算符进行了重载,使得下述表达式将被替换为函数调用 flush(cout):
因此,可以用更为方便的插入表示法来成功地进行刷新。
17.2.4 用 cout 进行格式化
ostream 插入运算符将值转换为文本格式。在默认情况下,格式化值的方式如下。
- 对于 char 值,如果它代表的是可打印字符,则将被作为一个字符显示在宽度为一个字符的字段中。
- 对于数值整型,将以十进制方式显示在一个刚好容纳该数字及负号(如果有的话)的字段中。
- 字符串被显示在宽度等于该字符串长度的字段中。
浮点数的默认行为有变化。下面详细说明了老式实现和新实现之间的区别。
- 新式:浮点类型被显示为 6 位,末尾的 0 不显示(注意,显示的数字位数与数字被存储时精度没有任何关系)。数字以定点表示法显示还是以科学计数法表示(参见第 3 章),取决于它的值。具体来说,当指数大于等于 6 或小于等于−5 时,将使用科学计数法表示。另外,字段宽度恰好容纳数字和负号(如果有的话)。默认的行为对应于带%g 说明符的标准 C 库函数 fprintf( )。
- 老式:浮点类型显示为带 6 位小数,末尾的 0 不显示(注意,显示的数字位数与数字被存储时的精度没有任何关系)。数字以定点表示法显示还是以科学计数法表示(参见第 3 章),取决于它的值。另外,字段宽度恰好容纳数字和负号(如果有的话)。
因为每个值的显示宽度都等于它的长度,因此必须显式地在值之间提供空格;否则,相邻的值将不会被分开。
程序清单 17.2 演示默认的输出情况,它在每个值后面都显示一个冒号(:),以便可以知道每种情况下的字段宽度。该程序使用表达式 1.0/9.0 来生成一个无穷小数,以便能够知道打印了多少位。
注意:
并非所有的编译器都能生成符合当前 C++标准格式的输出。另外,当前标准允许区域性变化。例如,欧洲实现可能遵循欧洲人的风格:使用逗号而不是句点来表示小数点。也就是说,2.54 将被写成 2,54。区域库(头文件 locale)提供了用特定的风格影响(imbuing)输入或输出流的机制,所以同一个编译器能够提供多个区域选项。本章使用美国格式。
程序清单 17.2 defaults.cpp
程序清单 17.2 中程序的输出如下:
每个值都填充自己的字段。注意,1.200 末尾的 0 没有显示出来,但末尾不带 0 的浮点值后面将有 6 个空格。另外,该实现将指数显示为 3 位,而其他实现可能为两位。
1.修改显示时使用的计数系统
ostream 类是从 ios 类派生而来的,而后者是从 ios_base 类派生而来的。ios_base 类存储了描述格式状态的信息。例如,一个类成员中某些位决定了使用的计数系统,而另一个成员则决定了字段宽度。通过使用控制符(manipulator),可以控制显示整数时使用的计数系统。通过使用 ios_base 的成员函数,可以控制字段宽度和小数位数。由于 ios_base 类是 ostream 的间接基类,因此可以将其方法用于 ostream 对象(或子代),如 cout。
注意:
ios_base 类中的成员和方法以前位于 ios 类中。现在,ios_base 是 ios 的基类。在新系统中,ios 是包含 char 和 wchar_t 具体化的模板,而 ios_base 包含了非模板特性。
来看如何设置显示整数时使用的计数系统。要控制整数以十进制、十六进制还是八进制显示,可以使用 dec、hex 和 oct 控制符。例如,下面的函数调用将 cout 对象的计数系统格式状态设置为十六进制:
完成上述设置后,程序将以十六进制形式打印整数值,直到将格式状态设置为其他选项为止。注意,控制符不是成员函数,因此不必通过对象来调用。
虽然控制符实际上是函数,但它们通常的使用方式为:
ostream 类重载了<<运算符,这使得上述用法与函数调用 hex(cout)等价。控制符位于名称空间 std 中。程序清单 17.3 演示了这些控制符的用法,它以 3 种不同的计数系统显示了一个整数的值极其平方。注意,可以单独使用控制符,也可将其作为一系列插入的组成部分。
程序清单 17.3 manip.cpp
下面程序清单 17.3 中程序的运行情况:
2.调整字段宽度
您可能已经注意到,在程序清单 17.3 的输出中各列并没有对齐,这是因为数字的字段宽度不相同。可以使用 width 成员函数将长度不同的数字放到宽度相同的字段中,该方法的原型为:
第一种格式返回字段宽度的当前设置;第二种格式将字段宽度设置为 i 个空格,并返回以前的字段宽度值。这使得能够保存以前的值,以便以后恢复宽度值时使用。
width( ) 方法只影响将显示的下一个项目,然后字段宽度将恢复为默认值。例如,请看下面的语句:
由于 width( ) 是成员函数,因此必须使用对象(这里为 cout)来调用它。输出语句生成的输出如下:
12 被放到宽度为 12 个字符的字段的最右边,这被称为右对齐。然后,字段宽度恢复为默认值,并将两个#符号以及 24 放在宽度与它们的长度相等的字段中。
警告:
width( ) 方法只影响接下来显示的一个项目,然后字段宽度将恢复为默认值。
C++永远不会截短数据,因此如果试图在宽度为 2 的字段中打印一个 7 位值,C++将增宽字段,以容纳该数据(在有些语言中,如果数据长度与字段宽度不匹配,将用星号填充字段。C/C++的原则是:显示所有的数据比保持列的整洁更重要。C++视内容重于形式)。程序清单 17.4 演示了 width( ) 成员函数是如何工作的。
程序清单 17.4 width.cpp
程序清单 17.4 中程序的输出如下:
在上述输出中,值在字段中右对齐。输出中包含空格,也就是说,cout 通过加入空格来填满整个字段。右对齐时,空格被插入到值的左侧。用来填充的字符叫做填充字符(fill character)。右对齐是默认的。
注意,在程序清单 17.4 中,第一条 cout 语句显示字符串时,字段宽度被设置为 30,但在显示 w 的值时,字段宽度不是 30。这是由于 width( ) 方法只影响接下来被显示的一个项目。另外,w 的值为 0。这是由于 cout.width(30)返回的是以前的字段宽度,而不是刚设置的值。W 为 0 表明,默认的字段宽度为 0。由于 C++总会增长字段,以容纳数据,因此这种值适用于所有的数据。最后,程序使用 width( ) 来对齐列标题和数据,方法是将第 1 列宽度设置为 5 个字符,将第 2 列的宽度设置为 8 个字符。
3.填充字符
在默认情况下,cout 用空格填充字段中未被使用的部分,可以用 fill( ) 成员函数来改变填充字符。例如,下面的函数调用将填充字符改为星号:
这对于检查打印结果,防止接收方添加数字很有用。程序清单 17.5 演示了该成员函数的用法。
程序清单 17.5 fill.cpp
下面是程序清单 17.5 中程序的输出:
注意,与字段宽度不同的是,新的填充字符将一直有效,直到更改它为止。
4.设置浮点数的显示精度
浮点数精度的含义取决于输出模式。在默认模式下,它指的是显示的总位数。在定点模式和科学模式下(稍后将讨论),精度指的是小数点后面的位数。已经知道,C++的默认精度为 6 位(但末尾的 0 将不显示)。precision( ) 成员函数使得能够选择其他值。例如,下面语句将 cout 的精度设置为 2:
和 width( ) 的情况不同,但与 fill( ) 类似,新的精度设置将一直有效,直到被重新设置。程序清单 17.6 准确地说明了这一点。
程序清单 17.6 precise.cpp
下面是程序清单 17.6 中程序的输出:
注意,第 3 行没有打印小数点及其后面的内容。另外,第 4 行显示的总位数为 2 位。
5.打印末尾的 0 和小数点
对于有些输出(如价格或栏中的数字),保留末尾的 0 将更为美观。例如,对于程序清单 17.6 的输出,$20.40 将比$20.4 更美观。iostream 系列类没有提供专门用于完成这项任务的函数,但 ios_base 类提供了一个 setf( ) 函数(用于 set 标记),能够控制多种格式化特性。这个类还定义了多个常量,可用作该函数的参数。例如,下面的函数调用使 cout 显示末尾小数点:
使用默认的浮点格式时,上述语句还将导致末尾的 0 被显示出来。也就是说,如果使用默认精度(6 位)时,cout 不会将 2.00 显示为 2,而是将它显示为 2.000000。程序清单 17.7 在程序清单 17.6 中添加了这条语句。
您可能对表示法 ios_base::showpoint 有疑问,showpoint 是 ios_base 类声明中定义的类级静态常量。类级意味着如果在成员函数定义的外面使用它,则必须在常量名前面加上作用域运算符(::)。因此 ios_base::showpoint 指的是在 ios_base 类中定义的一个常量。
程序清单 17.7 showpt.cpp
下面是使用当前 C++格式时,程序清单 17.7 中程序的输出:
在上述输出中,第一行显示了;第三行显示了小数点,但没有显示末尾的 0,这是因为精度被设置为 2,而小数点前面已经包含两位。
6.再谈 setf( )
setf( ) 方法控制了小数点被显示时其他几个格式选项,因此来仔细研究一下它。ios_base 类有一个受保护的数据成员,其中的各位(这里叫作标记)分别控制着格式化的各个方面,例如计数系统、是否显示末尾的 0 等。打开一个标记称为设置标记(或位),并意味着相应的位被设置为 1。位标记是编程开关,相当于设置 DIP 开关以配置计算机硬件。例如,hex、dec 和 oct 控制符调整控制计数系统的 3 个标记位。setf( ) 函数提供了另一种调整标记位的途径。
setf( ) 函数有两个原型。第一个为:
其中,fmtflags 是 bitmask 类型(参见后面的“注意”)的 typedef 名,用于存储格式标记。该名称是在 ios_base 类中定义的。这个版本的 setf( ) 用来设置单个位控制的格式信息。参数是一个 fmtflags 值,指出要设置哪一位。返回值是类型为 fmtflags 的数字,指出所有标记以前的设置。如果打算以后恢复原始设置,则可以保存这个值。应给 setf( ) 传递什么呢?如果要第 11 位设置为 1,则可以传递一个第 11 位为 1 的数字。返回值的第 11 位将被设置为 1。对位进行跟踪好像单调乏味(实际上也是这样)。然而,您不必作做这项工作,ios_base 类定义了代表位值的常量,表 17.1 列出了其中的一些定义。
表 17.1 格式常量
常 量 | 含 义 |
---|---|
ios_base ::boolalpha | 输入和输出 bool 值,可以为 true 或 false |
ios_base ::showbase | 对于输出,使用 C++基数前缀(0,0x) |
ios_base ::showpoint | 显示末尾的小数点 |
ios_base ::uppercase | 对于 16 进制输出,使用大写字母,E 表示法 |
ios_base ::showpos | 在正数前面加上+ |
注意:
bitmask 类型是一种用来存储各个位值的类型。它可以是整型、枚举,也可以是 STL bitset 容器。这里的主要思想是,每一位都是可以单独访问的,都有自己的含义。iostream 软件包使用 bitmask 来存储状态信息。
由于这些格式常量都是在 ios_base 类中定义,因此使用它们时,必须加上作用域解析运算符。也就是说,应使用 ios_base ::uppercase,而不是 uppercase。如果不想使用 using 编译指令或 using 声明,可以使用作用域运算符来指出这些名称位于名称空间 std 中。修改将一直有效,直到被覆盖为止。程序清单 17.8 演示了如何使用其中一些常量。
程序清单 17.8 setf.cpp
下面是程序清单 17.8 中程序的输出:
注意,仅当基数为 10 时才使用加号。C++将十六进制和八进制都视为无符号的,因此对它们,无需使用符号(然而,有些 C++实现可能仍然会显示加号)。
第二个 setf( ) 原型接受两个参数,并返回以前的设置:
函数的这种重载格式用于设置由多位控制的格式选项。第一参数和以前一样,也是一个包含了所需设置的 fmtflags 值。第二参数指出要清除第一个参数中的哪些位。例如,将第 3 位设置为 1 表示以 10 为基数,将第 4 位设置为 1 表示以 8 为基数,将第 5 位设置为 1 表示以 16 为基数。假设输出是以 10 为基数的,而要将它设置为以 16 为基数,则不仅需要将第 5 位设置为 1,还需要将第 3 位设置为 0——这叫作清除位(clearing the bit)。聪明的十六进制控制符可自动完成这两项任务。使用函数 setf( ) 时,要做的工作多些,因为要用第二参数指出要清除哪些位,用第一参数指出要设置哪位。这并不像听上去那么复杂,因为 ios_base 类为此定义了常量(如表 17.2 所示)。具体地说,要修改基数,可以将常量 ios_base::basefield 用作第二参数,将 ios_base ::hex 用作第一参数。也就是说,下面的函数调用与使用十六进制控制符的作用相同:
表 17.2 setf(long, long) 的参数
第二个参数 | 第一个参数 | 含 义 |
---|---|---|
ios_base ::basefield | ios_base ::dec | 使用基数 10 |
ios_base ::oct | 使用基数 8 | |
ios_base ::hex | 使用基数 16 | |
ios_base ::floatfield | ios_base ::fixed | 使用定点计数法 |
ios_base ::scientific | 使用科学计数法 | |
ios_base ::adjustfield | ios_base ::left | 使用左对齐 |
ios_base ::right | 使用右对齐 | |
ios_base ::internal | 符号或基数前缀左对齐,值右对齐 |
ios_base 类定义了可按这种方式处理的 3 组格式标记。每组标记都由一个可用作第二参数的常量和两三个可用作第一参数的常量组成。第二参数清除一批相关的位,然后第一参数将其中一位设置为 1。表 17.2 列出了用作 setf( ) 的第二参数的常量的名称、可用作第一参数的相关常量以及它们的含义。例如,要选择左对齐,可将 ios_base ::adjustfield 用作第二参数,将 ios_base ::left 作为第一参数。左对齐意味着将值放在字段的左端,右对齐则表示将值放在字段的右端。内部对齐表示将符号或基数前缀放在字段左侧,余下的数字放在字段的右侧(遗憾的是,C++没有提供自对齐模式)。
定点表示法意味着使用格式 123.4 来表示浮点值,而不管数字的长度如何,科学表示法则意味着使用格式 1.23e04,而不考虑数字的长度。如果您熟悉 C 语言中 printf( ) 的说明符,则可能知道,默认的 C++模式对应于%g 说明符,定点表示法对应于%f 说明符,而科学表示法对应于%e 说明符。
在 C++标准中,定点表示法和科学表示法都有下面两个特征:
- 精度指的是小数位数,而不是总位数;
- 显示末尾的 0。
setf( ) 函数是 ios_base 类的一个成员函数。由于这个类是 ostream 类的基类,因此可以使用 cout 对象来调用该函数。例如,要左对齐,可使用下面的调用:
要恢复以前的设置,可以这样做:
程序清单 17.9 是一个使用两个参数的 setf( ) 的示例。
注意:
程序清单 17.9 中的程序使用了一个数学函数,有些 C++系统不自动搜索数学库。例如,有些 UNIX 系统要求这样做:
-lm 选项命令链接程序搜索数学库。同样,有些使用 g++的 Linux 系统也要求这样做。
程序清单 17.9 setf2.cpp
下面是程序清单 17.9 中程序的输出:
注意到精度 3 让默认的浮点显示(在这个程序中用于内部对齐)总共显示 3 位,而定点模式和科学模式只显示 3 位小数(e 表示法的指数位数取决于实现)。
调用 setf( ) 的效果可以通过 unsetf( ) 消除,后者的原型如下:
其中,mask 是位模式。mask 中所有的位都设置为 1,将使得对应的位被复位。也就是说,setf( ) 将位设置为 1,unsetf( ) 将位恢复为 0。例如:
您可能注意到了,没有专门指示浮点数默认显示模式的标记。系统的工作原理如下:仅当只有定点位被设置时使用定点表示法;仅当只有科学位被设置时使用科学表示法;对于其他组合,如没有位被设置或两位都被设置时,将使用默认模式。因此,启用默认模式的方法之一如下:
第二个参数关闭这两位,而第一个参数不设置任何位。一种实现同样目标的简捷方式是,使用参数 ios::floatfield 来调用函数 unsetf( ):
如果已知 cout 处于定点状态,则可以使用参数 ios_base::fixed 调用函数 unsetf( ) 来切换到默认模式;然而,无论 cout 的当前状态如何,使用参数 ios_base::floatfield 调用函数 unsetf( ) 都将切换到默认模式,因此这是一种更好的选择。
7.标准控制符
使用 setf( ) 不是进行格式化的、对用户最为友好的方法,C++提供了多个控制符,能够调用 setf( ),并自动提供正确的参数。前面已经介绍过 dec、hex 和 oct,这些控制符(多数都不适用于老式 C++实现)的工作方式都与 hex 相似。例如,下面的语句打开左对齐和定点选项:
表 17.3 列出了这些控制符以及其他一些控制符。
表 17.3 一些标准控制符
提示:
如果系统支持这些控制符,请使用它们;否则,仍然可以使用 setf( )。
8.头文件 iomanip
使用 iostream 工具来设置一些格式值(如字段宽度)不太方便。为简化工作,C++在头文件 iomanip 中提供了其他一些控制符,它们能够提供前面讨论过的服务,但表示起来更方便。3 个最常用的控制符分别是 setprecision( )、setfill( ) 和 setw( ),它们分别用来设置精度、填充字符和字段宽度。与前面讨论的控制符不同的是,这 3 个控制符带参数。setprecision( ) 控制符接受一个指定精度的整数参数;setfill( ) 控制符接受一个指定填充字符的 char 参数;setw( ) 控制符接受一个指定字段宽度的整数参数。由于它们都是控制符,因此可以用 cout 语句连接起来。这样,setw( ) 控制符在显示多列值时尤其方便。程序清单 17.10 演示了这一点,它对于每一行输出,都多次修改了字段宽度和填充字符,同时使用了一些较新的标准控制符。
注意:
有些 C++系统不自动搜索数学库。前面说过,有些 UNIX 系统要求使用如下命令选项来访问数学库:
程序清单 17.10 iomanip.cpp
下面是程序清单 17.10 中程序的输出:
现在可以生成几乎完全对齐的列了。使用 fixed 控制符导致显示末尾的 0。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论