格式化输入与输出
除了条件状态外,每个iostream对象还维护了一个格式状态来控制IO如何格式化的细节,如整型值是几进制、浮点值的精度、一个输出元素的宽度等。
标准库定义了一组操作符(manipulator)来改变流的格式状态,一个操作符是一个函数或是一个对象,会影响流的状态,并能用做输入或输出运算符的运算对象。类似输入和输出运算符,操作符也返回它所处理的流对象,因此我们可以在一条语句中组合操作符和数据。
前接*表示默认值 | |
boolalpha *noboolalpha | 将true和false输出为字符串 将true和false输出为1和0 |
showbase *noshowbase | 对整型值输出表示进制的前缀 不生成表示进制的前缀 |
showpoint *noshowpoint | 对浮点值总是显示小数点 只有当浮点值包含小数部分时才显示小数点 |
showpos *noshowpos | 对非负数显示+ 对非负数不显示+ |
uppercase *nouppercase | 在十六进制值中打印0X,在科学计数法中打印E 在十六进制值中打印0x,在科学计数法中打印e |
*dec hex oct | 整型显示为十进制 整型显示为十六进制 整型显示为八进制 |
left *right internal | 左对齐输出 右对齐输出 左对齐符号,右对齐值 |
fix scientific hexfloat defaultfolat | 浮点值显示定点十进制 浮点值显示为科学计数法 浮点值显示为十六进制(C++11特性) 重置浮点数格式为十进制(C++11特性) |
unitbuf *nounitbuf | 每次输出操作后都刷新缓冲区 回复正常缓冲区刷新方式 |
*skipws noskipws | 输入运算符跳过空白符 输入运算符不跳过空白符 |
flush | 刷新ostream缓冲区 |
ends | 插入空字符,然后刷新ostream缓冲区 |
endl | 插入换行,然后刷新ostream缓冲区 |
操作符用于两大类输出控制:控制数值的输入形式以及控制补白的数量和位置。大多数改变格式状态的操作符都是设置/复原成对的。当操作符改变流的格式状态时,通常改变后的状态对所有后续IO都生效,所以通常最好在不再需要特殊格式时尽快将流恢复到默认状态:
控制bool值格式
cout << "default bool values:" << true << " " << false
<< "\nalpha bool values:" << boolalpha
<< true << " " << false << endl;
"result:
default bool vaules: 1 0
alpha bool values: true false
"
bool bool_val = get_status();
cout << boolalpha
<< bool_val
<< noboolalpha;
指定整型进制
cout << "deafult:" << 20 << " " << 1024 << endl;
cout << "octal:" << oct << 20 << " " << 1024 << endl;
cout << "hex:" << hex << 20 << " " << 1024 << endl;
cout << "decimal:" << dec << 20 << " " << 1024 << endl;
"result:
default: 20 1024
octal: 24 2000
hex: 14 400
decimal: 20 1024
"
在输出中指定进制
当对流应用showbase操作符时,会在结果中显示进制(0x:十六进制;0:八进制;无:十进制)
cout << showbase;
cout << "deafult:" << 20 << " " << 1024 << endl;
cout << "octal:" << oct << 20 << " " << 1024 << endl;
cout << "hex:" << hex << 20 << " " << 1024 << endl;
cout << "decimal:" << dec << 20 << " " << 1024 << endl;
cout << noshowbase;
"result:
default: 20 1024
octal: 024 02000
hex: 0x14 0x400
decimal: 20 1024
"
默认情况下,十六进制会以小写大医,前导字符也是小写的x。可以使用uppercase操作符来输出大写的X和A-F
cout << uppercase << showbase << hex
<< "printed in hexadecimal:" << 20 << " " << 1024
<< nouppercase << noshowbase << dec << endl;
"result:
printed in hexadecimal: 0X14 0X400
"
控制浮点数格式
我们可以控制浮点数输出三个格式:
- 以多高精度(多少个数字)打印浮点值
- 数值是打印为十六进制、定点十进制还是科学计数法形式
- 对于没有小数部分的浮点是否打印小数点
默认情况下,浮点数按6位数字精度打印;如果浮点数没有小数部分,则不打印小数点;根据浮点数的值选择打印成顶点十进制或者科学计数法形式。
指定打印精度
我们可以通过调用IO对象的precision成员或使用setprecision操作符来改变精度。precision成员是重载的,一个版本接受int值,将精度设置为此值并返回旧值。另一个版本不接受参数,返回当前精度值。setprecision操作符接受一个参数,用来设置精度。操作符setprecision和其他接受参数的操作符都定义在头文件iomanip中。
#include <iomanip>
#include <cmath>
cout << "Precision: " << cout.precision()
<< ", Value: " << sqrt(2.0) << endl;
cout.precision(12);
cout << "Precision: " << cout.precision()
<< ", Value: " << sqrt(2.0) << endl;
cout << setprecision(3);
cout << "Precision: " << cout.precision()
<< ", Value: " << sqrt(2.0) << endl;
"result:
Precision: 6, Value: 1.41421
Precision: 12, Value: 1.41421356237
Precision: 3, Value: 1.41
"
指定浮点计数法
cout << "default format: " << 100 * sqrt(2.0) << '\n'
<< "scientific: " << scientific << 100 * sqrt(2.0) << '\n'
<< "fixed decimal: " << fixed << 100 * sqrt(2.0) << '\n'
<< "hexadecimal: " << hexfloat << 100 * sqrt(2.0) << '\n'
<< "scientific: " << scientific << 100 * sqrt(2.0) << '\n'
<< "use defaults: " << defaultfloat << 100 * sqrt(2.0)
<< '\n\n'
"
result:
default format: 141.421
scientific: 1.241214e+002
fixed decimal: 141.421356
hexadecimal: 0x1.1ad7bcp+7
use default: 141.412
"
输出补白
当按列打印数据时,我们常常需要非常精细地控制数据格式。标准库提供了一些操纵符帮助我们完成所需的控制:
- setw指定下一个数字或字符串值的最小空间(不改变输出流的内部状态,只决定下一个输出的大小)
- left表示左对齐输出
- right表示右对齐输出,默认格式
- internal控制负数的符号的位置,左对齐符号,右对齐值
- setfill允许指定一个字符代替默认的空格来补白输出
#include <iomanip>
int i = -16;
double d = 3.14159;
//补白第一列,使用输出中最小12个位置
cout << "i: " << setw(12) << i << "next col" << '\n'
<< "d: " << setw(12) << d << "next col" << '\n';
//补白第一列,左对齐所有列
cout << left
<< "i: " << setw(12) << i << "next col" << '\n'
<< "d: " << setw(12) << d << "next col" << '\n';
//补白第一列,右对齐所有列
cout << right
<< "i: " << setw(12) << i << "next col" << '\n'
<< "d: " << setw(12) << d << "next col" << '\n';
//补白第一列,补在域内部
cout << internal
<< "i: " << setw(12) << i << "next col" << '\n'
<< "d: " << setw(12) << d << "next col" << '\n';
//补白第一列,用#补白
cout << setfill('#')
<< "i: " << setw(12) << i << "next col" << '\n'
<< "d: " << setw(12) << d << "next col" << '\n'
<< setfill(' ');
setfill(ch) | 用ch填充空白 |
setprecission(n) | 将浮点精度设置为n |
setw(w) | 读或写值的宽度为w个字符 |
setbase(b) | 将整数输出为b进制 |
控制输入格式
默认情况下,输入运算符会忽略空白符(空格符、制表符、换行符、换纸符和回车符)。可以使用noskipws来修改:
cin >> noskipws;
while( cin >> ch)
cout << ch;
cin >> skipws;
未格式化的输入/输出操作
标准库还提供了一组低层操作,支持未格式化IO(unformatted IO)。这些操作允许我们将一个流单做一个无解释的字符序列来处理
单字节操作
有几个未格式化操作每次一个字节的处理流,它们对读取而不是忽略空白符
char ch;
while(cin.get(ch))
cout.put(ch);
is.get(ch) | 从istream is读取下一个字节存入字符ch中,返回is |
os.put(ch) | 将字符ch输出到ostream os。返回os |
is.get() | 将is的下一个字节作为int返回 |
is.putback(ch) | 将字符ch返回is。返回is |
is.unget() | 将is向后移动一个字节,返回is |
is.peek() | 将下一个字节作为int返回,但不从流中删除它 |
将字符放回输入流
- peek返回输入流中下一个字符的副本,但不会将它从流中删除,peek返回的值仍然留在流中
- unget使得输入流向后移动,从而最后读取的值又回到流中。即使我们不知道最后从流中读取什么值,仍然可以调用unget
- putback是更特殊版本的unget:它退回从流中读取的最后一个值,但它接受一个参数,此参数必须与最后读取的值相同
从输入操作返回的int值
peek和午餐版本的get都从输入流返回一个int类型。原因是:可以返回文件尾标记。返回int的函数将它们要返回的字符先转换成unsigned char,然后再将结果提升到int。因此,即使字符集中有字符映射到赋值,这些操作返回的int也是正值。而标准库使用负值表示文件尾,这样就可以保证与任何合法字符的值都不同。
#include <cstdio>
int ch;
while( (ch = cin.get()) != EOF)
cout.put(ch);
多字节操作
一些未格式化IO操作一次处理大块数据。如果速度是要考虑的重点问题的话,可以使用,但注意这些操作要求我们自己分配并管理用来保存和提取数据的字符数组:
is.get(sink, size, delim) | 从is中读取最多size个字节,并保存到字符数组中,字符数组的起始地址由sink给出。读取过程直至遇到字符delim或读取size个字节或遇到文件尾时停止。如果遇到delim,将其留在输入流中,不读取出来存入sink |
is.getline(sink, size, delim) | 与接受三个参数的get版本类似,但会读取并丢弃delim |
is.read(sink, size) | 读取最多size个字节,存入字符数组sink中。返回is |
is.gcount() | 返回上一个未格式化读取操作从is读取的字节数 |
os.write(source, size) | 将数组source中size个字节写入到os。返回os |
is.ignore(size, delim) | 读取并忽略最多size个字符,包括delim。 与其他未格式化函数不同,ignore有默认参数:size默认为1,delim默认为文件尾 |
- 一个常见的错误时本想从流中删除分隔符,但却忘了做。
- get和peek返回值是int
流随机访问
随机IO本质上是依赖系统的。为了理解如何使用这些特性,必须查询系统稳定。虽然标准库为所有流类型定义了seek和tell函数,但大多数系统只支持适用于fstream和sstream类型
seek和tell函数
tellg() tellp() | 返回到一个输入流(tellg)或输出流(tellp)标记的当前位置 |
seekg(pos) seekp(pos) | 在一个输入流或输出流中将标记重定位到给定的绝对地址。pos通常是前一个tellg和tellp返回的值 |
seekp(off, from) seekg(off, from) | 在一个输入流或输出流中将标记定位到from之前或之后off个字符,from可以是下列值之一:
|
标准库值在一个流中只维护单一的标记——并不存在独立的读标记和写标记。所以,我们在读写操作间切换必须进行seek操作来重定位标记。
重定位标记
#include <istream>
#include <ostream>
pos_type newposition;
off_type offset;
seekg(new_position);
seekp(new_position);
seekg(offset, from);
seekp(offset, from);
访问标记
ostringstream writeStr;
ostringstream::pos_type mark = writeStr.tellp();
if(cancelEntry)
writeStr.seekp(mark);
读写同一个文件
int main()
{
fstream inOut("copyOut", fstream::ate | fstream::in |fstream::out);
if(!inOut)
{
cerr << "Unable to open file!" << endl;
return EXIT_FAILURE;
}
auto end_mark = inOut.tellg();
inOut.seekg(0, fstream::beg);
size_t cnt = 0;
string line;
while( inOut && inOut.tellg() != end_mark && getline(inOut, line))
{
cnt += line.size() + 1;
auto mark = inOut.tellg();
inOut.seekp(0, fstream::end);
inOut << cnt;
if(mark != end_mark)
inOut << " ";
inOut.seekg(mark);
}
inOut.seekp(0, ofstream::end);
inOut << "\n";
return 0;
}