从C到C++___使用cout进行输出

使用cout进行输出

C++使用了很多较为高级的语言特性来实现输入和输出,其中包括类、派生类、函数重载、虚函数、模板和多重继承。本文章注重看它们是怎么设计的,学习如何控制输入输出。

1.C++输入和输出概述

C++把输入和输出看作字节流。如下图所示:

还有一个概念是缓冲区,比如输入缓冲区和输出缓冲区。缓冲区里面存着要输出的内容或者读取的内容,程序可以对缓冲区的内容做操作,也可以控制缓冲区输出到哪,入口在哪等等。总之控制输入输出就是控制流和缓冲区。
我们之前写C++代码的时候,经常#include<iostream>。实际上这个文件中包含一些管理流和缓冲区的类。下面我们看一些I/O类:

  • streambuf类为缓冲区提供了内存,并提供了用于填充缓冲区、访问缓冲区内容、刷新缓冲区和管理缓冲区内存的类方法
  • ios_base类表示流的一般特征,如是否可读取、是二进制流还是文本流
  • ios类基于ios_bas,其中包括了一个指向streambuf对象的指针成员
  • ostream类是从ios类派生而来的,提供了输出方法
  • istream类也是从ios类派生而来的,提供了输入方法
  • iostream类是基于istreamostream的,因此继承了输入输出方法

iostream文件除了有类的定义,它还自动创建了8个流对象(4个用于窄字符流,4个用于宽字符流)

  • cin对象对应标准输入流。这个流关联的是标准输入设备(通常是键盘)。wcin对象和它类似,但是处理的是wchar_t类型的。
  • cout对象对应标准输出流。这个流关联的是标准输出设备(通常是显示器)。wcout对象和它类似,但是处理的是wchar_t类型的。
  • cerr对象对应标准错误流。这个流是用来显示错误信息的,关联的也是标准输出设备(通常是显示器)。这个流没有被缓冲,即信息直接发送给显示器。wcerr对象和它类似,但是处理的是wchar_t类型的。
  • clog对象也对应着标准错误流。这个流是用来显示错误信息的,关联的也是标准输出设备(通常是显示器)。这个流会被缓冲。wclog对象和它类似,但是处理的是wchar_t类型的。

我们完成了一个重要的事:对象就代表流。

cout<<"Bjarne free";
由于coutostream类的一个对象,ostream类中定义了operator<<()函数,上面那段代码将字符串"Bjarne free"和标准输出相连。总之,流的一端和程序相连,另一端和标准输出相连,cout对象凭借streambuf对象的帮助,管理流中的字节流。

  • 重定向

标准输入和输出流通常连着键盘和屏幕。但是很多操作系统都支持重定向。下面看一个例子:

#include<iostream>
int main()
{
    using std::cin;
    using std::cout;
    char a;
    int n=0;
    while (cin.get(a))
    {
        n++;
    }
    cout<<"the input contained "<<n<<" characters.";
    
}

这是一段很简单的代码,用来统计输入字符数的。我们将它制成C++可执行程序counter.exe,我们打开终端测试一下这个程序。

一开始,我们直接运行counter.exe,这个程序的标准输入流是键盘,我们就输入helloapple\nEOF,标准输出流是屏幕,就是显示的the input contained 11 characters.
我们再看第二个命令counter.exe < words.txt > num.txt,这里的<>就是重定向符号,<使得标准输入重定向到words.txt文件,>使得标准输出重定向到num.txt文件,那么这行命令的意思就是说,统计words.txt文件的字符数,然后把结果输出到num.txt中。

这说明操作系统支持重定向操作。同样的,C++也支持重定向。cout代表标准输出流,cerrclog代表标准错误流。对标准输出流重定向不会影响标准错误流。有些操作系统也允许对标准错误流做重定向,如UNIXlinux

2. 输出方式

C++将输出当作字节流。字节流就是一些二进制编码。例如double值是由64位二进制数据表示的。但是字节流发送给屏幕的时候,我们希望显示的是字符,而不是这些01二进制数字。所以说,ostream类最重要的任务之一就是把字节流转换成字符流,然后用ASCII码打印出来。

2.1 重载的<<运算符

我们经常把cout<<连在一块,但是你知道<<是什么吗?

<<运算符默认是左移运算符,这和C语言是一样的。但是ostream类重载定义了<<运算符使之支持输出,现在<<叫做插入运算符。

<<运算符能识别C++中所有的基本类型:

  • unsigned char;
  • signed char;
  • char;
  • short;
  • unsigned short;
  • int;
  • unsigned int;
  • long;
  • unsigned long;
  • long long;
  • unsigned long long;
  • float;
  • double;
  • long double;

所以说,对于上面每种数据类型,ostream类都提供了成员方法operator<<()
例如,cout<<88;cout对象调用了接口ostream & operator<<(int)

ostream类还为下面的指针类型定义了插入运算符函数:

  • const signed char*;
  • const unsigned char*;
  • const char*;
  • void*;

C++用指向字符串存储位置的指针来表示字符串。指针可以是char数组名,显式的char指针或用引号括起来的字符串。

char name[20]="Beyonce";
char * pn="Violet D'Amore";
cout<<"hello";
cout<<name;
cout<<pn;

方法使用字符串中的终止空字符’\0’来确定何时停止显示字符。
而对于其他类型的指针,C++会把他对应于void*,并打印地址的数值表示。

int eggs=12;
char* amount="dozen";
cout<<&eggs;
cout<<amount;//显示字符串
cout<<(void*)amount;//显示地址

插入运算符的返回类型是ostream &。原型格式是:

ostream & operator<<(type);

返回类型是ostream &,就是说它返回一个对象的引用,那么是哪个对象呢?调用对象。我们在之前说过,这样做的目的是支持拼接输出

例如:cout<<"I have "<<num<<"apples.";中表达式cout<<"I have "将会显示字符串,并返回cout对象,
然后原语句变为cout<<num<<"apples,";然后cout<<num显示num,并返回cout对象,然后语句变为cout<<"apples.";显示字符串。

这是一种很好的特性,我们之前为类重载<<时,都用到了这种特性。值得注意的时,我们自己设计类时,使用的时友元函数的方式重载<<;但是在ostream类中使用的是成员函数的方式。

2.2 put()write()

除了operator<<()函数外,ostream类还提供了put()write(),前者显示字符,后者显示字符串。

  • put()

最初它的原型是:

ostream& put(char);

目前它的标准和这个一样,但是已经被模板化,以适用wchar_t。我们可以这样使用它:cout.put('w');。当然它也支持拼接输出,所有也可以这样cout.put('I').put('t');,甚至可以这样cout.put('h')<<"ello";
我们也可以将数值参数(如int和double)用于put,

cout.put(65);//显示A
cout.put(66.3);//显示B

上面第一条语句把65转换成char值,然后显示ASCII码是65的字符。第二条语句把66.3转换成char值66,然后显示ASCII码是66的字符。

  • write()

其模板原型是:

basic_ostream<charT,traits>& write(const char_type* s,streamsize n);

write()的第一个参数是字符串的地址,第二个参数是显示多少字符。使用cout调用write()时,将调用char具体化,此时返回类型是ostream &

下面这段代码演示了write()如何工作:

#include<iostream>
#include<cstring>

int main()
{
    using std::cout;
    using std::endl;
    const char * state1="Florida";
    const char* state2="Kansas";
    const char* state3="Euphoria";
    int len=std::strlen(state2);
    for(int i=1;i<=len;i++)
        cout.write(state2,i)<<endl;
    for(int i=len;i>=1;i--)
        cout.write(state2,i)<<endl;
    
    cout<<"Exceeding string length:\n";
    cout.write(state2,len+5)<<endl;

    long val=560031841;
    cout.write((char*)&val,sizeof(long));
}
K
Ka
Kan
Kans
Kansa
Kansas
Kansas
Kansa
Kans
Kan
Ka
K
Exceeding string length:
KansasEuph
aha!

这里需要注意的是,write()方法不会再遇到空字符时,自动停止打印字符,而只是打印指定数目的字符,即使超出了字符串的边界,就行这里的cout.write(state2,len+5)就超出边界了,它把state3字符串的一部分内容打印出来。cout.write((char*)&val,sizeof(long));这段代码将数值指针强制转换成字符指针,然后显示字符串。

2.3 刷新输出缓冲区

输出时,程序首先填满缓冲区,然后把整块数据传输给硬盘(或其他设备),并清空缓冲区,以备下一批输出使用,这被称为刷新缓冲区

当程序使用cout将字节发送给标准输出时,由于ostream类对cout对象处理的输出进行缓冲,所以输出不会立即发送到目标地址,而是被存储在缓冲区中,直到缓冲区填满。然后程序将刷新(flush)缓冲区,把内容发送出去,并清空缓冲区,以存储新数据。通常缓冲区为512字节或其整数倍。当标准输出连接的是硬盘上的文件时,缓冲可以节省大量的时间。毕竟我们不希望发送512字节,存取硬盘512次。然而,对于屏幕输出来说,首先填充缓冲区,实在是太不方便了。幸运的是,在屏幕输出时,程序不必等到缓冲区被填满。例如,你可以将换行符发送到缓冲区,将刷新缓冲区。

除此之外,C++都会在输入即将发生的时候刷新缓冲区。例如:

cout<<"Enter a number: ";
float num;
cin>>num;

这里Enter a number:会立即显示出来,因为C++会在等待输入前立即刷新缓冲区

如果你发现你不能在所希望的时间刷新缓冲区,可以使用两个控制符中的一个强制进行刷新。flushendl,前者会立即刷新缓冲区,后者会插入一个换行符然后刷新缓冲区。

cout<<"Hello, good-looking! "<<flush;
cout<<"Wait a moment,please."<<endl;

实际上,你也可以这样flush(cout),它和cout<<flush;是等价的。

3.cout格式化输出

3.1 默认格式

ostream中的<<运算符将值转化成文本格式。在默认情况下,格式化值的方式如下:

  • char值,如果它是可打印字符,则将它显示在宽度是一个字符的字段中
  • 数值整型,将以十进制方式显示在一个刚好容纳该数字及负号(如果有的话)的字段中
  • 字符串被显示在宽度等于该字符串长度的字段中
  • 浮点数,浮点类型被显示为6位有效数字,但末尾的0不显示(注意,显示的数字位数与数字被存储时的精度没有任何关系)。数字以定点表示法还是科学计数法表示,取决于它的值。当指数大于等于6或小于等于-5时,使用科学计数法表示。字段的宽度恰好容纳数字和符号(如果有的话)
#include<iostream>

int main()
{
    using std::cout;
    cout<<"1234567890\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";
}
1234567890  
K:
273:        
-273:       
1.2:        
1.31111:    
167:        
167.111:    
1.67111e+06:
0.00023:    
2.3e-05:    

3.2 计数系统

ostream类是从ios类派生而来的,而ios类是从ios_base类派生而来的。ios_base存储了描述格式状态的信息。通过使用控制符可以控制显示整数时使用的计数系统。
计数系统有十进制(默认格式)、十六进制、八进制,分别使用dechexoct控制符。这些控制符位于名称空间std中。
例如,hex(cout);或者cout<<hex;可以让程序以十六进制打印整数值。而且这种设置是持久的,它会一直保持这种格式状态,直到重新设置

#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";
    cout<<hex;
    cout<<n<<" "<<n*n<<" (hexadecimal)\n";
    cout<<oct<<n<<" "<<n*n<<" (octal)\n";
    dec(cout);
    cout<<n<<" "<<n*n<<" (decimal)\n";
}
Enter an integer: 13
n n*n
13 169 (decimal)  
d a9 (hexadecimal)
15 251 (octal)    
13 169 (decimal)

3.3 调整字段宽度

在上面那个例子中,我们发现输出中各列没有对齐,这是因为数字的字段宽度不相同。我们可以使用ostream类的成员函数width(),所以得通过类对象来调用,例如cout.width(),该成员方法的原型是:

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

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

C++不会截短数据,所以你只能使用width()来增长字段。

//使用cout4.cpp

#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(int i=1;i<=100;i*=10)
    {
        cout.width(5);
        cout<<i<<":";
        cout.width(8);
        cout<<i*i<<":\n";
    }
}
        default field width = 0:
    N:   N * N:
    1:       1:
   10:     100:
  100:   10000:

在上述输出中,值在字段中是右对齐,这是默认的。输出中包含空格,也就是说,cout通过加入空格来填满整个字段。右对齐时,空格被插入到值的左侧。用来填充的字符叫做填充字符,默认是空格。

3.4 填充字符

在默认情况下,cout使用空格填充字段,可以使用fill()的成员函数来改变填充字符。
例如,cout.fill('*')会将填充字符改为星号。
新的填充字符会一直有效,直到更改它为止

//使用cout5.cpp
#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]<<std::endl;
    }
}
Waldo Whipsnade: $****900
Wilmarie Wooper: $***1350

3.5 设置浮点数的显示精度

浮点数的精度的含义取决于输出模式。浮点数的输出模式有:默认模式、定点模式、科学模式。在默认模式下,精度是指显示的总位数即有效数字,在定点和科学模式下,精度是指小数点后面的数字。当然浮点数末尾的零默认是不显示的。默认模式下,浮点数的精度是6,但是我们可以使用成员函数precision()使得能够选择其他值。
例如,cout.precision(2);会将精度设置成2。
新的精度设置将一直有效,直到被重新设置

//使用cout6.cpp
#include<iostream>
int main()
{
    using std::cout;
    float price1=20.40;
    float price2=1.9+8/9.0;
    cout<<"\"Furry Friends\" is $"<<price1<<"!\n";
    cout<<"\"Furry Friends\" is $"<<price2<<"!\n";

    cout.precision(2);
    cout<<"\"Furry Friends\" is $"<<price1<<"!\n";
    cout<<"\"Furry Friends\" is $"<<price2<<"!\n";
}
"Furry Friends" is $20.4!   
"Furry Friends" is $2.78889!
"Furry Friends" is $20!     
"Furry Friends" is $2.8!    

3.6 打印末尾的0和小数点

ostream中并没有保留0或者小数点的函数。但是它的间接基类ios_base中提供了setf()函数,能够控制多种格式化特性。ios_base中还定义了多个常量,可用作该函数的参数。这些常量实际上就是类中公有部分的静态const量。
cout作为ostream类对象,它自然继承了间接基类ios_base的接口setf();
例如,cout.setf(ios_base::showpoint);能够使得cout显示末尾小数点。在默认浮点数模式下,上述语句还能显示末尾的0。
实际上,ios_base::showpoint也可以写成ostream::showpoint,因为公有继承继承了公有部分。

//使用cout7.cpp
#include<iostream>
int main()
{
    using std::cout;
    cout.setf(std::ios_base::showpoint);
    float price1=20.40;
    float price2=1.9+8/9.0;
    cout<<"\"Furry Friends\" is $"<<price1<<"!\n";
    cout<<"\"Furry Friends\" is $"<<price2<<"!\n";

    cout.precision(2);
    cout<<"\"Furry Friends\" is $"<<price1<<"!\n";
    cout<<"\"Furry Friends\" is $"<<price2<<"!\n";
}
"Furry Friends" is $20.4000!
"Furry Friends" is $2.78889!
"Furry Friends" is $20.!    
"Furry Friends" is $2.8!  

3.7 细说setf()

ios_base里面有一个受保护的数据成员—格式成员。它是32bit的数据,它的每一位都控制一种格式(即bitmask类型)。

  • 如何访问格式成员?

ios_base::flags()ios_base::setf()都可以访问格式成员。

flags()有两种原型:

fmtflags flags() const;
直接返回格式成员

fmtflags flags (fmtflags fmtfl);
将格式成员设置成fmtfl,并把设置前格式成员返回

这里的fmtflagsbitmask的别名,bitmask类型是一种用来存储各个位值的类型。它可以是整型、枚举、也可以是STL bitset容器,它的思想是每一个位都可以单独访问,都有字节的含义。

总之,flags()是一个很简单的接口,但是我们一般不使用它,因为它是对整个数据做操作的,而我们希望的是:能对格式成员进行位操作。(bitmask数据,每一位都有作用。),setf()就可以对格式成员做位操作,这才是我们介绍的重点。

setf()有两个原型:

fmtflags setf(fmtflags);
fmtflags setf(fmtflags,fmtflags);
  • 第一种用法:fmtflags setf (fmtflags fmtfl);

The first form sets the stream’s format flags whose bits are set in fmtfl, leaving unchanged the rest, as if a call to flags(fmtfl|flags()).

OK,很简单,setf(fmtfl)相当于是flags(fmtfl|flags())
如果我们想让格式状态的第11位变成1,那么我们就传入一个第11位是1的数字。当然这很硬核,然而我们不需要这样做,因为ios_base类已经提供了这些格式常量,即上一节所说的公有静态const量。

格式常量
常量格式常量
ios_base::boolalpha输入和输出bool值,可以设置成true或false
ios_base::showbase对于输出,使用C++基数前缀(0,0x)
ios_base::showpoint显示末尾小数点
ios_base::uppercase对于16进制输出,使用大写字母,E表示法
ios_base::showpos在正数前面加’+’
            flag constant                             display in binary
      ios_base::boolalpha:                   00000000000000000000000000000001
       ios_base::showbase:                   00000000000000000000001000000000
      ios_base::showpoint:                   00000000000000000000010000000000
      ios_base::uppercase:                   00000000000000000100000000000000
        ios_base::showpos:                   00000000000000000000100000000000

我们发现这些常量都是只有一位是1,其他位都是0的量,那么就是说 第一种setf()只对一位做操作。我们看看,格式成员的默认值是多少? 00000000000000000001000000000010 ,这里第一个1是指跳过空格和回车(对输入而言),第二个1是指使用10进制.

//使用cout8.cpp
#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);
    cout<<temperature<<endl;

    cout<<"For our programing friends, that's\n";
    cout<<std::hex<<temperature<<endl;
    cout.setf(ios_base::showbase);
    cout.setf(ios_base::uppercase);
    cout<<"or\n";
    cout<<temperature<<endl;
    cout<<"How "<<true<<"! oops ---How ";
    cout.setf(ios_base::boolalpha);
    cout<<"How "<<true<<endl;

}
Today's water temperature: +63    
For our programing friends, that's
3f
or
0X3F
How 0X1! oops ---How How true

注意!仅当采用十进制时,才会使用加号。C++将十六进制和八进制都视为无符号的,因此对它们,无序使用符号。

  • 第二种用法:fmtflags setf (fmtflags fmtfl, fmtflags mask);

The second form (2) sets the stream’s format flags whose bits are set in both fmtfl and mask, and clears the format flags whose bits are set in mask but not in fmtfl, as if a call to flags((fmtfl&mask)|(flags()&~mask)).

也就是说 setf (fmtfl,mask);flags((fmtfl&mask)|(flags()&~mask))等价。这个形式挺复杂的,同样的我们看看格式常量怎么说…

setf(long,long)的参数
第二个参数第一个参数含义
ios_base::basefieldios_base::dec使用基数10(十进制)
ios_base::oct使用基数8
ios_base::hex使用基数16
ios_base::floatfieldios_base::fixed使用定点计数法
ios_base::scientific使用科学计数法
ios_base::adjustfieldios_base::left使用左对齐
ios_base::right使用右对齐
ios_base::internal符号或基数前缀左对齐,值右对齐
      ios_base::basefield:                   00000000000000000000000001001010
            ios_base::dec:                   00000000000000000000000000000010
            ios_base::oct:                   00000000000000000000000001000000
            ios_base::hex:                   00000000000000000000000000001000
     ios_base::floatfield:                   00000000000000000000000100000100
          ios_base::fixed:                   00000000000000000000000000000100
     ios_base::scientific:                   00000000000000000000000100000000
    ios_base::adjustfield:                   00000000000000000000000010110000
           ios_base::left:                   00000000000000000000000000100000
          ios_base::right:                   00000000000000000000000010000000
       ios_base::internal:                   00000000000000000000000000010000

好了现在很清楚了,第一个参数都只有一位是1,而第二个参数相当于是清除位。就比如说,我使用十进制,那么第一个参数负责设置十进制,第二个参数负责把八进制和十六进制清除掉。

定点计数法是指使用格式123.4来表示浮点值,而不管数字的长度如何。
科学计数法是指使用格式1.234e+02来表示浮点值,而不管数字的长度如何。

在C++标准种,定点计数法和科学计数法都有以下两个特征:

  • 精度指的是小数位数,而不是总位数
  • 显示末尾的0

熟悉C语言的printf()函数的话,你就能看出来,浮点数的默认表示法就是%g说明符,定点计数法就是%f说明符,科学计数法就是%e

#include<iostream>
#include<cstdio>
int main()
{
    using std::printf;
    using std::cout;
    using std::endl;
    using std::ios_base;
    cout<<123.4<<endl;
    printf("%g\n",123.4);
    cout.setf(ios_base::fixed,ios_base::floatfield);
     cout<<123.4<<endl;
    printf("%f\n",123.4);
    cout.setf(ios_base::scientific,ios_base::floatfield);
    cout<<123.4<<endl;
    printf("%e\n",123.4);
}
123.4
123.4
123.400000
123.400000
1.234000e+02
1.234000e+02

setf()ios_base类的接口,由于它是ostream的基类,因此cout对象可以调用这个函数。
例如,要使用左对齐:
ios_base::fmtflags old=cout.setf(ios_base::left,ios_base::adjustfield);
要恢复以前的设置,可以这样做:
cout.setf(old,ios_base::adjustfield);cout.flags(old);

这里解释一下?为什么上面第一个式子可以恢复以前的设置:
因为,setf(setf (fmtfl,mask),mask)等于是啥都不做,稍微计算一下:

setf (fmtfl,mask)的返回值就是老的flags(),它做完后新的flags()(fmtfl&mask)|(flags()&~mask)然后开始计算setf(setf (fmtfl,mask),mask)后,新的flags()的值是:

(flags()&mask)|(((fmtfl&mask)|(flags()&~mask))&~mask)))
=(flags()&mask)|((fmtfl&mask&~mask)|(flags()&~mask&~mask))
=(flags()&mask)|(0|flags()&~mask)
=(flags()&mask)|(flags()&~mask)
=flags()

OK,只能说setf()的第二种用法很巧妙了。

/使用cout9.cpp
#include<iostream>
#include<cmath>

int main()
{
    using namespace std;
    //使用左对齐,显示正负号,小数点,精度为3
    cout.setf(ios_base::left,ios_base::adjustfield);
    cout.setf(ios_base::showpos);
    cout.setf(ios_base::showpoint);
    cout.precision(3);
    //使用e-notation,并且保存旧形式即浮点数默认表示形式
    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";
    }
    //将左对齐改成使用internal对齐
    cout.setf(ios_base::internal,ios_base::adjustfield);
    //使用默认浮点数显示方式
    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";
    }
    //把对齐方式改成右对齐
    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";
    }
}
Left Justification:
+1  |+1.000e+00  |
+11 |+3.317e+00  |
+21 |+4.583e+00  |
+31 |+5.568e+00  |
+41 |+6.403e+00  |
Internal justification:
+  1|+       1.00|
+ 11|+       3.32|
+ 21|+       4.58|
+ 31|+       5.57|
+ 41|+       6.40|
Right Justification:
  +1|      +1.000|
 +11|      +3.317|
 +21|      +4.583|
 +31|      +5.568|
 +41|      +6.403|

注意到精度3让默认的浮点显示,即有效数字是3位,而科学计数法和定点计数法却是保留3位小数。

3.8 细说unsetf()

调用setf()的效果可以通过unsetf()消除。unsetf()的原型是:

void unsetf(fmtflags mask);

Clears the format flags selected in mask.

也就是unsetf(mask)相当于是flags(flags()&~mask);

如果setf()是第一种用法,那么unsetf()可以这么用:

cout.setf(ios_base::showpoint);
cout.unsetf(ios_base::showpoint);
cout.setf(ios_base::boolalpha);
cout.setf(ios_base::boolalpha);

如果setf()是第二种用法,那么unsetf()可以这么用:

cout.setf(ios_base::fixed,ios_base::floatfield);
cout.unsetf(ios_base::floatfield);

总之,无论当前输出格式怎么样,使用unsetf(ios_base::floatfield);都会使浮点数输出格式变为默认格式

3.7和3.8的内容我是直接参照<ios_base>的使用手册做的表述。ios_base的使用手册,这里有更详细的表述。

3.9 标准控制符

使用setf()不是进行格式化的最好方式。C++为用户提供了多个控制符,这些控制符会调用setf(),并自动提供正确的参数。我们在前``面已经介绍过一些控制符了,dechexoctendlflush
cout<<left<<fixed;这句话会使用左对齐,并使用定点计数法.

一些标准控制符
控制符调用
boolalphasetf(ios_base::boolalpha)
noboolalphaunsetf(ios_base::boolalpha)
showbasesetf(ios_base::showbase)
noshowbaseunsetf(ios_base::showbase)
showpointsetf(ios_base::showpoint)
noshowpointunsetf(ios_base::showpoint)
showpossetf(ios_base::showpos)
noshowposunsetf(ios_base::showpos)
uppercasesetf(ios_base::uppercase)
nouppercaseunsetf(ios_base::uppercase)
internalsetf(ios_base::internal,ios_base::adjustfield)
leftsetf(ios_base::left,ios_base::adjustfield)
rightsetf(ios_base::right,ios_base::adjustfield)
decsetf(ios_base::dec,ios_base::basefield)
hexsetf(ios_base::hex,ios_base::basefield)
octsetf(ios_base::oct,ios_base::basefield)
fixedsetf(ios_base::fixed,ios_base::floatfield)
scientificsetf(ios_base::scientific,ios_base::floatfield)

3.10 头文件iomanip

使用iostream工具来设置一些格式值(如字段宽度)不太方便。为简化工作,C++在头文件iomanip中提供了其他控制符。三个最常用的控制符使setprecision()setfill()setw()。和标准控制符不一样,这些控制符是带参数的。setprecision()接受一个整数参数以设置精度;setfill()接受一个char参数以设置填充字符、setw()接受一个整数参数以设置字宽。

//使用cout10.cpp
#include<iostream>
#include<iomanip>
#include<cmath>
int main()
{
    using namespace std;
    cout<<fixed<<right;
    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;
    }

}
     N   square root   fourth root
....10       3.162        1.7783  
....20       4.472        2.1147
....30       5.477        2.3403
....40       6.325        2.5149
....50       7.071        2.6591
....60       7.746        2.7832
....70       8.367        2.8925
....80       8.944        2.9907
....90       9.487        3.0801
...100      10.000        3.1623

值得注意的是,这里的setw()width()一样值影响接下来显式的一个项目,然后字段宽度恢复为默认。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值