本想着应付考试,然而考试并没有考。
基本的I/O类库与对象
iostream
istream
、wistream
从流读取数据ostream
、wostream
向流中写入数据iostream
、wiostream
读写流
fstream
ifstream
、wifstream
从文件读取数据ofstream
、wofstream
向文件写入数据fstream
、wfstream
读写文件
sstream
istringstream
、wistringstream
从string读取数据ostringstream
、wostringstream
向string写入数据stringstream
、wstringstream
读写string
iomanip
:用于指定输入输出流的格式
为了支持宽字符的语言(即wchar_t类型),io库定义了以w开头的一组类型和对象,比如wcin
、wcout
分别对应cin
、cout
。这些用起来和普通字符没什么不同,后面我们就以普通字符为例子。
IO类型间的关系
IO类型之间存在继承的关系。如下
当然,实际上的继承关系远比这复杂。要了解更多,可以看回课本。
正是因为有了上面的继承关系,一些用于istream
、ostream
的操作,比如<<
、>>
也可以用于ifstream
、istringstream
、ofstream
、ostringstream
IO对象无拷贝或赋值
IO对象无拷贝或赋值!所以我们不能用=
对流赋值或拷贝,也不能在函数中使用流参数或返回流,只能使用或返回流的引用或指针。这点格外要注意。
流状态
由于IO可能发生错误,我们需要一些标志和函数标记或检测流状态。
标志
ios类中,有一个数据成员,其每一位都对应一种错误状态,称为状态字。具体如下
标识常量 | 值 | 含义 |
---|---|---|
goodbit | 0x00 | 状态正常 |
eofbit | 0x01 | 文件结束 |
failbit | 0x02 | IO操作失败,但数据未丢失,可恢复 |
badbit | 0x04 | 流崩溃,数据丢失,不可恢复 |
使用时,记得格式是ios::goodbit
,当然,直接用值也行,就是不那么好记。
检查与设置
以下函数用于检查流状态:
1 |
|
以下函数用于设置流状态(都是返回void):
1 |
|
关于最后两个函数的区别可以看:clear与setstate的区别
缓冲
输出流都有一个缓冲区,用来保存程序读写的数据,并直到缓冲区刷新时才写到输出设备或文件。缓冲刷新的时机为:
-
程序正常结束,main函数的return操作会执行缓冲刷新。
-
缓冲区满时,只有刷新后数据才能继续写入缓冲区
-
用操纵符
endl
、ends
、flush
来显式刷新,用法为cin<<endl
。它们三个的区别是:endl
:添加一个“换行”,再刷新;ends
:添加一个“空格”,再刷新;flush
:不添加额外字符,直接刷新; -
用
unitbuf
来设置不缓存,立即刷新,用法为cin<<unitbuf
;若要取消,可用nounitbuf
。 -
一个输出流被关联到另一个流,当读写后面那个流时,刷新原输出流。比如
cout
与cin
关联,当写cin时,刷新cout
格式化输入输出
标准库定义了一组操纵符来控制流的格式状态,也就是修改数值的输出形式或控制补白的数量和位置。一般来讲,操纵符都是“设置”/“复原“成对的。下面的若无说明,无需包含iomanip头文件,凡是以set开头的都在iomanip中。
bool格式
默认情况下,bool值输出0/1;输出true/false,可用boolalpha
;复原可用noboolalpha
。一旦设置了bool格式,会对后面所有的bool值起作用。
1 |
|
整型格式
默认情况下,整型输出使用十进制。用hex
改为十六进制;oct
改为八进制;dec
改回十进制。一旦设置了格式,会对后面所有的整型起作用。
1 |
|
默认是只输出数字。如果想要十六进制输出0x18,八进制输出030,可以用showbase
,若要取消,可以用noshowbase
。一旦设置,对后面所有的整型起作用。
1 |
|
默认情况下,十六进制的0x18是用小写的x,并且用小写的“abcdef”,可以用uppercase
来设置为大写,nouppercase
设置为小写。一旦设置,对后面所有的整型起效。
1 |
|
默认情况下,正数前面无正号,若要输出正号,可用showpos
,取消可以用noshowpos
。一旦设置,对后面所有的正整数和正浮点数都有效。
浮点数格式
默认精度为6位,超出的位四舍五入。若要设置精度,可以用下面三个函数:
1 |
|
1 |
|
1 |
|
注意,在最后一个例子中,cout.precision(int)是从后往前执行,并且最终的输出结果取决于前面的。并且float类型的最大精度为6,double最大精度为15。
浮点数有三种计数法:科学计数法、定点十进制或十六进制计数法。操纵符scientific
设置科学计数法;fixed
设置定点十进制;hexfloat
设置十六进制法。标准库默认会根据数值自动选择计数法,我们也可以通过defaultfloat
来设置成默认模式。一旦设置,对后面所有的浮点数都有效。
一旦设置为scientific
、fixed
或hexfloat
后,精度的含义会发生变化:默认模式指的是总位数,设置后指的是小数点后的位数。
1 |
|
科学计数法的e和十六进制默认为小写,要用大写的话可以用uppercase
,要用小写的话可以用nouppercase
。
默认情况下,若浮点数的小数部分为零,则不显示小数点。可以用showpoint
和noshowpoint
在显示与不显示之间转换。若显示,则小数点后面的零取决于精度。
1 |
|
输出补白
setw(int)
:包含在iomanip中。指定下一个数字或字符串的最小空间(宽度)。如果没填满,则在前面加空格;如果填满或大于,则按正常输出。只对下一个数字或字符串有效。
1 |
|
left
:左对齐输出。比如在上面的最小空间中,没填满时,数字或字符串默认是在右边;设置左对齐后,是在左边。一旦设置,对后面所有的数字或字符串都有效。右对齐为right
1 |
|
setfill('a')
:包含在iomanip中。设置用于补白的字符,默认为空格,只允许用一个字符去替换。一旦设置,对后面所有的都有效。
1 |
|
internal
:控制负数符号的位置,设置之后左对齐符号或基数指示符,右对齐数字,中间补白(前提是setw的宽度要大于负数长度)。一旦设置,对后面所有的负数或非十进制数都有效。并没有找到取消的方法……
1 |
|
控制输入格式
默认输入为忽略空格、制表、换行、换纸、回车符。要让输入不忽略,可用noskipws
;忽略可用skipws
1 |
|
1 |
|
其他格式化输入输出
上面大部分使用<<
和>>
+控制符来实现,下面介绍两种其他设置格式的方法:
ios类中的方法
ios类中可以通过格式控制函数来设置格式。格式控制函数如下:
1 |
|
其中,标志字和上面的控制符差不多,不过用的时候要加上ios::
,下面列出常用的标志字:
位组 |
格式标志 |
作用 |
默认值 |
所占bit |
|
skipws |
使用输入操作符时跳过空白字符 |
设置 |
1 |
|
unitbuf |
每次操作后刷新缓冲区 |
Cerr设置,其他对象不设置 |
2 |
|
uppercase |
字母采用大写 |
不设置 |
3 |
|
showbase |
输出整数时加上进制前缀 |
未设置 |
4 |
|
showpoint |
按精度输出浮点数(不够补0) |
未设置 |
5 |
|
showpos |
输出非负数时加‘+’ |
未设置 |
6 |
adjustfield |
left |
加入指定字符使输出左对齐 |
right |
7 |
right |
加入指定字符使输出右对齐 |
8 |
||
Internal |
在符号和数值中间插入指定字符 |
9 |
||
basefield |
dec |
10进制输入/输出 |
dec |
10 |
oct |
8进制输入/输出 |
11 |
||
hex |
16进制输入/输出 |
12 |
||
floatfield |
scientific |
浮点数按科学计数法输出 |
无,由浮点数量级决定 |
13 |
fixed |
浮点数按小数输出 |
14 |
||
|
boolalpha |
以字母格式输入和输出布尔值 |
未设置 |
15 |
下面几点额外需要注意:
- 用的时候前面要加上
ios::
- 在使用
setf
设置属于某一位组的标记位时,需要提供位组作为第二个参数来将其他互斥的标记位复位,否则可能会出现设置无效的现象。并且建议用公有静态符号basefield
、adjustfield
和floatfield
- cout和cin初始时只有
ios::skipws
和ios::dec
,即0010 0000 0001
iomanip库中的方法
iomanip中有一些控制符可以用于设置标志字:
1 |
|
这些并不是函数,其用法和操纵符一样:
1 |
|
标准流(iostream)
标准流用于用户与硬件之间的输入输出。它有如下几个特殊的对象:
cin
:istream对象,连向键盘,从键盘读取数据cout
:ostream对象,连向显示器,将标准流输出到屏幕上cerr
:ostream对象,标准错误输出流,连向显式器,将错误信息“不经过缓冲区地”、“实时地”输出到屏幕上。不能重定向到文件clog
:ostream对象,标准错误输出流,连向打印机,将错误信息输出到缓冲区,等到缓冲区刷新再输出。不能重定向到文件
除了上面几个对象外,我们不能定义自己的istream
或ostream
或iostream
,因为它们并没有构造函数。
未格式化的操作
之前,我们使用的<<
和>>
会根据要 读取或写入的数据类型 来转换成对应格式,并且默认忽略空白符,这种叫格式化的IO操作。如果只是要单纯地提取字节,并且不忽略空白符,可以用底层操作,也就是未格式化的操作。
单字节操作
1 |
|
1 |
|
注意,cin.get()
和cin.peek()
是返回一个int
而不是char
,这样我们就可以返回文件尾标记(EOF
)。而char
中每一个都表示一个真实的字符,不能表示文件尾。
多字节操作
-
cin.get(char sink[], int size, char delim)
从cin中读取最多size个字符,如果遇到delim或文件尾则结束(delim不会读取出来,保留在流中),读取的字符存放到sink内。
-
cin.getline(char sink[], int size, char delim)
和上面类似,不过会读取并丢弃delim.
-
cin.read(char sink[], int size)
读取size个字节放入sink中,返回cin
-
cin.gcount()
返回上一个未格式化的操作(不包括gcount)从is读取的字节数。如果是
peek
、unget
、putback
,则返回0 -
cout.write(char sink[], int size)
将sink中size个字节存入cout中,返回cout
-
cin.ignore(int size, char delim)
读取并忽略最多size个字符,包括delim。与其他未格式化的操作不同,ignore有默认参数size=1, delim=eof
文件流(fstream)
头文件fstream
中定义了三个类:只读文件ifstream
、只写文件ofstream
、读写文件fstream
。它们分别继承自istream
、ostream
和iostream
,因此可以在函数参数中用文件流代替相应的标准流。
打开文件
我们先定义一个文件流对象,然后再将对象与文件关联起来:
1 |
|
文件有不同的打开方式,如下:
标识常量 | 值 | 意义 |
---|---|---|
ios::in | 0x0001 | 读方式 |
ios::out | 0x0002 | 写方式 |
ios::ate | 0x0004 | 打开文件后文件指针定位到文件末尾 |
ios::app | 0x0008 | 每次的写入内容都追加到文件末尾 |
ios::trunc | 0x0010 | 删除文件已有内容 |
ios::nocreate | 0x0020 | 如果文件不存在,则打开失败 |
ios::noreplace | 0x0040 | 如果文件存在,则打开失败 |
ios::binary | 0x0080 | 以二进制方式打开 |
ifstream
不能以ios::out
打开,ofstream
不能以ifstream
打开;ofstream
默认以ios::out||ios::trunc
打开,即默认会删除原文件。要想保留源文件,可以用ios::out||iot::app
或ios::out||ios::in
;ios::trunc
只能在ios::out
设定时才能设置;并且ios::trunc
和ios::app
不能同时设定;- 在
ios::app
模式下,即使没有指定ios::out
,文件也会以写方式打开; - 默认情况下,文件以文本模式打开
根据上面所说的,我们可以指定打开方式:
1 |
|
如果打开失败,failbit
会被置位,此时,if(file)
为false。一旦一个文件流与一个文件关联,再调用open会导致文件流failbit被置位,因此,要关闭后才能打开新的文件。
关闭文件
当一个文件用完后,最好即使关闭,即调用file.close()
。这样缓冲区的数据会写入文件,并添加文件结束标志,切断文件流与文件的联系。
尽管文件流在析构时会自动调用file.close()
(至于什么时候调用析构函数,可看之前的类基础博文),但我们最好手动写上,防止程序在中途崩溃。
读写文件
读写文件的操作与cin
、cout
类似,可以用>>
、<<
。
当然,file.get()
、file.getline()
等函数也是可以用的。
如果移动读指针用seekg()
,写指针用seekp()
,这和前面也是一样的。
唯一有点麻烦的是二进制文件,我们只能通过下面这种方法写入:
1 |
|
而且读二进制文件也要这样读:
1 |
|
串流(strstream/sstream)
串流有两类,一类是以C类型字符串为流的strstream
,一类是以string为流的sstream
。串流用起来与cin
和cout
没什么不同(毕竟是由它俩派生的嘛~),不过原理上,串流是将数据以字符串的格式储存,再将字符串格式的数据输出到数据中,因此它很适合当“中间类”。比如要一次读文件的一行:
1 |
|
更多拓展知识
文件流 |
ios::app |
ios::ate |
||
打开方式 |
结果 |
打开方式 |
结果 |
|
ofstream (默认是ios::in | ios::trunc) |
ios::app或ios::app|ios::out |
如果没有文件,生成空文件; 如果有文件,在文件尾追加 |
ios::ate或ios::ate|ios::out |
如果没有文件,生成空文件; 如果有文件,清空该文件 |
ios::app|ios::in |
不管有没有文件,都是失败 |
ios::ate|ios::in |
如果没有文件,打开失败; 如果有文件,定位到文件尾,可以写文件,但是不能读文件 |
|
Ifstream (默认是ios::in) |
ios::app或ios::app|ios::out |
不管有没有文件,都是失败 |
ios::ate或ios::ate|ios::out |
如果没有文件,打开失败; |
ios::app|ios::in |
? |
ios::ate|ios::in |
? |
|
fstream (默认是ios::in | ios::out) |
ios::app|ios::out |
如果没有文件,创建文件; 如果有文件,在文件尾追加 |
ios::ate|ios::out |
如果没有文件,创建文件; 如果有,清空文件 |
ios::app|ios::in |
如果没有文件,失败 |
ios::ate|ios::in |
如果没有文件,失败 |
|
N/A |
N/A |
ios::ate|ios::out|ios::in |
如果没有文件,打开失败, 如果有文件,定位到文件尾 |
|
总结 |
ios::app不能和ios::in相配合, 但可以和ios::out配合,打开输入流 |
ios::ate可以和ios::in配合,此时定位到文件尾; 如果没有ios::in相配合而只是同ios::out配合,那么将清空原文件; |
||
区别 |
app会在每次写操作之前都把写指针置于文件末尾, |
而ate模式则只在打开时才将写指针置于文件末尾。在文件操作过程中,可以通过seekp等操作移动指针位置。 |
||
例子: 多个线程或者进程对一个文件写的时候,假如文件原来的内容是abc
|
以ios::app: 第一个线程(进程)往里面写了个d,第二个线程(进程)写了个e的话,结果是abcde |
以ios:ate: 后面写的会覆盖前面一个写的,第一个线程(进程)往里面写了个d,第二个线程(进程)写了个e的话,结果为abce |
参考:
吐槽:
2019/6/4 C++考试完全没考到这一部分的知识,有点失望(我干嘛要复习一天这个啊!)