为IOStream平反​

很多Native的程序员钟情于printf / fprintf / …,
谈到C++的IOStream Library,大部分的意见都是:复杂、难用、不直接。其实,STL巨细靡遗、井然有序的特质,
在IOStream library里,体会的淋漓尽致,如果你想扩展OO的视野,那么IOStream*对是一颗沉睡的宝珠。 ​

一些基本概念​

  1. 流对象 Stream object​

C++里的IO是用“流”的概念来表示的。
所谓的“流”,就是一串字节序列。输出就是“流入”stream object,输入就是“流出”stream object。
而stream object,则可以理解为是一种具象的“流”的容器。 ​

  1. 流类 Stream class​

IO自身就种类繁多(输入、输出、文件操作),所以IOStream library也提供了不同的类来封装不同类型的IO。
其中*重要的两种:​

    • istream: 用于表示输入数据的stream class;​
    • ostream:用于表示输出数据的stream class;​

这两个类是basic_istream<>和basic_ostream<>的char类型实例化类。
实际上,IOStream library并不和某一个具体的字符类型相关。 ​

  1. 全局流对象 Global stream object​

IOStream library定义了一些istream和ostream的全局对象,对应操作系统的标准IO通道:​

    • cin:istream对象,对应C语言中的stdin,通常是操作系统中的键盘设备;​
    • cout:ostream对象,对应C语言中的stdout,通常是操作系统中的显示器;​
    • cerr:ostream对象,对应C语言中的stderr,通常这个流对象也连接到显示器设备,但是cerr是没有缓冲的;​
    • clog:ostream对象,C语言里没有对应的对象,clog输出是带有缓冲性质的,其余和cerr相同;​

通过这些对象,我们可以利用操作系统的重定向机制,把cout重定向到日志文件,而cerr / clog输出到屏幕。 ​

  1. 流操作符​

和一开始说的一样,C++用到流对象的方向来实现IO。stream operator<<表示流入流对象,表示输出;stream operator>>
表示流出对象,表示输入; ​

  1. Manipulators​

Manipulators是一种对象,这种对象用来改变输入流的解释方式或者输出流的格式化形式。
因此,manipulator并不一定产生实际输出或者会消费掉一个输入。其中*重要的几个manipulator:​

    • endl:输出\n,并且刷新输出缓冲区;​
    • ends:输入\0;​
    • flush:刷新输出缓冲区;​
    • ws:跳过空格读取;​

看到这里,你应该对IOStream library有一个轮廓性的认识了,下面我们的讨论,也都属于这5部分的范畴,跟我来。 ​

IOStream类的基础结构​

IOStream的层次结构如下图所示:

%title插图%num

在每一个方框内,上面的是模板类,下面是char和wchar_t的实例化类型。 ​

在ios_base中,定义了与字符类型和字符类型信息无关的属性,其中大部分都是状态和格式标记; ​

basic_ios<>从ios_base派生,定义了和字符类型信息相关的属性。
例如:特定字符类型使用的缓冲区。
这个缓冲区对象,是basic_streambuf<>类的派生类对象,并根据需要的字符类型进行实例化; ​

basic_istream<>和basic_ostream<>从basic_ios<>虚拟派生,并分别用来进行输入和输出。
和basic_ios<>一样,这两个类也不和具体的某一种字符类型相关,而是提供了两个模板参数,
一个用来定义字符类型,另一个用来定义和字符类型相关的信息。
我们常用的istream和ostream,就是char类型实例化的两个类; ​

basic_iostream<>从basic_istream<>和basic_ostream<>派生,该类型的流对象即可以进行输入操作,
也可以进行输出操作; ​

basic_streambuf<>是整个IOStream library的核心。
这个类定义了流对象可以使用的所有读写操作的接口。
所有的stream class都使用basic_streambuf<>提供的接口进行实际的IO操作; ​

更加详细的类定义​

和我们前面说得一样,IOStream library的类都带有两个参数,其中一个是字符的类型,另外一个是和字符类型相关的信息。
这个字符类型相关的信息包括:EOF的表示形式以及如何复制和移动字符序列等(也是基于类型萃取的方式实现的)。
通常情况下,字符的类型和这些附加的信息总是成对出现的,因此定义一个模板类来表达这些所有字符的公共属性,
并且,根据每一种字符类型进行特化是一个不错的主意。
STL提供了char_traits<charT> (charT表示字符的类型)来解决这个问题,并且为char和wchat_t提供了特化版本。 ​

namespace std {
template <class charT, class traits = char_traits<charT>>
class basic_ios;
typedef basic_ios<char> ios;
typedef basic_ios<wchar_t> wios
}

basic_ios使用的stream buffer类定义如下: ​

namespace std {
template < class charT, class traits = char_traits <charT > >
class basic_streambuf;
typedef basic_streambuf < char > streambuf;
typedef basic_streambuf <wchar_t > wstreambuf;
}

​看到这里,你应该可以想象到,basic_istream<>,basic_ostream<>和basic_iostream<>的实现也同样采取了
“类型参数化”的手段,并且,也有各自的char & wchar_t特化版本。 ​

namespace std {
template < class charT, class traits = char_traits <charT > >
class basic_istream;
template < class charT, class traits = char_traits <charT > >
class basic_ostream;
template < class charT, class traits = char_traits <charT > >
class basic_iostream;
typename basic_istream < char > istream;
typename basic_istream <wchar_t > wistream;
typename basic_ostream < char > ostream;
typename basic_ostream < char > ostream;
typename basic_iostream <wchar_t > iostream;
typename basic_iostream <wchar_t > wiostream;
}

有哪些全局对象​

前面我们说过,STL为char和wchar_t提供了一些预定义全局对象来访问标准的IO通道,4个针对char,4个针对wchar_t。​

类型​

名称​

目的​

istream​

cin​

从标准输入通道读取​

ostream​

cout​

向标准输出通道输出​

ostream​

cerr​

向标准错误通道输出错误信息​

ostream​

clog​

向标准日志通道输出日志信息​

wistream​

wcin​

从标准输入通道读取寬字符集输入​

wostream​

wcout​

向标准输出通道输出寬字符集​

wostream​

wcerr​

以寬字符集向标准错误通道输出错误信息​

wostream​

wlog​

以寬字符集向标准日志通道输出日志信息​

basic_ostream<>对所有的内建类型,以及char * / bool / void *进行了重载,重载的方式是,把第二参数按箭头的方向发送到对应的stream中。
默认条件下,这些标准的流对象都会和标准C流进行同步。(这是导致效率不高的罪魁祸首!)
当通过标准C++流对象写入缓冲区的时候,它会先刷新对应的C流,反之亦然,当然,这些同步会花费一些时间,如果你不希望这样,可以在任何输入输出之前,调用:

sync_with_stdio( false);

有哪些头文件​

上面提及的stream class分布在下面这些头文件里:​

  • <iosfwd>:所有stream class的前置声明;​
  • <streambuf>:stream buffer基类的定义;​
  • <istream>:basic_istream<>和basic_iostream<>的定义;​
  • <ostream>:basic_ostream<>的定义;​
  • <iostream>:全局流对象的声明;​

对于大部分的程序员来说,<iosfwd> / <istream> / <ostream>已经足够。
只有需要使用标准流对象的时候,才需要包含<iostream>,但由于初始化的问题,包含这个头文件会导致一小段代码的执行,进而对程序的执行效率稍有影响,虽然这小段代码自身的计算成本微不足道,但因此带来的一系列内存页面交换操作则可能进一步对性能产生影响。​

再说<<和>>操作符​

basic_istream<>和basic_ostream<>重载了<<和>>,并以此作为标准的IO操作符。 ​

<<

basic_ostream<>对所有的内建类型,
以及char * / bool / void *进行了重载,重载的方式是,把第二参数按箭头的方向发送到对应的stream中。
由于operator <<可以被重载,使得我们可以将任意的用户定义类型集成到IOStream library中。
其实,标准C++已经这样做了,string / bitset / complex对象均可以发送到std::cout,并且正常显示在屏幕上。 ​

和C里面,printf中的%d / %X / %O等等相比,这是一个长足的进步,
程序员不再需要指定待打印的字符的类型,编译器会进行正确的推导,并且正确显示。​

>>

和<<一样,basic_istream<>对所有的内建类型,以及char * / bool / void *进行了重载,重载的方式是,将读入参数发送到箭头所指参数。
同样,用户自定义类型也可以通过重载operator >>的方式集成到IOStream library,让我们像输入一个内建型别一样去输入一个用户自定义类型。 ​

特殊类型的IO​

  • bool:默认情况下,bool类型按照0 (false) / 1 (true)进行输出,输入时,0表示false,其他值表示true。
    但是值得注意的是:当输入非1表示true时,流状态会被设置std::ios_base::failbit标志。
    当然,通过国际化选项,C++也可以把bool输出成true / false或wahr / falsch(德语……)这样的文字;​
  • char和wchar_t:当读入这两个类型时,默认前置的空格将会被忽略;​
  • char *:通常,输入的起始空格会被忽略,通常,在输入字符串时,要指定一个*大安全长度:
    char buffer[81];
    std::cin>>std::setw(81)>>buffer;
    STL中的string类则更为智能,可以根据实际输入的字符串伸缩自如,所以,尽可能的使用std::string替代char * 吧。 ​
  • void *:当把一个void *类型的数据发送到basic_ostream<>时,将输出该指针指向的地址;​

到这里,我们关于IOStream library中和类型相关的讨论,就结束了,你应该对于整个库的层次结构熟记于心。
关于IO我们还有:流状态 / 标准IO函数 / 国际化这三个话题,希望我有时间,希望我能坚持写完~~~ ^.^​

IOStream library的stream对象维护了一组状态表达IO操作的结果(成功 or 失败?)以及失败的原因。
这次的内容我们就围绕着stream object展开。 ​

Stream object的状态​

流状态用ios_base::iostate这种类型的常量表示,C++标准里并没有强制iostate用枚举的方式来实现,int, std::bitset等也未尝不可。

iostate有下面四种状态: ​

iostate常量​

含义​

goodbit​

没有任何错误​

eofbit​

遇到文件末尾​

failbit​

一般性错误,IO操作没有成功​

badbit​

致命错误,会引发未定义的结果​

除了goodbit之外,其他的三个表示置1时表示真值,而goodbit则是全0,并不存在某一个bit表示good的概念。
eofbit通常和failbit是同时出现的,在IO操作超过文件结束位置的时候,先发生的是操作失败进而导致failbit被置位,而后,才是eofbit。 ​

操作iostate的成员函数​

这些成员函数定义在basic_ios<>里,用来处理流状态: ​

返回值​

函数​

说明​

bool​

good()​

如果流状态为goodbit,则返回true​

bool​

eof()​

如果eofbit被置位,则返回true​

bool​

fail()​

如果failbit或者badbit被置位,则返回true​

bool​

bad()​

如果badbit被置位,则返回true​

iostate​

rdstate()​

返回流对象当前的iostate​

void​

clear()​

清除流对象所有的iostate​

void​

clear(state)​

清除流对象所有的iostate,并把流状态设置成state指定的值​

void​

setstate(state)​

向流对象的状态中添加state指定的状态​

流状态和布尔条件​

为了在条件表达式里使用流对象,IOStream library重载了两个操作符void *和!。其中void *用来测试​

if (stream object)

!用于测试:​

if ( !(stream object))

值得说明的是,连续对state object使用两次!操作符并不会让state object恢复原来的状态,这是有悖于!操作符默认行为的举动,其实,明确调用state_obj.fail()进行判断是一个更好的办法。​

如果你执意要用!,那么,请在state_obj外围加上括号,因为!操作符的优先级,要比<<或>>高。对一个bool值进行IO是没有意义的。
例如 if (!cin>>a) 这是错误的.

流状态和异常​

和传统C语言通过返回值表示状态*大的不同,C++的异常是一种强制性的错误检测,
一旦程序中有代码抛出异常,你不处理,就会交由操作系统提供的默认异常处理程序接手,进而干掉发生异常的程序。
流对象在状态发生错误时,同样会抛出std::ios::failure异常只是出于兼容性考虑,这个抛异常的开关没有打开。 ​

STL提供了ios::exceptions()函数来打开这个开关,
不带参数调用的时候,返回当前打开异常开关的流状态,
另外一个重载版本带一个参数,打开参数指定的流状态异常。 ​

iostate exceptions( ) const;
void exceptions(
iostate _Newexcept
);
void exceptions(
io_state _Newexcept
);

几点说明:​

  • 0 或者 ios::goodbit 作为参数,不会导致异常; ​
  • 当调用过clear()或setstate()后,所有之前已经置位的错误流状态均会导致异常发生(具备检测作用.); ​
  • 对于异常本身,只有调用其what()函数这个做法是跨平台的,至于what()输出的内容,则没有保障其内容的一致性;​

至此,我们已经对IOStream library的结构、iostream对象和iostream状态有了清楚的认识。

实际上,除了重载过的<<和>>用于标准输入输出外,STL还提供了一组标准IO函数,他们定义在istream和ostream中。
和标准IO操作符不同的是,IO函数执行的是非格式化IO,而操作符执行的格式化IO(例如标准IO函数不会忽略前置空格)。 ​

标准IO函数​

STL中的标准IO函数都使用一个叫做streamsize的类型表达数量的概念,本质上说,这是一个带符号的size_t类型。 ​

标准输入函数​

在下面的故事里,istream只是一个替代符,用来代表basic_istream模板类以及basic_istream的任意一个实例化类。​

首先登场的是用户输入的函数:

函数​

终止条件​

读入字符​

自动添加结束符​

返回值​

get(void)​ EOF​ 1​ N/A​ 字符的int值或者其他​
get(char &c)​ N/A​ 1​ N/A​ istream &​
get(s, num)​ (‘\n’ or EOF)(excluding)​ up to num-1​ Yes​ istream &​
get(s, num, t)​ (t or EOF) (excluding)​ up to num-1​ Yes​ istream &​
getline(s, num)​ ‘\n’ or EOF (including)​ up to num-1​ Yes​ istream &​
getline(s, num, t)​ t or EOF (including)​ up to num-1​ Yes​ istream &​
read(s, num)​ EOF (exception)​ num​ no​ istream &​
readsome(s, num)​ EOF (no exception)​ up to num-1​ No​ streamsize​

这里,把一些区别性的东西额外摘出来,供大家记忆:​

按照是否在字符串后面自动添加结束标志分,可以分成两大类:​

  • 对s自动添加的:get / getline ​
  • 不自动添加的:read / readsome ​

get和getline的区别在于,get在delimeter之前会停止,并不读取delimeter;getline则会读取delimeter,但并不把读入的delimeter存到指定的字符串里。​

只有read是严格按照参数指定的个数进行读取的(所以有可能触发异常(未读满,即读到EOF)),其他函数的num参数只是读取的上限;

上面这些函数的都需要调用者保证,s有足够的空间存放读入的字符串,否则会发生未定义错误。​


接下来是和输入有关的一些辅助函数

函数​

说明​

返回值​

gcount()​ 返回*后一次非格式化读操作读取的字符数。​ streamsize​
ignore()​ 忽略输入流中的一个字符。​ istream &​
ignore(streamsize count)​ 忽略输入流中*多count个字符。​ istream &​
ignore(streamsize count, int delim)​ 忽略输入流中*多count个字符,直到遇到delim表示的字符,并且也会把delim忽略掉。​ istream &​
peek()​ 返回下一个要读取的字符,但是并不真正读取。​ int​
unget()​ 将*后一个读取的字符放回输入流。​ istream &​
putback(char c)​ 和unget()类似,但是putback会在读取操作发生之前检查,参数c是否是*后一个读入的字符。​ istream &​

同样,把一些细节的问题单独记在下面:​

对于ignore来说,如果参数count是numberic_limits<int>::max(),则在delim前所有的字符都会被忽略,或者遇到EOF。 ​

当putback()无法放回输入流或者要求放回的字符错误时,badbit被置位,并根据异常的设置抛出异常。
而对于*多能回写多少个字符,则是实现的细节问题,没有明确的标准。 ​

标准输出函数​

和标准输入函数类似,我们用ostream表示用于输出的流对象,它可以是ostream / wostream或者其他basic_ostream的实例化类对象。​

函数名​

说明​

返回值​

put(char c)​ 把字符c写入到输出流对象。​ ostream &​
write(const char *str,
streasize size)​
把str指向的字符串中的size个字符
写入到输出流对象。​
ostream &​
flush()​ 强制把输出流对象的缓存写入到输出流对象​ ostream &​

一些值得注意的地方:​

  • 可以通过检查返回的ostream对象的状态,来判定输出是否成功; ​
  • 对于write来说,字符串的结束符并不会导致输出停止,而是会被输出出来; ​
  • 调用者必须保证传递给write的str至少拥有size个字符,否则会发生未定义错误; ​

总结​

以上,就是我们常用的标准IO函数(没有格式化的),当我们需要操作C字符串的时候,用它们比用<<和>>更安全,
并经streamsize会经常出场提示你,关于容量方面的制约。

常用C++的你,一定熟悉std::cout<<std::endl的写法,你可曾想过,std::endl是如何工作的呢? ​

Manipulators的实现原理​

从*本质的方面来说,manipulator是通过函数重载实现的,各种运算符是被重载的函数。
而manipulator本身是一个函数,作为参数传递给被重载的函数,像下面这样: ​

ostream & ostream : : operator << (ostream &( *op)(ostream &)) {
return ( *op)( * this);
}

ostream &(*op)(ostream &)就是一个manipulator的原型,把需要manipulator处理的流对象按引用传入,并操作修改,然后返回,
例如我们*常见的std::endl: ​

std : :ostream & std : :endl(std : :ostream &strm) {
// Write a new line.
strm.put( ‘\n’);
// Flush the buffer.
strm.flush();
// Return the stream object.
return strm;
}

这样,当你写下std::cout<<std::endl的时候,就相当于调用了ostream的<<操作符,进而在<<运算符重载内部,变成了std::endl(std::cout),进而修改了ostream对象。 ​

把上面我们写过的实例化endl写成一般的版本,STL中的实现如下: ​

template < class charT, class traits >
std : :basic_ostream <charT, traits > &
std : : endl(std : :basic_ostream <charT, traits > &strm)
{
strm.put(strm.widen( ‘\n’));
strm.flush();
return strm;
}

这里,widen的作用是把\n转换成当前的流对象使用的字符集中对应的元素。 ​

常用的manipulator​

这里,我们列举一些istream和ostream中常用的manipulator:​

Manipulator​

Class​

含义​

flush 不是函数

basic_ostream​

刷新对应流对象的缓存。​

endl​

basic_ostream​

向流对象的缓冲区插入一个换行符,并刷新缓冲区。​

ends​

basic_ostream​

向流对象的缓冲区插入一个字符串结束符。​

ws​

basic_istream​

读入并忽略空格​

STL中还有一些带有参数的manipulators,为了使用它们,你需要包含iomanip头文件,它们的实现和endl这类不带参数的不同,与平台和实现版本相关,并没有一个统一的方式来定制。 ​

自定义manipulators​

可以按照std::endl()的方法,如法炮制一个你自己的manipulator出来。
例如,我们做一个istream的manipulator,用于忽略读入的当前行。 ​

template < class charT, class traits >
inline
std : :basic_istream <charT, traits > &
ignoreLine (std : :basic_istream <charT, traits > &strm)
{
strm.ignore(std : :numeric_limits < int > : :max(), trm.widen( ‘\n’));

return strm;
}

这样,你就可以像这样:std::cin>>ignoreLine;来使用它了(本质上来说这和调用std::cin.ignore(max(), c)是一样的)。​

两个方面的内容影响着I/O格式化的定义:
一方面,是一些诸如数字精度、填充字符或数制方面的标志;
另一方面,需要能够通过调整格式来满足不同地域习俗。
这次我们的话题围绕着格式化标志的内容展开;我们将在谈到国际化这个主题的时候来看如何处理另一方面的需求。 ​

格式化标志​

ios_base定义了一组表示I/O格式的成员标志,他们被用来指定诸如”*小宽度”、“浮点数精度”和“填充字符”等内容。
ios::fmtflags记录了所有可以被定义的I/O格式。 ​

STL还对常见的标志提供了分组,例如针对所有“八进制数”、“十进制数”和“十六进制数”生效的标志。
针对这些组,STL提供了特殊掩码来简化对“标志组”的访问。 ​

通过成员函数来修改format flags​

ios_base提供了一组用来设置和清除I/O format flags的函数: ​

成员函数​

含义​

返回值​

setf(flags)​

在原有标志上添加flags标志。​

修改之前的flags

setf(flags, mask)​

把mask指定的标志组的标志设定成flags。​

修改之前的flags

unsetf(flags)​

清除flags​

N/A​

flags()​

返回所有已经设置的flags。​

返回所有已经设置的flags

flags(flags)​

把格式标志设置成flag。​

返回修改前的flags

copyfmt(stream)​

复制stream流对象的格式标志。​

N/A​

为了便于理解,贴一段常用的保存->修改->恢复format flags的代码: ​

using std : :ios, std : :cout;
// save actual format flags
ios : :fmtflags oldFlags = cout.flags();
// do some changes
cout.setf(ios : :showpos | ios : :showbase | ios : :uppercase);
cout.setf(ios : :internal, ios : :adjustfield);
cout << std : :hex << x << std : :endl;
// restore saved format flag
cout.flags(oldFlags);
通过Manipulator修改format flags​

STL还提供了两个manipulators来操纵格式标志: ​

Manipulators​

含义​

setiosflags(flags)​

相当于调用setf(flags)。​

resetiosflags(mask)​

清除mask代表的标志组(相当于调用setf(0, mask)。​

一些用来定制I/O的函数和方法​

在STL中,有一些manipulator是通过模板特化实现的,通过特化,使得使用这些manipulator的代码更加易读和易用。
如果你在希望输出bool类型的时候不再是0 or 1;在输出浮点数的时候,能够统一精度;在输出数字的时候,能够统一宽度,那么下面的内容一定对你有用。 ​

bool变量的格式化输出​

ios::boolalpha标志定义了bool变量的输出方式,
置1 输出 true / false;
清0 输出 1 / 0。

我们可以通过std::boolalpha和std::noboolalpha这两个manipulator来置位和清除这个标志。​

bool b;
std : :cout <<std : :noboolalpha <<b << ” == ” <<std : :boolalpha <<b <<std : :endl;
关于宽度、填充字符​

ios_base类提供了两个函数来解决字段宽度和填充的问题:​

成员函数​

功能​

width()​

返回当前实际的域宽度。​

width(val)​

把当前宽度设置成val,并返回前一个宽度。​

fill()

返回当前的填充字符。

fill(c)​

把c定义成当前的填充字符,并返回上一个填充字符。​

width()对于输出流对象,定义了*小的输出宽度,但是它只对接下来的格式化输出有效。默认的*小宽度是0,表示任意宽度。 ​

谈到宽度和填充,自然就少不了如何对齐的问题,当实际宽度不足时,在左边、右边还是中间填充字符呢?ios_base定义了一组标志来处理对齐的问题: ​

标志掩码​

标志​

含义​

adjustfield​

left​

左对齐,也就是在右侧填充。​

adjustfield​

right​

右对齐,也就是在左侧填充。​

adjustfield​

internal​

这种对齐方式根据内容的不同,有不同的表达方式。​

我们可以使用函数setf(flag, mask)来设置这些表示。
这里需要注意的是,
任何格式化IO操作结束后,width会被自动重置成默认值,而填充字符和对齐方式在我们显式修改之前,不会改变。
所以说 width()[成员函数] 会被其他格式化IO自动重置,
setw()[格式操作符] 一般只对紧跟的一个输出有效.

下面是一些关于对齐的显示实例: ​

对齐方式​

width()​

-42​

0.12​

“Q”​

‘Q’​

left​

6​

-42___​

0.12__​

Q_____​

Q_____​

right​

6​

___-42​

__0.12​

_____Q​

Q_____​

internal​

6​

-___42​

__0.12​

_____Q​

_____Q​

STL中还定义了一些manipulators,来帮助我们定制格式化输出,它们是:​

Manipulator​

含义​

setw(val)​

把IO流对象的宽度设置成val,等同于width(val)。​

setfill(c)​

把IO流对象的默认填充字符设置成c,等同于fill(c)。​

left​

设置左对齐标志。​

right​

设置右对齐标志。​

internal​

设置internal对齐标志。​

于是,在没有manipulator之前,我们也许要这样写: ​

int a = – 42;
std : :cout.width( 6);
std : :cout.fill( ‘_’);
std : :cout.setf(ios : :left, std : :ios : :adjustfield);
std : :cout <<a <<std : :endl;

得益于manipulator,我们的代码简洁多了:​

std : :cout <<std : :setw( 6) <<std : :setfill( ‘_’) <<std : :left <<a <<std : :endl;
把width()用于控制输入域宽度​

width()可用户在输入标准C字符串的的时候限制读入的长度
当width!=0的时候,允许输入的*长字符数是width()–1。
例如:​

char buf[ 81];
std : :cin >>setw( sizeof(buf)) >>buf;

尽管如此,我们还是推荐在STL世界里,使用std::string来替代传统C字符串。​

负数标志和大写字母​

IOStream library中,两个表示和数值的显示有关系:

Flag​

Meaning​

showpos​

在正值前面加上’+‘。​

uppercase​

在输出浮点数或按十六进制输出的时候,字母的部分使用大写字母(默认使用小写)。​

除了使用setf来设置上述标志外,IOStream还提供了一些manipulator来处理这些标志。​

Manipulator​

Meaning​

std::showpos​

设置ios::showpos标志。​

std::noshowpos​

取消ios::showpos标志。​

std::uppercase​

强制使用大写字母。​

std::nouppercase​

强制使用小写字母。​

这些标志,在我们显式设置修改他们之前,它们一直会保持上一次被设置的状态。​

关于数制​

ios_base中定义了一组标志来定义整数值的IO使用的数值,这组标志用base::ios::basefield这个mask来选择。 ​

Mask​

Flag​

Meaning​

basefield​

oct​

8进制读写。​

basefield​

dec​

10进制读写。​

basefield​

hex​

16进制读写。​

basefield​

N/A​

按照10进制输出并根据输入的数值的前缀判断数制。​

对数制标志的修改会在标志被重置之前一直生效。当没有设置数制标志时,输入的数制根据输入的前缀来判定,例如,如果输入0x开头的数字则采用16进制,0开头的数字采用8进制,其他情况,是10进制。 ​

同样,除了使用setf设置上述标志外,IOStream也提供了对应的manipulator: ​

Manipulator​

Meaning​

oct​

设置8进制标志。​

dec​

设置十进制标志。​

hex​

设置16进制标志。​

一些常用的代码片段: ​

// Clear one flag and set another.
std : :cout.unsetf(std : :ios : :dec);
std : :cout.setf(std : :ios : :hex);

// Set one flag and clear all other flags in the group.
std : :cout.setf(std : :ios : :hex, std : :ios : :basefield);

// Use manipulators.
std : :cout <<std : :ios : :hex <<x <<std : :endl;

除了上面三个改变数制的标志外,IOStream还提供了一个控是否显式当前数制的标志:​

Flag​

Meaning​

std::ios::showbase​

当设置该标志后,八进制数显示时会加上前缀0,十六进制数显示时会加上前缀0x。 ​

同样,IOStream也提供了设置showbase的manipulator。 ​

Manipulator​

Meaning​

std::showbase​

设置showbase标志​

std::noshowbase​

取消showbase标志​

浮点数显示​

浮点数的格式化输出相对复杂一些,显示方式和精度共同控制着实际的显示效果。
显示方式有三种:fix / scientific / None。 ​

Mask​

Field​

Meaning​

floatfield​

fixed​

采用10进制显示​

floatfield​

scientific​

采用科学计数法显示​

floatfield​

N/A​

由IOStream决定*合适的方式​

显示的精度由precision()决定,该函数有两个重载版本:​

成员函数​

功能​

precision()​

返回当前 流状态的浮点数精度。​

precision(val)​

把精度设置成val并返回之前的精度。​

所有因为精度问题造成的“截断”都不会采用直接截断的方法,而是会在*后一位采用四舍五入。默认的精度是小数点后6位。
当precision()用于科学计数法的时候,用于指定小数点后的位数。
默认情况下,fix和scientific标志均未设置,IOStream判定的准则如下:小数点前有1个0,然后是所有必要的0。如果“precision()个”十进制数足够表现浮点数,则使用十进制表示,否则,使用科学计数法表示。 ​

IOStream还提供了一个标志用来控制小数点的显示,一般情况下,对于整数的显示,如果在精度范围内,不会显示小数点。开启showpoint后,将会强制显示小数点。​

无论是对精度、显示方式还是小数点的控制,都通过IOStream提供的一组manipulator来控制。​

Manipulator​

Meaning​

showpoint​

设置ios::showpoint标志​

noshowpoint​

取消ios::showpoint标志​

setprecision(val)​

设置精度为val​

fixed​

使用十进制格式显示​

scientific​

使用科学计数法显示​

浮点数的显示方式有些复杂,当你不知道如何是好的时候,可以参考下下面的表格: ​

显示方式​

precision()​

421.0​

0.0123456789​

Normal​

2​

4.2e+02

0.012​

Normal​

6​

421​

0.0123457​

Normal with showpoint​

2​

4.2e+02​

0.012​

Normal with showpoint​

6​

421.000​

0.0123457​

fixed​

2​

421.00​

0.01​

fixed​

6​

421.000000​

0.012346​

scientific​

2​

4.21e+02​

1.23e-02​

scientific​

6​

4.210000+e02​

1.234568e-02​

​还是试着说一下这个方式吧.

常规状态下: 精度表示从高往低,从*个不为0的数算起的位数(有效位数)
自动使用fixed或scientific中的一种. 根据精度是否能够恰当表达这个数值.选定一种方式,
若fixed方式表达不了,(值位数大于精度等),则采用scientific方式

非常规状态下: 精度表示为限定小数点后的位数
fixed 和scientific状态下, 都会显示小数点,除了某些指定精度为1的情况

通用格式化标志​

下面这两个标志不和具体某一个类型相关,单独放在这里:​

Flags​

Meaning​

skipws​

使用>>读取数值时,忽略起始空格。这个标志是缺省默认的。​

unitbuf​

每次输出操作后,清空输出缓冲区。cout默认不设置此标志,但是cerr和wcerr则作为默认设置。​

国际化相关的内容​

你可以让你的IO格式适应不同国家当地的习俗,ios_base提供了相关的函数来完成这个任务。 ​

成员函数​

作用​

imbue(loc)​

设置locale对象为loc​

getloc()​

返回当前的locale对象​

每一个流都可以关联一个属于自己的locale对象。
初始的locale对象是在流构建之初被指定的,其中locale定义了一些关于格式细节的内容,例如:数字的格式、小数点使用的字符或者boolean变量的文字表达形式。 ​

和C语言提供的本地化设置相比,你可以把每一个流配置自己的locale对象,这样,你可以按照美国数字格式读入,而按照德国格式输出。
对于不同locale,我想我们还需要专门一个话题,这里就不展开了。 ​

*后,IOStream提供了两个函数,用来把字符转换成和流的locale对应的字符集。 ​

成员函数​

含义​

widen(c)​

把字符c转换成和流locale字符集对应的字符。​

narrow(c, def)​

把字符c转换成一个char,如果没有对应的字符,则返回def。​

写在*后​

终于写完了,是的,我是说终于。
对于格式化,这个复杂的话题,陆陆续续整理和编写了半个月,如果你也看到了这里,我同样也要说,辛苦了。​