文件指针
每个文件流类都内含一个文件指针,用于记录当前在文件中的读/写位置。执行读写操作时,数据始终从文件指针所在位置开始。
- 默认情况下,以读或写方式打开文件时,文件指针位于文件开头。
- 若以追加(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一个字节;
 因此跨平台时同一行占用的字节数不同。
 
- Windows 以 
- 尾部填充:某些系统用零字节填充文件尾部,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()—— 返回流是否已打开
警告:不要向磁盘写入指针
指针仅保存地址,不同次运行地址可能变化。将地址写入文件并在下次读回会导致悬垂指针,极其危险。应始终写入值而非地址。
