原文链接:https://www.dyxmq.cn/program/code/c-cpp/the-usage-of-fstream-in-cpp.html
一、文件流
C++的IO类中定义了三个文件读写流fstream、ifstream以及ofstream,它们都继承于相同的父类istream,通过不同的实现以实现不同的文件流操作。
三者的区别为:
- ifstream:从文件读取数据
- ofstream:从文件写入数据
- fstream:既可以读数据、又可以写数据
1.1 IO接口和读写模式
三个文件流实现了以下几个函数接口:
函数名 | 用途 |
---|---|
open(s, mode) | 以mode模式打开文件s |
close() | 关闭文件流 |
is_open() | 返回文件是否已经打开 |
read(buff, size) | 读入最多size字节数据到buff中 |
write(buff, size) | 写入size字节数据到文件中 |
在使用open的时候,可以只传入文件s,不指定打开模式。如果不指定模式,系统会自动根据文件类型选择默认的打开模式。同时,除了open()的方式打开文件以外,还可以在对象构造的时候打开文件:
ofstream output("/tmp/test.txt", ios::out);
ios::out
表示已只读方式打开文件,对应unix c中的O_WRONLY模式。在C++中,有以下读写模式可以选择:
模式 | 说明 |
---|---|
ios::in | 以读方式打开 |
ios::out | 以写方式打开 |
ios::app | 以追加写方式打开 |
ios::trunc | 以截断方式打开文件 |
ios:binary | 以二进制方式打开文件 |
ios::ate | 打开文件后指针定位到文件尾 |
这些模式可以单独使用,也可以组合使用,如果需要组合使用,使用逻辑操作符|
或起来即可。这里要特别注意的是ios::out
模式默认会截断文件,也就是说,ios::out
和ios::out | ios::trunc
效果是一样的,都会将文件截断。如果不希望以截断方式打开文件时,则需要设置读写模式为ios::out | ios::app
,以这种模式打开文件后,数据会以追加的方式写入到文件。
1.2 读写文件示例
写文件
// 写文件
void write_file() {
ofstream output;
// 待写入数据
string output_data = "HelloWorld\nWelcome To Tencent\n";
// 打开文件
output.open("test.txt", ios::out);
// 写入数据
output.write(output_data.c_str(), output_data.size());
// 关闭文件
output.close();
}
读文件
void read_file() {
ifstream input;
char input_data[1024];
// 打开文件
input.open("test.txt", ios::in);
// 读取数据
input.read(input_data, 1024);
// 打印读取到的数据
cout << input_data;
// 关闭文件
input.close();
}
打印结果:
1.3 以IO操作符读写数据
三个IO操作类都继承于istream,可以直接使用IO操作符(<<
和>>
)来进行文件读写。
ofstream output("test.txt");
ifstream input("test.txt");
std::string input_data;
output << "HelloWorld\n";
output.close();
input >> input_data;
cout << input_data;
input.close();
return 0;
注意:在使用IO操作符从文件读取数据的时候,数据输入的对象可以是字符串,也可以是对应的数据类型(如int)。如果输入到字符串,默认是读到空白字符的时候就停止了,需要通过循环控制读取后面的数据。
二、流状态
流在执行IO操作的时候,会根据不同的情况产生不同的状态码,如:
状态位 | 说明 |
---|---|
strm::badbit | 流已崩溃 |
strm::failgit | IO已崩溃 |
strm::eofbit | 已经读到文件尾 |
strm::goodbit | 一切正常,没有异常 |
我们可以使用已经封装好的函数bad()/fail()/eof()/good()
来判断当前IO是否已经达到某种状态,如判断文件是否已经读到结束。同时,我们还可以使用clear()
函数来清除当前IO对象的状态位,当流已崩溃(如将流中的字符串对象读到一个int对象上)时,可以手动来清除状态位继续往下读取。
示例,使用eof标志位判断文件是否读完成
ofstream output("test.txt");
ifstream input("test.txt");
std::string input_data;
output << "Hello World\n";
output.close();
while (!input.eof()) { // 没有读到文件尾就一直读取
input_data.clear();
input >> input_data; // 读到空白符就停止
// 过滤掉空行
if (input_data == "") continue;
cout << input_data << endl;
}
input.close();
因为使用IO操作符从流中读取数据默认遇到空行就停止,所以Hello World\n
字符串需要读三次才能读完,第一次是Hello
,第二次是World
,第三次是换行后的空行,所以,最后的输出结果是:
三、getline一次读一行
流对象中内置了getline()成员方法可以一次读一行数据到字符串数组中,需要传入一个字符数组和最大长度size:
ifstream input("test.txt", ios::in);
char buff[1024];
while (!input.eof()) {
input.getline(buff, 1024);
cout << buff << endl;
}
这个成员方法可以实现一次读一行的操作,但是只支持C风格的字符串传入,无法支持C++中的string类型。这里可以使用系统库的getline()函数来实现,系统库中有一个getline()函数可以直接从IO流中读取一行数据到string中:
ifstream input("test.txt", ios::in);
std::string input_data;
while (!input.eof()) {
getline(input, input_data);
cout << input_data << endl;
}
上面两个实现都可以正常按行输出文件内容:
四、fstream和文件指针
当使用fstream流时,对象既可以写入文件,也可以读取文件。此时读写文件指针是公共的,写入文件会导致指针后移,再读就会从当前位置重新读取(读到垃圾数据)。为了避免这个问题,可以使用seekp和seekg来移动文件指针:
fstream file("test.txt", ios::in | ios::out);
string input_data;
file << "HelloWorld\n" << unitbuf;
// 移动读指针到文件开始处
file.seekg(0, ios::beg);
file >> input_data;
cout << input_data
五、错误处理
5.1 读取文件失败了,应该如何判断
执行完open操作后,直接判断对象是否为真即可确定打开文件是否失败。出现错误后,可以使用errno输出错误原因。
例如,打开一个不存在的文件:
ifstream input("aabbcc.txt", ios::in);
if (!input) {
cout << "open fail error: " << strerror(errno) << endl;
}
输出结果会打印出文件不存在的错误: