一、文件流

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::outios::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::failgitIO已崩溃
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;
}

输出结果会打印出文件不存在的错误:

最后修改:2020 年 12 月 26 日
如果觉得我的文章对你有用,请随意赞赏