C++随机文件 I/O

文件指针

每个文件流类都内含一个文件指针,用于记录当前在文件中的读/写位置。执行读写操作时,数据始终从文件指针所在位置开始。

  • 默认情况下,以读或写方式打开文件时,文件指针位于文件开头。
  • 若以追加(append)模式打开,文件指针被置于文件末尾,从而避免覆盖现有内容。

使用 seekg() 与 seekp() 进行随机文件访问

迄今为止,我们接触的文件访问都是顺序的——即按文件内容顺序读写。
然而,C++ 也支持随机文件访问,可任意跳转到文件的指定位置,直接读写所需记录,而无需顺序遍历。

随机访问通过操作文件指针实现:

  • seekg() 用于输入流(g 代表 get);
  • seekp() 用于输出流(p 代表 put)。

对于文件流,读写指针始终相同,因此二者可互换使用。

seekg()/seekp() 参数说明

参数含义
偏移(offset)要移动的字节数,可为正(向文件尾)或负(向文件头)。
ios 标志指定偏移基准:
std::ios::beg相对于文件开头(默认)
std::ios::cur相对于当前位置
std::ios::end相对于文件末尾

示例:

inf.seekg(14, std::ios::cur);   // 从当前位置向前移动 14 字节  
inf.seekg(-18, std::ios::cur);  // 从当前位置向后移动 18 字节  
inf.seekg(22, std::ios::beg);   // 移动到文件第 22 字节  
inf.seekg(24);                  // 同上,省略基准时默认为 beg  
inf.seekg(-28, std::ios::end);  // 移动到文件末尾前 28 字节  

快速定位:

inf.seekg(0, std::ios::beg);  // 文件头  
inf.seekg(0, std::ios::end);  // 文件尾  

警告:文本文件随机定位的风险

在文本文件中,除文件开头外的任意位置进行 seek 可能产生意外结果:

  • 换行符抽象差异
    • Windows 以 CR LF 两个字节表示换行;
    • Unix/Linux 仅用 LF 一个字节;
      因此跨平台时同一行占用的字节数不同。
  • 尾部填充:某些系统用零字节填充文件尾部,seek 到文件尾或其附近位置时结果可能不一致。

示例(使用之前创建的文本文件 Sample.txt):

#include <fstream>
#include <iostream>
#include <string>

int main()
{
    std::ifstream inf{ "Sample.txt" };
    if (!inf)
    {
        std::cerr << "Uh oh, Sample.txt could not be opened for reading!\n";
        return 1;
    }

    std::string strData;

    inf.seekg(5);                           // 移动到第 6 字节
    std::getline(inf, strData);             // 读取本行剩余内容
    std::cout << strData << '\n';

    inf.seekg(8, std::ios::cur);            // 再向前移动 8 字节
    std::getline(inf, strData);
    std::cout << strData << '\n';

    inf.seekg(-14, std::ios::end);          // 文件末尾前 14 字节
    std::getline(inf, strData);             // 行为未定义(与编码有关)
    std::cout << strData << '\n';
}

输出示例(取决于编码):

is line 1
line 2
This is line 4

二进制文件更适合随机定位;可用二进制模式打开:

std::ifstream inf{ "Sample.txt", std::ios::binary };

获取文件大小

tellg()/tellp() 返回文件指针绝对位置(字节偏移)。先 seek 到文件尾,再调用 tellg() 即可得到文件大小:

std::ifstream inf{ "Sample.txt", std::ios::binary };
inf.seekg(0, std::ios::end);
std::cout << inf.tellg();  // 输出文件字节数

作者机器输出 64(Windows);Unix 为 60;若文件尾部有填充,结果可能不同。

同时读写:使用 fstream

fstream 支持同时读写,但读写切换需满足:

  • 在读写操作之间执行一次定位操作(seek)。
  • 若不想移动指针,可 seek 到当前位置:
iofile.seekg(iofile.tellg(), std::ios::beg);

注意:

  • while (fstream_obj) 无法像 ifstream 那样直接判断剩余内容,需结合其他方法。

示例:将文件中所有元音替换为 #

#include <fstream>
#include <iostream>
#include <string>

int main()
{
    std::fstream iofile{ "Sample.txt", std::ios::in | std::ios::out };
    if (!iofile)
    {
        std::cerr << "Uh oh, Sample.txt could not be opened!\n";
        return 1;
    }

    char ch;
    while (iofile.get(ch))
    {
        switch (ch)
        {
        case 'a': case 'e': case 'i': case 'o': case 'u':
        case 'A': case 'E': case 'I': case 'O': case 'U':
            iofile.seekg(-1, std::ios::cur); // 回退一字节
            iofile << '#';                  // 覆盖
            iofile.seekg(iofile.tellg(), std::ios::beg); // 恢复读模式
            break;
        }
    }
    return 0;
}

运行后 Sample.txt 变为:

Th#s #s l#n# 1
Th#s #s l#n# 2
Th#s #s l#n# 3
Th#s #s l#n# 4

其他实用文件函数

  • std::remove(filename) —— 删除文件
  • is_open() —— 返回流是否已打开

警告:不要向磁盘写入指针

指针仅保存地址,不同次运行地址可能变化。将地址写入文件并在下次读回会导致悬垂指针,极其危险。应始终写入而非地址。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"