Stream
C vs. C++
C
在 C 语言中,I/O 使用 scanf() 和 printf()。
在运行时解析格式字符串,和给定的地址交互。
通常处于更底层、更快。
最底层的函数 fread() 和 read(),需要手写快速输入时使用。
缺点:几乎没有类型检查,没有面向对象的重载方法
C++
相比之下,C++ 的 stream 有更好的类型检查、可拓展性和面向对象风格。
缺点:通常更慢
C + C++
当一个 C++ 程序中使用了 C 风格的输入输出时,请不要再使用 C++ 风格的输入输出。
原因:若两种风格的输入输出不共用缓冲区,混用会导致输出的顺序和得到的结果的顺序不同。
声明
位置
| 交互的对象 | 头文件 | 输入类名 | 输出类名 |
|---|---|---|---|
| 通用(控制台) | <iostream> |
istream |
ostream |
| 文件 | <fstream> |
ifstream |
ofstream |
字符串 string |
<sstream> |
istringstream |
ostringstream |
种类
- 输入类
- 从交互处读取数据,通过输入类传递给对象
- 重载了
>>运算符
- 输出类
- 从需要输出的对象读取数据,传递给交互处
- 重载了
<<运算符
- 控制符
- 声明在
<iomanip>中 - 用于控制输入输出的格式
- 声明在
文本流与二进制流
文本流指的是 ASCII、Unicode 或其他文本编码构成的字符数据流,文本流是特殊的二进制流。
以二进制方式读写文本流是允许的,反过来则不行。
文本流和二进制流的区别:
- 文本流的内容均为可读字符
- 文本流以行为单位
- 读取文本流的时候,内容可能被自动识别并修改(
\n可能是0DMacOS、0AUnix 或0D0AWindows)
使用
预定义的 stream
cin:标准输入,绑定到stdincout:标准输出,绑定到stdoutcerr:无缓冲的调试区输出,绑定到stderrclog:有缓冲的调试区输出,绑定到stderr
#include <iostream>
#include <string>
int main(){
int i;
float f;
char c;
std::string str;
std::cin >> str;
std::cin >> i >> f;
std::cout << str << ": " << i << " + " << f << '\n';
std::cerr << "Byebye\n";
return 0;
}
>>> Hello 4 3
<<< Hello: 4 + 3
<<< Byebye
如果在 Linux 下启动,还可以重定向:
./test 1>output 2>err
那么你可以分别在 output 和 err 文件中看见输出。
上文的 1 可以省略,它表示 stdout;2 表示 stderr;0 表示 stdin,且使用 < 而非 >。
预定义流的函数
cin
-
int get():返回下一个字节,或者EOF。 -
istream& get(char& ch):读取下一个字节cin >> get(ch);。 -
istream& getline(istream& is, string& str, char delim = '\n'):读取一行。- 是自由函数
- 遇到 delimiter 停止
-
istream& getline(char* str, int size):读取接下来size个字符。- 是
istream的成员 - 不推荐使用
- 是
-
void ignore(int limit = 1, int delim = EOF):忽略接下来limit个字符。- 是
istream的成员 - 如果遇到 delimiter,将其忽略,然后停止
- 是
-
int gcount():返回上一个操作读取字符的数量。 -
void putback(char ch):将一个字符放回流中。 -
char peek():返回下一个字符,但不读取它。
cout
void put(char ch):将一个字符放入缓冲区。void flush():刷新缓冲区,将缓冲区中的字符全部打印。
对类重载
istream& operator>>(istream& is, T& obj){
// code to read obj's members
return is;
}
运算符的返回值一定是 istream&,这样才能允许连续读入。
格式化
使用控制符来格式化,代替格式字符串的格式控制功能。
std::cin >> std::hex >> n;
那么 n 输入的值应当被视为 16 进制,例如输入 12 会被视为 16 + 2 = 18。
通常控制符的效果是持续的,持续到下一次同类控制符生效。
预定义的控制符
| 控制符 | 控制流的类型 | 效果 |
|---|---|---|
ws |
I | 跳过空格 |
setw(int) |
I/O | 指定字段所占的宽度 |
setfill(char) |
I/O | 指定占位符 |
hex,dec,oct |
I/O | 指定输出的进制 |
setbase(int) |
O | 指定输出的进制 |
setprecision(int) |
O | 指定浮点数的输出精度 |
endl |
O | 换行,并刷新缓冲区 |
flush |
O | 刷新缓冲区 |
自定义控制符
ostream& tab(ostream& os){
// ... any code
os << '\t';
return os;
}
这样就可以通过 os << tab 来使用控制符了。
格式标志
在 std::ios 空间下有一些格式标志,用于设置流的格式。
例如当设置了 std::ios::showpos 后,非负数在输出时会带上 +。
设置方法:
setf(flags),unsetf(flags):stream 的成员函数setiosflags(flags),resetiosflags(flags):控制符
| 标志 | 意义 |
|---|---|
skipws |
跳过前导空格 |
left,right |
左右对齐 |
scientific |
使用科学计数法表示浮点数 |
fixed |
使用正常的计数方法表示浮点数 |
hex,dec,oct |
数值的进制 |
showbase |
显示数值的进制 |
showpoint |
总是显示小数点 |
showpos |
在非负数前显示 + |
uppercase |
大写显示科学计数法的 E 和 16 进制的 X |
internal |
在符号和数值之间插入字符 |
实际上,流内部的状态用一个 32 位二进制表示,每一个标志对应 32 位二进制上的一位,该位为 1 表示标志有效,0 表示无效。
所以 setf() 时可以将不同的标志或起来。
#include <iostream>
#include <iomanip>
using namespace std;
int main(){
cout.setf(ios::showpos | ios::scientific);
cout << 123 << " " << 456.78 << endl;
cout << resetiosflags(ios::showpos) << 123 << endl;
return 0;
}
状态机
cin 的四个状态:
- good:正常工作
- bad:读取内容时出错,可能是网络断开、文件无法打开、文件损坏等
- fail:操作错误,想读取一个整数,结果下一段内容是字符串
- eof:End of File,表示文件内容读取完毕
cin.clear():将 cin 重置为 good。
注意重置之后并不改变流内的内容。如果需要重新输入,请配合 ignore() 使用。
cin.good() cin.bad() cin.fail() cin.eof():返回一个布尔值,表示 cin 是否为该状态。
cin 重载了operator int() 为 cin.good(),所以 if(cin) 等价于 if(cin.good())。
fstream
打开模式
在 C 风格的文件 I/O 中,我们使用字符串 w+、rb 等来指定文件打开的模式;
对于 ifstream 和 ofstream,我们使用标志来完成这个任务。
和前面的标志类似,这些标志声明在 std::ios 中,可以或运算。
| 模式 | 意义 |
|---|---|
app |
每次输出都在文件末尾追加 |
ate |
在文件末尾打开 |
binary |
二进制输入输出 |
in |
读取文件 |
out |
写入文件 |
trunc |
打开前丢弃流的内容 |
noreplace (C++23) |
如果已经存在文件,抛出异常 |
使用
简单情况下,我们可以直接使用构造函数:
ifstream fin1("id.txt"); // default ios::in
ifstream fin2("key.txt", ios::binary | ios::in);
ofstream fout("name.txt", ios::app | ios::out); // default ios::out
当然我们可以使用两步构造。第二步,打开文件的成员是 open(const char*, int flags):
ifstream fin;
fin.open("key.txt", ios::binary | ios::in);
ofstream fout;
fout.open("name.txt", ios::app | ios::out);
关闭文件流使用 close() 即可:
fout.close();