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