当谈及输入输出流时,需要牢记的是:输入输出是相对于程序而言的,比如读文件相当于向程序输入数据,写文件相当于程序向磁盘输出数据。
本章节将会叙述 标准 IO 流、文件流、字符串流 以及相关的迭代器
标准 IO 流
所谓标准 IO,包含三个部分:标准输入流、标准输出流、标准错误流, C++ 将其映射为四个类对象
流名称 | 作用 |
---|---|
cin | 标准输入流(stdin) |
cout | 标准输出流(stdout) |
cerr | 无缓冲的标准错误流(stderr) |
clong | 有缓冲的标准错误流(stderr) |
用于宽字节标准 IO 的类对象为:wcin, wcout, werr, wclong
一般来说,要使用标准 IO 流,需要包含的头文件为 istream, ostream 或者 iostream
实际上,istream, ostream 等只是 C++ 创建的别名:
|
标准 IO 流是在 C++ 输入输出流中是最重要的流,文件流和字符串流都直接或者间接继承自标准流,因此,标准 IO 流对于其他流也是适用的。
标准 IO 流的打开模式
标准 IO 流定义了多种打开模式:
模式 | 作用 |
---|---|
ios_base::app | 追加模式(append) |
ios_base::ate | 打开文件并定位到文件尾部(at end) |
ios_base::trunc | 截断模式 |
ios_base::binary | 以二进制模式打开或写入流,该行为系统相关 |
ios_base::in | 读 |
ios_base::out | 写 |
尽管以上模式定义于 ios_base::
中,但是一般使用 ios::
以方便书写。
标准 IO 流的状态
一个流总是处于以下状态之一:
状态名 | 含义 |
---|---|
good() | 上一个流操作成功 |
eof() | 到达流末尾 |
fail() | 非致命错误,上一个流操作失败(比如读取数组却得到一个 'X') |
bad() | 致命错误(比如磁盘读取错误) |
IO 流只有在 good() 状态下才可以进行操作。否则对其操作全部无效。当一个流被当作条件时,其返回结果为 good() 的值:
for(char s = 'a'; cin>>s;){
if( s == 'f') cin.setstate(ios::failbit);
cout<<s<<endl;
}
当流不处于 good() 状态时,你可以使用 clear()
将流的状态重新设置为 good() 状态。
在某些情况下,你可能当流处于某种状态时直接抛出异常(比如当流处于 bad() 状态时):
cin.exceptions(cin.exceptions() | ios::badbit); // 当流处于 bad() 状态时抛出异常
try{
cin.setstate(ios::badbit);
} catch (ios::failure&s) {
cout<<s.what()<<endl;
}
需要特别注意的是,IO 流重载了 operator bool()
这意味着 IO 流对象可以隐式转换为 bool 值。
输入控制
C++ 对输入添加了以下函数:
get 有 6 种重载形式:
函数签名 | 解释 |
---|---|
int get() | 返回值为从流中读到的字符或者 EOF |
istream& get (char& c) | 返回值为 *this,将读到的字符储存到 c 中 |
istream& get (char* s, streamsize n) | 读取 c-string,直到读够 n-1 个或者遇到 'n' |
istream& get (char* s, streamsize n, char delim) | 读取 c-string,直到读够 n-1 个或者遇到 delim 字符 |
istream& get (streambuf& sb) | 将字符读入到 sb 中,遇到 'n' 停止 |
istream& get (streambuf& sb, char delim) | 将字符读入到 sb 中,遇到 delim 字符 停止 |
如果你希望读取整个文件,一般有以下几种方式:
fstream fs("Z:/main.cpp", ios::in | ios::out);
char cstr[30];
while (fs.get(cstr, 30, EOF))
cout << cstr;
fstream fs("Z:/main.cpp", ios::in | ios::out);
char cstr;
while (fs.get(cstr))
cout << cstr;
其可能造成以下结果:
对于无参形式而言,若流中无可用字符,返回 EOF 并置 failbit,否则返回读到的字符。遇到 EOF 停止读取
对于有参形式而言,返回值为 *this,但是可以隐式转换为 bool,可以看做返回值为 !fail()
若读流到达末尾,置 eofbit
若没有字符被写入,置 failbit
若流内部发生错误(比如内部抛异常),置 badbit
当 get 遇到连续两个 delim 时,会置 failbit |
getline
函数名 | 作用 |
---|---|
in>>x | 根据 x 的类型从 in 读取数据并存入 x 中。 |
getline(istream&in, string&s) | 并从 in 中读取一行,写入到 s 中。不保留换行符 |
in.get() | 返回值为 int, 到达末尾时返回 EOF,提前到达置 eofbit为真 |
in.getline(char*s, int n) | 从 in 中读取 n 个字符并写入到 s 中,不保留换行符 |
in.read(s, n) | 从 in 中读取 n 个字符并写入到 s 中,不会自动添加 '0' |
尽量选择 in>>x
和 getline()
,而不是其他更加底层的函数。
另外,还有一个 ignore:
istream& ignore(int n = 1, int delim = EOF)
ignore 会跳过 n 个字符或遇到 delim 为止
输入输出格式控制
iomanip
无格式输入函数
输入流的 无格式输入函数 (UnformattedInputFunction )
其行为如下:
在存储周期内自动构建一个 noskipws 设置为 true 的 basic_istream::sentry 。
若输入流的 eofbit 和 badbit 被同时设定,则 failbit 也会设置,如果输入流的 failbit 允许抛出异常,则还会抛出 ios_base::failure
如果可能,刷新 std::tie 的输入流
通过调用 sentry::operator bool() 检查 sentry 的情况,这与 basic_ios::good 的效果相同。
如果 sentry 返回
false
或 sentry 在构造时抛出异常:设置输入流已经提取的字符数量( gcount )为 0
如果函数被用于向一个 charT 数组写入数据,向数组的第一个位置写入一个空字符
如果 sentry 返回值为 true ,其行为与调用
rdbuf()→sbumpc()
或rdbuf()→sgetc()
行为形同。若到达流的末尾(通过调用
rdbuf()→sbumpc()
或rdbuf()→sgetc()
),设置 eofbit 。若 eofbit 的异常被打开,则抛出 ios_base::failure如果在输入时抛出了异常,置 badbit 。若允许异常,则抛出 ios_base::badbit
若没有异常抛出,设置输入流的已提取字符数( gcount )
无论如何(无论是被异常终止还是被返回), sentry 的析构函数总是在离开函数时被调用。
以下标准库函数是 无格式输入函数
std::getline 不会修改 gcount.
basic_istream::operator>>(basic_streambuf*)
basic_istream::get
basic_istream::getline
basic_istream::ignore
basic_istream::peek
basic_istream::read
basic_istream::readsome
basic_istream::putback 它会首先清除 eofbit
basic_istream::unget 它会首先清除 eofbit
basic_istream::sync 不会修改 gcount
basic_istream::tellg 不会修改 gcount
basic_istream::seekg 首先清除 eofbit 而且不会修改 gcount
std::ws 不会修改 gcount
读取全部内容
std::string ReadAll(const std::string& file_path){
if(file_path.empty()) return {};
if(!Path::Exists(file_path)) return {};
// 使用 binary 模式打开避免读到 eof
std::ifstream is(file_path, std::ios::in | std::ios::binary);
return { std::istreambuf_iterator<char>(is), std::istreambuf_iterator<char>() };
}
当一个二进制文件以文本模式打开时,fgetc 会返回得到的字节。另一方面,由于 ASCII 范围为 0\~255,因此操作系统将 getc 的返回值 -1 当作发生错误(抑或是遇到了 eof),因此可能导致文件提前结束。
这里需要注意:EOF 是读到文件末尾时文件系统给出的信号,而 fgetc 的返回值 -1 代表了收到此信号。当 fgetc 得到的字节为 -1 时,程序错误地将 -1 字节当作了 EOF 信号,从而导致文件读取提前结束。
要正确判断是否是遇到了 EOF,可以使用 feof 函数判断。当文件指针指向文件最后一个字节的下一位时,feof 返回 1
如果想往终端中输入 \^D,可以使用 ^V-\^D 实现。\^V 就相当于反转义下一个按键序列 |