C++文本文件读写操作详解

 
前面章节中,已经给大家介绍了文件流对象如何调用 open() 方法打开文件,并且在读写(又称 I/O )文件操作结束后,应调用 close() 方法关闭先前打开的文件。那么,如何实现对文件内容的读写呢?接下来就对此问题做详细的讲解。

在讲解具体读写文件的方法之前,读者首先要搞清楚的是,对文件的读/写操作又可以细分为 2 类,分别是以文本形式读写文件和以二进制形式读写文件。

1) 我们知道,文件中存储的数据并没有类型上的分别,统统都是字符。所谓以文本形式读/写文件,就是直白地将文件中存储的字符(或字符串)读取出来,以及将目标字符(或字符串)存储在文件中。

2) 而以二进制形式读/写文件,操作的对象不再是打开文件就能看到的字符,而是文件底层存储的二进制数据。更详细地讲,当以该形式读取文件时,读取的是该文件底层存储的二进制数据;同样,当将某数据以二进制形式写入到文件中时,写入的也是其对应的二进制数据。

举个例子,假设我们以文本形式将浮点数 19.625 写入文件,则该文件会直接将 "19.625" 这个字符串存储起来。当我们双击打开此文件,也可以看到 19.625。值得一提的是,由非字符串数据(比如这里的浮点数 19.625)转换为对应字符串(转化为 "19.625")的过程,C++ 标准库已经实现好了,不需要我们操心。

但如果以二进制形式将浮点数 19.625 写入文件,则该文件存储的不再是 "19.625" 这个字符串,而是 19.625 浮点数对应的二进制数据。以 float 类型的 19.625 来说,文件最终存储的数据如下所示:

0100 0001 1001 1101 0000 0000 0000 0000

至于如何得出 float 类型的 19.625 对应的二进制,感兴趣的读者可阅读《小数在内存中是如何存储的》一节。

显然,如果直接将以上二进制数据转换为 float 类型,仍可以得到浮点数 19.625。但对于文件来说,它只会将存储的二进制数据根据既定的编码格式(如 utf-8、gbk 等)转换为一个个字符。这也就意味着,如果我们直接打开此文件,看到的并不会是 19.625,往往是一堆乱码。

C++ 标准库中,提供了 2 套读写文件的方法组合,分别是:
  1. 使用 >> 和 << 读写文件:适用于以文本形式读写文件;
  2. 使用 read() 和 write() 成员方法读写文件:适用于以二进制形式读写文件。

本节先讲解如何用 >> 和 << 实现以文本形式读写文件,至于如何实现以二进制形式读写文件,下一节会做详细介绍。

C++ >>和<<读写文本文件

通过《C++文件流类》一节的学习我们知道,fstream 或者 ifstream 类负责实现对文件的读取,它们内部都对 >> 输出流运算符做了重载;同样,fstream 和 ofstream 类负责实现对文件的写入,它们的内部也都对 << 输出流运算符做了重载。

所以,当 fstream 或者 ifstream 类对象打开文件(通常以 ios::in 作为打开模式)之后,就可以直接借助 >> 输入流运算符,读取文件中存储的字符(或字符串);当 fstream 或者 ofstream 类对象打开文件(通常以 ios::out 作为打开模式)后,可以直接借助 << 输出流运算符向文件中写入字符(或字符串)。

举个例子:
#include <iostream>
#include <fstream>
using namespace std;

int main()
{
    int x,sum=0;
    ifstream srcFile("in.txt", ios::in); //以文本模式打开in.txt备读
    if (!srcFile) { //打开失败
        cout << "error opening source file." << endl;
        return 0;
    }
    ofstream destFile("out.txt", ios::out); //以文本模式打开out.txt备写
    if (!destFile) {
        srcFile.close(); //程序结束前不能忘记关闭以前打开过的文件
        cout << "error opening destination file." << endl;
        return 0;
    }
    //可以像用cin那样用ifstream对象
    while (srcFile >> x) {
        sum += x;
        //可以像 cout 那样使用 ofstream 对象
        destFile << x << " ";
    }
    cout << "sum:" << sum << endl;
    destFile.close();
    srcFile.close();
    return 0;
}

注意,此程序中分别采用 ios::in 和 ios::out 打开文件,即以文本模式而非二进制模式打开文件。感兴趣的读者可在其基础上添加 ios::binary,即以二进制模式打开文件,程序依旧会正常执行。这是因为,以文本模式打开文件和以二进制模式打开文件,并没有很大的区别(后续章节会做详细讲解)。

执行此程序之前,必须在和该程序源文件同目录中手动创建一个 in.txt 文件,假设其内部存储的字符串为:

10 20 30 40 50

建立之后,执行程序,其执行结果为:

sum:150

同时在 in.txt 文件同目录下,会生成一个 out.txt 文件,其内部存储的字符和 in.txt 文件完全一样,读者可自行打开文件查看。

通过分析程序的执行结果不难理解,对于 in.txt 文件中的 "10 20 30 40 50" 字符串,srcFile 对象会依次将 "10"、"20"、"30"、"40"、"50" 读取出来,将它们解析成 int 类型的整数 10、20、30、40、50 并赋值给 x,同时完成和 sum 的加和操作。

同样,对于每次从 in.txt 文件读取并解析出的整形 x,destFile 对象都会原封不动地将其再解析成对应的字符串(如整数 10 解析成字符串 "10"),然后和 " " 空格符一起写入 out.txt 文件。