C++ primer plus 第17 章 输入、输出和文件:用cout进行格式化

C++ primer plus 第17 章 输入、输出和文件:用cout进行格式化

C++ primer plus 第17 章 输入、输出和文件:用cout进行格式化


17.2.4 用cout进行格式化

ostream 插入运算符将值转换为文本格式。在默认情况下,格式化值的方式如下。

  • 对于char 值,如果它代表的是可打印字符,则将被作为一个字符显示在宽度为一个字符的字段中。
  • 对于数值整型,将以十进制方式显示在一个刚好容纳该数字及负号(如果有的话)的字段中。
  • 字符串被显示在宽度等于该字符串长度的字段中。浮点数的默认行为有变化。下面详细说明了老式实现和新实现之间的区别。
  • 新式:浮点类型被显示为6位,末尾的0不显示(注意,显示的数字位数与数字被存储时精度没有任何关系)。数字以定点表示法显示还是以科学计数法表示(参见第3章),取决于它的值。具体来说,当指数大于等于6或小于等于-5时,将使用科学计数法表示。另外,字段宽度恰好容纳数字和负号(如果有的话)。默认的行为对应于带%g说明符的标准C库函数fprintt()。
  • 老式:浮点类型显示为带6位小数,末尾的0不显示(注意,显示的数字位数与数字被存储时的精度没有任何关系)。数字以定点表示法显示还是以科学计数法表示(参见第3章),取决于它的值。另外,字段宽度恰好容纳数字和负号(如果有的话)。

因为每个值的显示宽度都等于它的长度,因此必须显式地在值之间提供空格:否则,相邻的值将不会被分开
程序清单 17.2演示默认的输出情况,它在每个值后面都显示一个冒号(😃,以便可以知道每种情况下的字段宽度。该程序使用表达式1.0/9.0来生成一个无穷小数,以便能够知道打印了多少位。

·注意:并非所有的编译器都能生成符合当前C++标准格式的输出。另外,当前标准允许区域性变化。例如,欧洲实现可能遵循欧洲人的风格:使用逗号而不是句点来表示小数点。也就是说,2.54将被写成2,54。区域库(头文件locale)提供了用特定的风格影响(imbuing)输入或输出流的机制,所以同一个编译器能够提供多个区域选项。本章使用美国格式·。

程序清单 17.2 defaults.cpp

// defaults.cpp -- cout default formats
#include <iostream>

int main()
{
    using std::cout;
    cout << "12345678901234567890\n";
    char ch = 'K';
    int t = 273;
    cout << ch << ":\n";
    cout << t << ":\n";
    cout << -t <<":\n";

    double f1 = 1.200;
    cout << f1 << ":\n";
    cout << (f1 + 1.0 / 9.0) << ":\n";

    double f2 = 1.67E2;
    cout << f2 << ":\n";
    f2 += 1.0 / 9.0;
    cout << f2 << ":\n";
    cout << (f2 * 1.0e4) << ":\n";


    double f3 = 2.3e-4;
    cout << f3 << ":\n";
    cout << f3 / 10 << ":\n";
    // std::cin.get();
    return 0; 
}

在这里插入图片描述

每个值都填充自己的字段。注意,1.200末尾的0没有显示出来,但末尾不带0的浮点值后面将有6个空格。另外,该实现将指数显示为3位,而其他实现可能为两位。

1.修改显示时使用的计数系统

ostrcam 类是从ios 类派生而来的,而后者是从ios base 类派生而来的。ios base 类存储了描述格式状态的信息。例如,一个类成员中某些位决定了使用的计数系统,而另一个成员则决定了字段宽度。通过使用控制符(manipulator),可以控制显示整数时使用的计数系统。通过使用iosbase的成员函数,可以控制字段宽度和小数位数。由于ios base类是 ostream的间接基类,因此可以将其方法用于ostream 对象(或子代),如 cout。
·注意:ios base 类中的成员和方法以前位于ios类中。现在,ios basc是ios 的基类。在新系统中,ios是包含 char 和 wchart具体化的模板,而ios base 包含了非模板特性。·
来看如何设置显示整数时使用的计数系统。要控制整数以十进制、十六进制还是八进制显示,可以使用dec、hex和oct控制符。例如,下面的函数调用将cout对象的计数系统格式状态设置为十六进制:
hex(cout);

完成上述设置后,程序将以十六进制形式打印整数值,直到将格式状态设置为其他选项为止。注意,控制符不是成员函数,因此不必通过对象来调用。
虽然控制符实际上是函数,但它们通常的使用方式为:

cout << hex;

ostream 类重载了<<运算符,这使得上述用法与函数调用hex(cout)等价。控制符位于名称空间 std中。程序清单17.3演示了这些控制符的用法,它以3种不同的计数系统显示了一个整数的值极其平方。注可以单独使用控制符,也可将其作为一系列插入的组成部分。意,

程序清单17.3 manip.Cpp

// manip.cpp -- using format manipulators
#include <iostream>
int main()
{
    using namespace std;
    cout << "Enter an integer: ";
    int n;
    cin >> n;

    cout << "n     n*n\n";
    cout << n << "     " << n * n << " (decimal)\n";
// set to hex mode
    cout << hex;
    cout << n << "     ";
    cout << n * n << " (hexadecimal)\n";

// set to octal mode
    cout << oct << n << "     " << n * n << " (octal)\n";

// alternative way to call a manipulator
    dec(cout);
    cout << n << "     " << n * n << " (decimal)\n";
    // cin.get();
    // cin.get();
    return 0; 
}

在这里插入图片描述

2.调整字段宽度

您可能已经注意到,在程序清单17.3的输出中各列并没有对齐,这是因为数字的字段宽度不相同。可以使用width成员函数将长度不同的数字放到宽度相同的字段中,该方法的原型为:

int widch();
int width(int i);

第一种格式返回字段宽度的当前设置;第二种格式将字段宽度设置为i个空格,并返回以前的字段宽度值。这使得能够保存以前的值,以便以后恢复宽度值时使用。
width()方法只影响将显示的下一个项目,然后字段宽度将恢复为默认值。例如,请看下面的语句:

 
Cout << '#';
cout.width(12);
cout << 12 <<“#”<24 << “#\n";

由于 width()是成员函数,因此必须使用对象(这里为cout)来调用它。输出语句生成的输出如下:#12井24#
12被放到宽度为12个字符的字段的最右边,这被称为右对齐。然后,字段宽度恢复为默认值,并将两个#符号以及24放在宽度与它们的长度相等的字段中。
警告:width()方法只影响接下来显示的一个项目,然后字段宽度将恢复为默认值。
C++永远不会截短数据,因此如果试图在宽度为2的字段中打印一个7位值,C++将增宽字段,以容纳该数据(在有些语言中,如果数据长度与字段宽度不匹配,将用星号填充字段。C/C++的原则是:显示所有的数据比保持列的整洁更重要。C++视内容重于形式)。程序清单17.4演示了width()成员函数是如何工作的。

程序清单 17.4 width.cpp

// width.cpp -- using the width method
#include <iostream>

int main()
{
    using std::cout;
    int w = cout.width(30);
    cout << "default field width = " << w << ":\n";

    cout.width(5);
    cout << "N" <<':';
    cout.width(8);
    cout << "N * N" << ":\n";

    for (long i = 1; i <= 100; i *= 10)
    {
        cout.width(5);
        cout << i <<':';
        cout.width(8);
        cout << i * i << ":\n";
    }
    // std::cin.get();
    return 0; 
}

在这里插入图片描述
在上述输出中,值在字段中右对齐。输出中包含空格,也就是说,cout通过加入空格来填满整个字段。右对齐时,空格被插入到值的左侧。用来填充的字符叫做填充字符(fillcharacter)。右对齐是默认的。注意,在程序清单17.4中,第一条cout语句显示字符串时,字段宽度被设置为30,但在显示w的值时,字段宽度不是30。这是由于width()方法只影响接下来被显示的一个项目。另外,w的值为0。这是由于cout.width(30)返回的是以前的字段宽度,而不是刚设置的值。W为0表明,默认的字段宽度为0。由于C++总会增长字段,以容纳数据,因此这种值适用于所有的数据。最后,程序使用 width()来对齐列标题和数据,方法是将第1列宽度设置为5个字符,将第2列的宽度设置为8个字符。

3.填充字符

在默认情况下,cout用空格填充字段中未被使用的部分,可以用i()成员函数来改变填充字符。例下面的函数调用将填充字符改为星号:如.

cout.fi1l('*');

这对于检查打印结果,防止接收方添加数字很有用。程序清单17.5演示了该成员函数的用法。

程序清单 17.5 fill.cpp

// fill.cpp -- changing fill character for fields
#include <iostream>

int main()
{
    using std::cout;
    cout.fill('*');
    const char * staff[2] = { "Waldo Whipsnade", "Wilmarie Wooper"};
    long bonus[2] = {900, 1350};

    for (int i = 0; i < 2; i++)
    {
        cout << staff[i] << ": $";
        cout.width(7);
        cout << bonus[i] << "\n";
    }
    // std::cin.get();
    return 0; 
}

在这里插入图片描述
注意,与字段宽度不同的是,新的填充字符将一直有效,直到更改它为止。

4.设置浮点数的显示精度

浮点数精度的含义取决于输出模式。在默认模式下,它指的是显示的总位数。在定点模式和科学模式下(稍后将讨论),精度指的是小数点后面的位数。已经知道,C++的默认精度为6位(但末尾的0将不显示)。precision()成员函数使得能够选择其他值。例如,下面语句将cout的精度设置为2:

cout.precision(2);

和 width()的情况不同,但与 f()类似,新的精度设置将一直有效,直到被重新设置。程序清单 17.6准确地说明了这一点。

程序清单17.6 precise.cpp

// precise.cpp -- setting the precision
#include <iostream>

int main()
{
    using std::cout;
    float price1 = 20.40;
    float price2 = 1.9 + 8.0 / 9.0;

    cout << "\"Furry Friends\" is $" << price1 << "!\n";
    cout << "\"Fiery Fiends\" is $" << price2 << "!\n";

    cout.precision(2);
    cout << "\"Furry Friends\" is $" << price1 << "!\n";
    cout << "\"Fiery Fiends\" is $" << price2 << "!\n";
	// std::cin.get();
    return 0; 
}

在这里插入图片描述
注意,第3行没有打印小数点及其后面的内容。另外,第4行显示的总位数为2位。

5.打印末尾的0和小数点

对于有些输出(如价格或栏中的数字),保留末尾的0将更为美观。例如,对于程序清单17.6的输出,S20.40 将比S20.4更美观。iostream 系列类没有提供专门用于完成这项任务的函数,但ios base 类提供了一个set)函数(用于set 标记),能够控制多种格式化特性。这个类还定义了多个常量,可用作该函数的参数。例如,下面的函数调用使cout 显示末尾小数点:

cout.setf(ios base::showpoint);

使用默认的浮点格式时,上述语句还将导致末尾的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

// showpt.cpp -- setting the precision, showing trailing point
#include <iostream>

int main()
{
    using std::cout;
    using std::ios_base;

    float price1 = 20.40;
    float price2 = 1.9 + 8.0 / 9.0;

    cout.setf(ios_base::showpoint);
    cout << "\"Furry Friends\" is $" << price1 << "!\n";
    cout << "\"Fiery Fiends\" is $" << price2 << "!\n";

    cout.precision(2);
    cout << "\"Furry Friends\" is $" << price1 << "!\n";
    cout << "\"Fiery Fiends\" is $" << price2 << "!\n";
	// std::cin.get();
    return 0; 
}

在这里插入图片描述
在上述输出中,第一行显示了;第三行显示了小数点,但没有显示末尾的0,这是因为精度被设置为 2,而小数点前面已经包含两位。

6.再谈 setf()

set()方法控制了小数点被显示时其他几个格式选项,因此来仔细研究一下它。ios base 类有一个受保护的数据成员,其中的各位(这里叫作标记)分别控制着格式化的各个方面,例如计数系统、是否显示末尾的0等。打开一个标记称为设置标记(或位),并意味着相应的位被设置为1。位标记是编程开关,相当于设置 DIP开关以配置计算机硬件。例如,hex、dec和 oct 控制符调整控制计数系统的3个标记位。setf()函数提供了另一种调整标记位的途径。
sett()函数有两个原型。第一个为:
fmtflags setf(fmtflags);其中,fimtfags是 bitmask类型(参见后面的“注意”)的typedef名,用于存储格式标记。该名称是在ios base 类中定义的。这个版本的set()用来设置单个位控制的格式信息。参数是一个 ftags值,指出要设置哪一位。返回值是类型为fntags 的数字,指出所有标记以前的设置。如果打算以后恢复原始设置,则可以保存这个值。应给set()传递什么呢?如果要第11位设置为1,则可以传递一个第11位为1的数字。返回值的第11位将被设置为1。对位进行跟踪好像单调乏味(实际上也是这样)。然而,您不必作做这项工作,ios base 类定义了代表位值的常量,表17.1列出了其中的一些定义。
在这里插入图片描述
注意:bitmask 类型是一种用来存储各个位值的类型。它可以是整型、枚举,也可以是 STLbitset 容器。这里的主要思想是,每一位都是可以单独访问的,都有自己的含义。iostream 软件包使用 bitmask 来存储状态信息。由于这些格式常量都是在ios base 类中定义,因此使用它们时,必须加上作用域解析运算符。也就是说,应使用 ios_base :uppercase,而不是uppercase。如果不想使用using编译指令或 using声明,可以使用作用域运算符来指出这些名称位于名称空间std中。修改将一直有效,直到被覆盖为止。程序清单17.8演示了如何使用其中一些常量。

程序清单 17.8 setf.cpp

// setf.cpp -- using setf() to control formatting
#include <iostream>

int main()
{
    using std::cout;
    using std::endl;
    using std::ios_base;

    int temperature = 63;

    cout << "Today's water temperature: ";
    cout.setf(ios_base::showpos);    // show plus sign
    cout << temperature << endl;

    cout << "For our programming friends, that's\n";
    cout << std::hex << temperature << endl; // use hex
    cout.setf(ios_base::uppercase);  // use uppercase in hex
    cout.setf(ios_base::showbase);   // use 0X prefix for hex
    cout << "or\n";
    cout << temperature << endl;
    cout << "How " << true << "!  oops -- How ";
    cout.setf(ios_base::boolalpha);
    cout << true << "!\n";
	// std::cin.get();
    return 0; 
}

在这里插入图片描述
注意,仅当基数为10时才使用加号。C++将十六进制和八进制都视为无符号的,因此对它们,无需使用符号(然而,有些C++实现可能仍然会显示加号)。第二个 setf()原型接受两个参数,并返回以前的设置:

fmtflags setf(fmtflags,fmtflags);

函数的这种重载格式用于设置由多位控制的格式选项。第一参数和以前一样,也是一个包含了所需设置的 fmntfags值。第二参数指出要清除第一个参数中的哪些位。例如,将第3位设置为1表示以 10为基数,将第4位设置为1表示以8为基数,将第5位设置为1表示以16为基数。假设输出是以10为基数的,而要将它设置为以16为基数,则不仅需要将第5位设置为1,还需要将第3位设置为0–这叫作清除位(clearing the bit)。聪明的十六进制控制符可自动完成这两项任务。使用函数 set()时,要做的工作多些,因为要用第二参数指出要清除哪些位,用第一参数指出要设置哪位。这并不像听上去那么复杂,因为ios base 类为此定义了常量(如表17.2所示)。具体地说,要修改基数,可以将常量ios base:basefield用作第二参数,将ios base::hex用作第一参数。也就是说,下面的函数调用与使用十六进制控制符的作用相同:

cout.setf(ios base::hex,ios base::basefield);

在这里插入图片描述
在这里插入图片描述
ios_base 类定义了可按这种方式处理的3组格式标记。每组标记都由一个可用作第二参数的常量和两三个可用作第一参数的常量组成。第二参数清除一批相关的位,然后第一参数将其中一位设置为1。表 17.2列出了用作 set )的第二参数的常量的名称、可用作第一参数的相关常量以及它们的含义。例如,要选择左对齐,可将iosbase:adiustfeld用作第二参数,将iosbase::lc 作为第一参数。左对齐意味着将值放在字段的左端,右对齐则表示将值放在字段的右端。内部对齐表示将符号或基数前缀放在字段左侧,余下的数字放在字段的右侧(遗憾的是,C++没有提供自对齐模式)。
定点表示法意味着使用格式123.4来表示浮点值,而不管数字的长度如何,科学表示法则意味着使用格式 1.23c04,而不考虑数字的长度。如果您熟悉C语言中printf0)的说明符,则可能知道,默认的 C++模式对应于%g说明符,定点表示法对应于%f说明符,而科学表示法对应于%e 说明符。在 C++标准中,定点表示法和科学表示法都有下面两个特征:

  • 精度指的是小数位数,而不是总位数:

  • 显示末尾的0。
    set()函数是 ios base 类的一个成员函数。由于这个类是 ostream 类的基类,因此可以使用 cout 对象来调用该函数。例如,要左对齐,可使用下面的调用:

ios base::fmtflags oldcout.setf(ios::left, ios::adjustfield);

要恢复以前的设置,可以这样做:

cout.setf(old,ios::adjustfield);

程序清单 17.9是一个使用两个参数的set)的示例。
注意:程序清单 17.9中的程序使用了一个数学函数,有些C++系统不自动搜索数学库。例如,有些UNIX系统要求这样做:

$cc setf2.c -lm

-Im 选项命令链接程序搜索数学库。同样,有些使用g++的Linux系统也要求这样做。

程序清单 17.9 setf2.cpp

// setf2.cpp -- using setf() with 2 arguments to control formatting
#include <iostream>
#include <cmath>

int main()
{
    using namespace std;
   // use left justification, show the plus sign, show trailing
    // zeros, with a precision of 3
    cout.setf(ios_base::left, ios_base::adjustfield);
    cout.setf(ios_base::showpos);
    cout.setf(ios_base::showpoint);
    cout.precision(3);
    // use e-notation and save old format setting
    ios_base::fmtflags old = cout.setf(ios_base::scientific,
        ios_base::floatfield);
    cout << "Left Justification:\n";
    long n;
    for (n = 1; n <= 41; n+= 10)
    {
        cout.width(4);
        cout << n << "|";
        cout.width(12);
        cout << sqrt(double(n)) << "|\n";
    }

    // change to internal justification
    cout.setf(ios_base::internal, ios_base::adjustfield);
    // restore default floating-point display style
    cout.setf(old, ios_base::floatfield);

    cout << "Internal Justification:\n";
    for (n = 1; n <= 41; n+= 10)
    {
        cout.width(4);
        cout << n << "|";
        cout.width(12);
        cout << sqrt(double(n)) << "|\n";
    }

    // use right justification, fixed notation
    cout.setf(ios_base::right, ios_base::adjustfield);
    cout.setf(ios_base::fixed, ios_base::floatfield);
    cout << "Right Justification:\n";
    for (n = 1; n <= 41; n+= 10)
    {
        cout.width(4);
        cout << n << "|";
        cout.width(12);
        cout << sqrt(double(n)) << "|\n";
    }
	// std::cin.get();
    return 0; 
}

在这里插入图片描述
注意到精度3让默认的浮点显示(在这个程序中用于内部对齐)总共显示3位,而定点模式和科学模式只显示3位小数(e表示法的指数位数取决于实现)。
调用set()的效果可以通过 unsetf( )消除,后者的原型如下:


void unsetf(fmtflags mask);

其中,mask是位模式。mask中所有的位都设置为1,将使得对应的位被复位。也就是说,set)将位设置为1,unsetf()将位恢复为0。例如:

cout.setf(ios_base::showpoint);// show trailing decimal point
cout.unsetf(ios_base::boolshowpoint);// don't show trailing decimal point
cout.setf(ios_base::boolalpha);//display true, false
cout.unsetf(ios_base::boolalpha);//display 1,0

您可能注意到了,没有专门指示浮点数默认显示模式的标记。系统的工作原理如下:仅当只有定点位被设置时使用定点表示法:仅当只有科学位被设置时使用科学表示法:对于其他组合,如没有位被设置或两位都被设置时,将使用默认模式。因此,启用默认模式的方法之一如下:

cout.setf(0,ios_base::floatfield);//go to default mode

第二个参数关闭这两位,而第一个参数不设置任何位。一种实现同样目标的简捷方式是,使用参数ios::floatfield 米调用函数 unsetf( ):

cout.unsetf(ios_base::floatfield);//go to default mode

如果已知 cout 处于定点状态,则可以使用参数 ios base::ixed 调用函数 unsetf()来切换到默认模式:然而,无论cout 的当前状态如何,使用参数ios base::oatfield 调用函数unsetf()都将切换到默认模式,因此这是一种更好的选择。

7.标准控制符

使用 set()不是进行格式化的、对用户最为友好的方法,C++提供了多个控制符,能够调用set(),并自动提供正确的参数。前面已经介绍过dec、hex和oct,这些控制符(多数都不适用于老式 C++实现)的工作方式都与hex相似。例如,下面的语句打开左对齐和定点选项:

cout << left << fixed;

表 17.3 列出了这些控制符以及其他一些控制符。
在这里插入图片描述
在这里插入图片描述

8.头文件iomanip

使用 iostream 工具来设置一些格式值(如字段宽度)不太方便。为简化工作,C++在头文件iomanip中提供了其他一些控制符,它们能够提供前面讨论过的服务,但表示起来更方便。3个最常用的控制符分别是 setprecision()、setfil()和setw(),它们分别用来设置精度、填充字符和字段宽度。与前面讨论的控制符不同的是,这3个控制符带参数。setprecision()控制符接受一个指定精度的整数参数;setfi()控制符接受一个指定填充字符的 char 参数:sew()控制符接受一个指定字段宽度的整数参数。由于它们都是控制符,因此可以用 cout语句连接起来。这样,setw()控制符在显示多列值时尤其方便。程序清单 17.10 演示了这一点,它对于每一行输出,都多次修改了字段宽度和填充字符,同时使用了一些较新的标准控制符。
注意:有些C++系统不自动搜索数学库。前面说过,有些UNIX系统要求使用如下命令选项来访问数学库:$Cc iomanip.clm

程序清单17.10 iomanip.cpp

// iomanip.cpp -- using manipulators from iomanip
// some systems require explicitly linking the math library
#include <iostream>
#include <iomanip>
#include <cmath>

int main()
{
    using namespace std;
    // use new standard manipulators
    cout << fixed << right;

    // use iomanip manipulators
    cout << setw(6) << "N" << setw(14) << "square root"
         << setw(15) << "fourth root\n";

    double root;
    for (int n = 10; n <=100; n += 10)
    {
        root = sqrt(double(n));
        cout << setw(6) << setfill('.') << n << setfill(' ')
               << setw(12) << setprecision(3) << root
               << setw(14) << setprecision(4) << sqrt(root)
               << endl;
    }
	// std::cin.get();
    return 0; 
}

在这里插入图片描述
现在可以生成几乎完全对齐的列了。使用fixed控制符导致显示末尾的0

  • 13
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值