使用cout进行输出
C++使用了很多较为高级的语言特性来实现输入和输出,其中包括类、派生类、函数重载、虚函数、模板和多重继承。本文章注重看它们是怎么设计的,学习如何控制输入输出。
1.C++输入和输出概述
C++把输入和输出看作字节流。如下图所示:
还有一个概念是缓冲区,比如输入缓冲区和输出缓冲区。缓冲区里面存着要输出的内容或者读取的内容,程序可以对缓冲区的内容做操作,也可以控制缓冲区输出到哪,入口在哪等等。总之控制输入输出就是控制流和缓冲区。
我们之前写C++代码的时候,经常#include<iostream>
。实际上这个文件中包含一些管理流和缓冲区的类。下面我们看一些I/O类:
streambuf
类为缓冲区提供了内存,并提供了用于填充缓冲区、访问缓冲区内容、刷新缓冲区和管理缓冲区内存的类方法ios_base
类表示流的一般特征,如是否可读取、是二进制流还是文本流ios
类基于ios_bas
,其中包括了一个指向streambuf
对象的指针成员ostream
类是从ios
类派生而来的,提供了输出方法istream
类也是从ios
类派生而来的,提供了输入方法iostream
类是基于istream
和ostream
的,因此继承了输入输出方法
iostream
文件除了有类的定义,它还自动创建了8个流对象(4个用于窄字符流,4个用于宽字符流)
cin
对象对应标准输入流。这个流关联的是标准输入设备(通常是键盘)。wcin
对象和它类似,但是处理的是wchar_t
类型的。cout
对象对应标准输出流。这个流关联的是标准输出设备(通常是显示器)。wcout
对象和它类似,但是处理的是wchar_t
类型的。cerr
对象对应标准错误流。这个流是用来显示错误信息的,关联的也是标准输出设备(通常是显示器)。这个流没有被缓冲,即信息直接发送给显示器。wcerr
对象和它类似,但是处理的是wchar_t
类型的。clog
对象也对应着标准错误流。这个流是用来显示错误信息的,关联的也是标准输出设备(通常是显示器)。这个流会被缓冲。wclog
对象和它类似,但是处理的是wchar_t
类型的。
我们完成了一个重要的事:对象就代表流。
cout<<"Bjarne free";
由于cout
是ostream
类的一个对象,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
代表标准输出流,cerr
和clog
代表标准错误流。对标准输出流重定向不会影响标准错误流。有些操作系统也允许对标准错误流做重定向,如UNIX
和linux
。
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++会在等待输入前立即刷新缓冲区
如果你发现你不能在所希望的时间刷新缓冲区,可以使用两个控制符中的一个强制进行刷新。flush
和endl
,前者会立即刷新缓冲区,后者会插入一个换行符然后刷新缓冲区。
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
存储了描述格式状态的信息。通过使用控制符可以控制显示整数时使用的计数系统。
计数系统有十进制(默认格式)、十六进制、八进制,分别使用dec
、hex
、oct
控制符。这些控制符位于名称空间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
,并把设置前格式成员返回
这里的
fmtflags
是bitmask
的别名,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))
等价。这个形式挺复杂的,同样的我们看看格式常量怎么说…
第二个参数 | 第一个参数 | 含义 |
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::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()
,并自动提供正确的参数。我们在前``面已经介绍过一些控制符了,dec
、hex
、oct
、endl
、flush
。
cout<<left<<fixed;
这句话会使用左对齐,并使用定点计数法.
控制符 | 调用 |
---|---|
boolalpha | setf(ios_base::boolalpha) |
noboolalpha | unsetf(ios_base::boolalpha) |
showbase | setf(ios_base::showbase) |
noshowbase | unsetf(ios_base::showbase) |
showpoint | setf(ios_base::showpoint) |
noshowpoint | unsetf(ios_base::showpoint) |
showpos | setf(ios_base::showpos) |
noshowpos | unsetf(ios_base::showpos) |
uppercase | setf(ios_base::uppercase) |
nouppercase | unsetf(ios_base::uppercase) |
internal | setf(ios_base::internal,ios_base::adjustfield) |
left | setf(ios_base::left,ios_base::adjustfield) |
right | setf(ios_base::right,ios_base::adjustfield) |
dec | setf(ios_base::dec,ios_base::basefield) |
hex | setf(ios_base::hex,ios_base::basefield) |
oct | setf(ios_base::oct,ios_base::basefield) |
fixed | setf(ios_base::fixed,ios_base::floatfield) |
scientific | setf(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()
一样值影响接下来显式的一个项目,然后字段宽度恢复为默认。