本想着应付考试,然而考试并没有考。
基本的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++考试完全没考到这一部分的知识,有点失望(我干嘛要复习一天这个啊!)