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
可能是0D
MacOS、0A
Unix 或0D0A
Windows)
使用
预定义的 stream
cin
:标准输入,绑定到stdin
cout
:标准输出,绑定到stdout
cerr
:无缓冲的调试区输出,绑定到stderr
clog
:有缓冲的调试区输出,绑定到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();