seekg()和seekp()操作的是字符还是字节?测试与文档矛盾解析
问题背景
《Programming: Principles and Practice》第393页对文件定位函数的示例代码注释称,seek()(实际应为seekg()/seekp())的位置参数按字符计数(比如位置5对应第6个字符)。但实际测试中发现并非如此:
若确有需要,可使用定位功能选择文件中特定位置进行读写。基本而言,每个打开的读文件有一个“读位置”,每个打开的写文件有一个“写位置”:
[示意图]fstream fs {name}; // open for input and output if (!fs) error("can't open ", name); fs.seek(5); // move reading position to the 5 (the 6th character) char ch; fs >> ch; // read and increment reading position cout << "character[5] is " << ch << ' {' << int(ch) << "}\n"; fs.seekp(1); // move writing position to 1 fs << 'y'; // write and increment writing position
用户创建了包含单个宽字符的wide.txt文件,运行以下测试代码:
#include "../std_lib_facilities.h" int main() { fstream fs {"wide.txt"}; fs.seekp(1); fs << 'y'; fs.close(); return 0; }
运行后wide.txt变为�y�,说明'y'被写入了第2个字节位置,而非第2个字符位置。这与部分资料中“按字符计数”的描述产生矛盾。
核心结论
seekg()(读定位)和seekp()(写定位)的偏移参数本质是字节数,而非字符数,除非满足以下特殊场景:
- 使用窄字符流(默认的
char类型流); - 文件采用的编码中每个字符恰好占用1字节(比如ASCII、ISO-8859-1等单字节编码)。
此时字节数与字符数相等,才会出现“按字符计数”的表象。
矛盾产生的原因
书籍的简化教学表述:
书中示例针对的是窄字符场景,作者为了降低初学者的理解门槛,直接将“字节偏移”简化描述为“字符位置”,忽略了多字节编码的复杂情况。部分资料的表述歧义:
很多资料在讨论seekg()/seekp()时,默认假设用户使用的是单字节编码的窄字符流,因此采用了“按字符计数”的简化说法,没有明确说明多字节编码下的差异。宽字符流的编码特性:
宽字符(如wchar_t)在文件中通常以多字节编码存储(比如UTF-16、UTF-32),单个宽字符占用2或4字节。此时seekp(1)定位的是第2个字节,而非第2个字符,写入单字节的'y'会破坏宽字符的编码结构,导致乱码。
内容的提问来源于stack exchange,提问作者user51462

