C++ 格式控制 setf、setiosflags等详解及避坑

C++提供的输出格式,可以简单理解成一个个标志位。我们对输出格式的控制,其实就是将相应的标志位打开、关闭和设置。内部的实现原理,不必太过深究,会用就行。但需要注意,有些标志位只需要打开或者关闭即可,比如左对齐、右对齐,但是,有些标志位则是需要具体数值或者字符,比如设置宽度和填充字符。

对这些标志位的修改(打开或关闭),因为历史原因,方式有很多。究竟哪种方式好,没有定论。但是我们在写程序时,尽量使用同一种设置方式。学习的时候,也只需要学习一种最适合你的即可。

想要控制输出格式,C++提供的方法很多,有人将其分为两种:一种是控制流的成员函数设置,另一种是利用控制符设置函数setiosflags、setfill、setw等。

但是我认为分为三种更合适,setiosflags应该单独作为一种,且最不推荐,为何,后面会做解释。

1、用cout的成员函数进行格式设置

这种方式,需用用到setf函数,该函数有两个版本,一个只含一个参数,另一个含有两个参数。参数为各个控制位对应的枚举值,即我们经常见到的 std::ios_base::left、std::ios_base::showbase等。其对应的取消设置函数为unsetf。

例如:想指定宽度为10,左对齐,不足位用‘*’填充,用十六进制打印出来,且前面要输出进制的前缀。则可以如下:

int nValue = 10;
cout.width(10);
cout.fill('*');
cout.setf(std::ios_base::showbase); //可以用"|"运算符
cout.setf(std::ios_base::hex, std::ios_base::basefield);
cout.setf(std::ios_base::left, std::ios_base::adjustfield);
cout << nValue << endl;

执行结果为:

setf的注意事项

C++将控制符分为了两类:

第一类是独立的控制符号,如showbase、showpos等,这些,用setf只有一个参数的版本;

另一类,为组合控制符号,如hex、left等,这些,需要用setf两个参数的版本:第一个参数为left等,第二个参数为其对应的组合值如adjustfield等,如下:

如上面的例子,想设置左对齐和十六进制,只能:
cout.setf(std::ios_base::left, std::ios_base::adjustfield);
cout.setf(std::ios_base::hex, std::ios_base::basefield);

2、用setiosflags函数设置相应控制位

想使用该函数,需要包含头文件#include

setiosflags函数有一定的限制,只能打开和关闭相应标志位,对于那些需要提供具体数值或者字符的格式,有些无能为力。

其功能,可以理解成和setf类似,只不过它不是cout的成员函数,位于std名称空间,用起来比setf要略微方便一些。

但是!其只有setf单参数有效的控制位有用,比如上面的hex,就没有效果。

setiosflags函数的参数和setf一样,可以用"|"运算符指定多个,也可以连续设置。

上面的例子,将左对齐改为从中间填充,可以用如下代码实现:

using std::cout;
using std::endl;
int nValue = 10;
cout.width(10);
cout.fill('*');
cout.setf(std::ios_base::hex, std::ios_base::basefield);
// cout << std::setiosflags(std::ios_base::showbase | std::ios_base::internal); //与下面一行代码效果相同
cout << std::setiosflags(std::ios_base::showbase) << std::setiosflags(std::ios_base::internal);
cout << nValue << endl;

执行结果为:

为什么单独列出来,因为这个标志位设置函数,有个巨大坑。

setiosflags的局限性

前面已经介绍了控制符号分为两类,setiosflags函数只能设置独立的控制符,即setf单参数版本设置的那些控制符。其他的组合控制符,查阅了很多资料,它好像无能为力。

所以,这里将setiosflags单独列出来,作为一个警告!

3、直接用控制符设置(本人最习惯)

除了以上两种方式外,为了更加便捷操作,可以直接使用控制符。

控制符,也需要加上头文件#include

实现2中的效果,可以如下实现:

using std::cout;
using std::endl;
int nValue = 10;
cout << std::setw(10) << std::setfill('*');
cout << std::hex << std::showbase << std::left;
cout << nValue << endl;

执行结果为:

甚至,所有格式控制和最后的nValue输出,可以合并成一行:

cout << std::setw(10) << std::setfill('*') << std::hex << std::showbase << std::internal << nValue << endl;

是不是很方便?

下面,开始介绍各个控制符的具体含义。

考虑到现在越来越多的程序开始使用宽字节(wchar_t)进行编码,所以后面的例子中将会使用wcout而不是cout。对于格式控制,不管是wcout还是cout,都完全一致。

以下代码如果想要copy至本地编译,请加上两行代码: using std::endl; using std::wcout;

(1)setw、setfill、left、internal、right、showpos、noshowpos

这七个控制符里面,前五个是使用频率最高的,后两个是为了说明internal加到此处的。

这里面,只有setw设置后仅对下一个项目(可以理解成下一个有效输出)有效,其他的控制符一经设置一直有效,直到修改与其关联的控制符。

setw:设置宽度,为函数,参数为int型数值;

setfill:设置填充字符,为函数,参数为字符,默认填充字符是空格;

left:左对齐,即数字显示在左边,不够的再用setfill设置的字符填充;

right:右对齐,即数字显示在右边,不够的再在左侧用setfill设置的字符填充;默认对齐方式是右对齐;

internal:填充字符放到符号(正负号)和数字中间;

showpos:显示非负数字前的正号(+,pos为positive的缩写);

noshowpos:不显示非负数字前的正号,默认的是不显示。

wcout << L"----------------setw、setfill、left、internal、right、showpos、noshowpos----------------" << endl;
double dValue1 = -23.457;
double dValue2 = 3.56;
wcout << std::setfill(L'*');
int nWidth = 10;
wcout << std::setw(nWidth) << dValue2 << endl;
wcout << std::setw(nWidth) << std::left << dValue1 << endl;
wcout << std::setw(nWidth) << std::right << dValue1 << endl;
wcout << std::setw(nWidth) << std::internal << std::showpos << dValue2 << endl;
wcout << std::right << std::noshowpos;

执行结果:

(2)setprecision、fixed、scientific

setprecision:设置数字精度;

fixed:浮点数设置成普通的小数显示;

scientific:浮点数用科学计数法。

首先看一下fixed和scientific,普通的小数表示和科学计数法,很简单。

double dValue = 2563.54785243568914255;
wcout << dValue << endl;
wcout << std::scientific << dValue << endl;

执行结果为:

setprecision需要注意

setprecision,也有一些需要注意的地方,看如下代码:

double d1 = 2.222222;
double d2 = 55.555555;
wcout << std::setprecision(3);
wcout << d1 << endl;
wcout << d2 << endl;

执行结果为:

是不是与期待的有点不同?很可能是希望保留两位小数,结果55.555555成了55.6。

这里,setprecision设置的是整个数值的有效位数,而不是小数部分的有效位数。

想要设置固定的有效小数位,怎么办呢?与std::fixed控制符一起使用即可。如下:

double d1 = 2.222222;
double d2 = 55.555555;
wcout << std::setprecision(3) << std::fixed;
wcout << d1 << endl;
wcout << d2 << endl;

执行结果为:

保留三位小数,注意,是四舍五入原则

(3)showpoint、noshowpoint

showpoint:对于小数部分为0的浮点数,显示小数点,并且根据设置的精度,补充小数末尾部分的0;

noshowpoint:与showpoint相反

这个控制符也有一些需要注意,如果设置了setprecision和std::fixed(其实只需要设置std::fixed即可,setprecision有默认值,一般为6),即便不设置showpoint,也会显示小数末尾的0。但是仅设置setprecision不行,如:

double dValue = 22.1;
wcout << std::setprecision(5) << dValue << endl; //仅设置了setprecision,不会显示末尾0
wcout << std::fixed << dValue << endl;

执行结果为:

如果未设置std::fixed,仅设置std::setprecision,想要显示小数末尾部分的0,则可以用std::showpoint。

需要注意,此时的setprecision表示的精度,不是小数部分,而是整个数值。如下:

double dValue = 22.1;
wcout << std::setprecision(5) << std::showpoint << dValue << endl;

执行结果如下:

(4)oct、dec、hex、showbase以及noshowbase

oct:八进制,前缀为:0;

dec:十进制,没有前缀,为进制的默认值;

hex:十六进制,前缀为:0x

showbase:显示进制的前缀;

noshowbase:不显示进制前缀。

这些控制符,都是一经设置一直有效。

int nValue_dec = 10;
wcout << std::oct << nValue_dec << endl; //八进制,默认不显示前缀
wcout << std::showbase << nValue_dec << endl; //打开前缀,还是输出上一行设置的八进制
wcout << std::hex << nValue_dec << endl; //十六进制,已设置过现实显示前缀
wcout << std::dec << nValue_dec << endl; //十进制,没有前缀
wcout << std::noshowbase << nValue_dec << endl;

执行结果:

(5)boolalpha、noboolalpha

boolalpha:将bool类型的变量输出为'true'或者'false'字样。

boolalpha:将bool类型的变量输出为1和0字样,默认为noboolalpha。

wcout << L"---------------------------boolalpha、noboolalpha----------------------------------" << endl;
bool bTrue = true;
bool bFalse = false;
wcout << bTrue << endl; //1
wcout << bFalse << endl; //0
wcout << std::boolalpha << bTrue << endl; //true
wcout << bFalse << endl; //false
wcout << std::noboolalpha << bTrue << endl; //1
执行结果:

(6)uppercase、nouppercase

uppercase:对于进制前缀、十六进制a-f和浮点数科学计数法的字母e,均显示大写字母;

nouppercase:对于进制前缀、十六进制a-f和浮点数科学计数法的字母e,均显示小写字母,为默认值;

注意:对其他的(包括浮点数的true和false)无影响。

int nValue = 10;
bool bValue = true;
double dValue = 2.354;
wcout << std::boolalpha << std::hex << std::showbase << std::scientific;
wcout << nValue << endl;
wcout << bValue << endl;
wcout << dValue << endl;
wcout << std::uppercase;
wcout << nValue << endl;
wcout << bValue << endl;
wcout << dValue << endl;
wcout << std::nouppercase;

执行结果为: