IO库
C++不直接处理输入输出,而是通过一族定义在标准库中的类型来处理IO。这些类型支持从设备读取数据、向设备写入数据的IO操作,设备可以是文件、控制台窗口等。还有一些类型允许内存IO,从string读取数据,向string写入数据。
IO库定义了读写内置类型值得操作。此外,一些类,如string,通常也会定义类似的IO操作,来读写自己的对象。
初学C++时,为了控制台的交互,往往会接触到IO的内容,现在可以对以下的设施进行说明了:
- istream类型,提供输入操作
- ostream类型,提供输出操作
- cin,istream对象,从标准输入读取数据
- cout,ostream对象,向标准输出写入数据
- cerr,ostream对象,用于输出程序错误信息,写入到标准错误
>>
运算符,从一个istream对象读取输入数据<<
运算符,向一个ostream对象写入输出数据- getline函数,从给定的istream读取一行数据,存入一个给定的string对象中。
1.IO类
头文件 | 类型 |
---|---|
iostream | Istream, wistream从流读取数据 |
ostream,wostream向流写入数据 | |
iostream,wiostream读写流 | |
fstream | ifstream,wifstream从文件读取数据 |
ofstream, wofstream向文件写入数据 | |
fstream,wfstream读写文件 | |
sstream | Istringstream, wistringstream从string读取数据 |
ostringstream,wostringstream向string写入数据 | |
stringstream,wstringstream读写string |
w是宽字符版本,wcin
,wcout
,wcerr
对应cin
,cout
,cerr
的宽字符版本对象。
1.1 IO对象无拷贝或赋值
不能拷贝和对IO对象赋值:
ofstream out1, out2;
out1 = out2; //错误:不能对流对象赋值
ofstream print(ofstream); //错误:不能初始化ofstream参数
out2 = print(out2); //错误:不能拷贝流对象
所以形参和返回类型都不能设置成流类型,IO操作的函数往往以引用的方式传递和返回流。读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。
1.2 条件状态
strm::iostate | strm是一种IO类型。iostate是一种机器相关的类型,提供了表达条件状态的完整功能。 |
---|---|
strm::badbit | strm::badbit用来指出流已崩溃 |
strm::failbit | strm::failbit用来指出一个IO操作失败了 |
strm::eofbit | strm::eofbit用来指出流到达了文件结束 |
strm::goodbit | strm::goodbit用来指出流未处于错误状态。此值保证为零 |
s.eof() | 若流s的eofbit置位,则返回true |
s.fail() | 若流s的failbit或badbit置位,则返回true |
s.bad() | 若流s的badbit置位,则返回true |
s.good() | 若流s处于有效状态,则返回true |
s.clear() | 将流s中所有条件状态位复位,将流的状态设置为有效。返回void |
s.clear(flags) | 根据给定的flags标志位,将流s中对应条件状态位复位。flags的类型为strm::iostate。返回void |
s.setstate(flags) | 根据给定的flags标志位,将流s中对应条件状态位置位。flags的类型为strm::iostate,返回void |
s.rdstate() | 返回流s的当前条件状态,返回值类型为strm::iostate |
一个流一旦发生错误,后续的IO操作都会失败。只有当流处于无错状态时,才可以进行读写。
badbit无法恢复,failbit可以修正。如果badbit,failbit和eofbit的任意一个被置位,则检测流状态的条件会失败。它们有对应的函数来查询。
1.3 输出缓冲
每个输出流都管理一个缓冲区,保存读写数据。组合多个输出操作成单一的设备写操作可以提升性能。
缓冲刷新的契机:
- 程序正常结束
- 缓冲区满,会刷新缓冲
- 使用操纵符如endl和flush,显式刷新缓冲
- 每个输出操作之后,可以用操纵符unitbuf设置流的内部状态,清空缓冲区。默认清空下,对cerr是设置unitbuf的,因此写到cerr的内容都是立即刷新的。
- 一个输出流可能被关联到另一个流。此时,读写被关联的流时,关联到的流的缓冲区会被刷新。例如默认清空下,cin和cerr都关联到cout。因此,读cin或写cerr都会导致cout的缓冲区被刷新。
cout << "hi!" << endl; //输出hi和一个换行,刷新缓冲区
cout << "hi!" << flush; //输出hi,刷新缓冲区
cout << unitbuf; //所有输出操作后都会立即刷新缓冲区
cout << nounitbuf; //回到正常的缓冲方式
程序崩溃时,输出缓冲区不会被刷新。
2.文件输入输出
实际上ifstream和ofstream继承于istream和ostream,所以操作和iostream差不多,它们还额外支持一些新的操作,用来管理与流关联的文件。
fstream fstrm; | 创建一个未绑定的文件流。fstream是头文件fstream中定义的一个类型 |
---|---|
fstream fstrm(s); | 创建一个fstream,并打开名为s的文件。s可以是string类型,或者是一个指向C风格字符串的指针。这些构造函数都是explicit的。默认的文件模式mode依赖于fstream类型。 |
fstream fstrm(s, mode); | 与前一个构造函数类似,但按指定mode打开文件 |
fstrm.open(s) | 打开名为s的文件,并将文件与fstrm绑定。s可以是一个string或C风格字符串指针。默认文件mode依赖于fstream类型。返回void |
fstrm.close() | 关闭与fstrm绑定的文件。返回void |
fstrm.is_open() | 返回一个bool值,指出与fstrm关联的文件是否成功打开并且尚未关闭 |
读写一个文件,需要自己定义文件流对象,建立关联。
文件模式:
- in: 读方式打开
- out:写方式打开
- app:每次写操作前均定位到文件末尾
- ate:打开文件后立即定位到文件末尾
- trunc:截断文件
- binary:二进制方式进行IO
各种模式的指定都有一些条件,比如对ifstream还是ofstream的亲和性,以及是否互相依赖、互斥等等。
3. string 流
istringstream
和ostringstream
专门负责对string的读写。也是istream和ostream的继承类。所以,除了雷同的常规操作以外,它们也有自己独有的操作:
sstream strm; | strm是一个未绑定的stringstream对象。sstream是头文件sstream中定义的一个类型 |
---|---|
sstream strm(s); | strm是一个sstream对象,保存string s的一个拷贝。次构造函数是explicit的。 |
strm.str() | 返回strm所保存的string的拷贝 |
strm.str(s) | 将string s拷贝到strm中。返回void |
4 格式化输入和输出
除了条件状态外,每个iostream对象还维护着一个格式状态来控制IO格式化细节。
标准库定义了一组操纵符来修改流的格式状态。操纵符是一个函数或对象,会影响流的状态,并能作为输入和输出运算符的运算对象。类似输入和输出运算符,操纵符也返回它所处理的流对象,因此我们可以在一条语句中组合操纵符和数据。
endl就是一种操纵符,写到输出流意味着换行并刷新缓冲区的操作。
操纵符用于两大类输出控制:控制数值的输出格式
,控制补白的数量和位置
。大多数改变格式状态的操纵符都是设置/复原成对的,一个操纵符用于设置新格式,另一个用于恢复正常格式。
当操纵符改变流的格式状态时,通常改变后的状态对所有后续IO都生效。
4.1 控制布尔值格式
bool值默认打印0或1,可以对流使用boolalpha
来操纵覆盖这种格式:
cout << "default bool values: " << true << " " << false
<< "\nalpha bool values: " << boolalpha
<< true << " " << false << endl;
前者输出1和0,后者输出字符串true和false。
取消cout格式的改变,可以用noboolalpha
:
bool bool_val = get_status();
cout << boolalpha << bool_val << noboolalpha;
4.2 指定整型值的进制
默认输出十进制,可以用hex、oct和dec改为十六进制、八进制和十进制。(只影响整型)
cout << "default: " << 20 << " " << 1024 << endl;
cout << "octal: " << oct << 20 << " " << 1024 << endl;
cout << "hex: " << hex << 20 << " " << 1024 << endl;
cout << "decimal: " << dec << 20 << " " << 1024 << endl;
4.3 输出中指出进制
用showbase
操纵符可以指示输出的整型数显示是何种进制,其中0x前导表示十六进制,0前导表示八进制,无前导表示十进制。
操纵符noshowbase
恢复。
4.4 控制浮点数格式
浮点数的输出格式涉及三个方面:
- 输出精度(即输出多少个数字)。
- 十六进制、定点十进制或者科学记数法形式输出。
- 没有小数部分的浮点值是否输出小数点。
默认情况下,浮点值按六位数字精度输出;如果浮点值没有小数部分,则不输出小数点;根据浮点数的值选择输出为定点十进制或科学计数法形式:非常大或非常小的值输出为科学记数法形式,其他值输出为定点十进制形式。
默认情况下,精度控制输出的数字总位数。输出时,浮点值按照当前精度四舍五入而非截断。
调用IO对象的precision
成员或者使用setprecision
操纵符可以改变精度。
precision
成员是重载的。一个版本接受一个int值,将精度设置为此值,并返回旧精度值。另一个版本不接受参数,直接返回当前精度值。setprecision
操纵符接受一个参数来设置精度。
setprecision操纵符和其他接受参数的操纵符都定义在头文件iomanip中。
// cout.precision返回当前精度值
cout << "Precision: " << cout.precision()
<< ", Value: " << sqrt(2.0) << endl;
// cout.precision(12)将打印精度设置为12位数字
cout.precision(12);
cout << "Precision: " << cout.precision()
<< ", Value: " << sqrt(2.0) << endl;
// 另一种设置精度的方法是使用setprecision操纵符
cout << setprecision(3);
cout << "Precision: " << cout.precision()
<< ", Value: " << sqrt(2.0) << endl;
iostream中的操纵符
boolalpha | 将true和false输出为字符串 |
---|---|
noboolalpha | 将true和false输出为1、0 |
showbase | 对整型值输出表示进制的前缀 |
noshowbase | 不生成表示进制的前缀 |
showpoint | 对浮点值总是显示小数点 |
noshowpoint | 只有当浮点值包含小数部分时才显示小数点 |
showpos | 对非负数显示+ |
noshowpos | 对非负数不显示+ |
uppercase | 在十六进制值中打印0X,科学计数法中打印E |
nouppercase | 在十六进制值中打印0x,科学计数法中打印e |
dec | 整型值显示为十进制 |
hex | 整型值显示为十六进制 |
oct | 整型值显示为八进制 |
left | 在值的右侧添加填充字符 |
right | 在值的左侧添加填充字符 |
internal | 在符号和值之间添加填充字符 (需要考虑std::cout.width(6)) |
fixed | 浮点值显示为定点十进制 |
scientific | 浮点值显示为科学计数法 |
hexfloat | 浮点值显示为十六进制 |
defaultfloat | 重置浮点数格式为十进制 |
unitbuf | 每次输出操作后都刷新缓冲区 |
nounitbuf | 恢复正常的缓冲区刷新方式 |
skipws | 输入运算符跳过空白符 |
noskipws | 输入运算符不跳过空白符 |
flush | 刷新ostream缓冲区 |
ends | 插入空字符,然后刷新ostream缓冲区 |
endl | 插入换行,然后刷新ostream缓冲区 |
定义在iomanip中的操纵符
setfill(ch) | 用ch填充空白 |
---|---|
setprecision(n) | 浮点精度设置为n |
setw(w) | 读写值的宽度为w个字符 |
setbase(b) | 将整数输出为b进制 |
5 未格式化的IO操作
标准库还提供了一组低层操作,支持未格式化IO。允许将一个流作一个无解释的字节序列来处理。
操作分两类,单字节操作和多字节操作。
单字节低层IO操作
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返回,但不从流中删除它 |
多字节低层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默认值为文件尾 |