一、文件流
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()的方式打开文件以外,还可以在对象构造的时候打开文件:
1 |
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 读写文件示例
写文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// 写文件 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(); } |
读文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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操作符(<<
和>>
)来进行文件读写。
1 2 3 4 5 6 7 8 9 10 11 12 |
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标志位判断文件是否读完成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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:
1 2 3 4 5 6 |
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中:
1 2 3 4 5 6 |
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来移动文件指针:
1 2 3 4 5 6 7 8 9 |
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输出错误原因。
例如,打开一个不存在的文件:
1 2 3 4 |
ifstream input("aabbcc.txt", ios::in); if (!input) { cout << "open fail error: " << strerror(errno) << endl; } |
输出结果会打印出文件不存在的错误:
评论