C 文件 I/O 的核心设计文件流类继承自标准流类因此你可以用几乎相同的方式处理控制台 I/O 和文件 I/O。 在fstream头文件中ofstream类是ostream的子类ifstream类是istream的子类fstream类是iostream的子类用于双向 I/O在程序中进行文件 I/O 时需要同时包含iostream和fstream头文件。要向文件写入数据需要构造一个连接到输出文件的ofstream对象然后使用ostream的函数如流插入运算符、put()和write()。类似地要从文件读取数据需要构造一个连接到输入文件的ifstream对象然后使用istream的函数如流提取运算符、get()、getline()和read()。这种继承设计使得文件 I/O 与标准 I/O 的接口高度一致操作控制台 I/O文件 I/O输出cout xofs x输入cin xifs x单字符输出cout.put(ch)ofs.put(ch)单字符输入cin.get(ch)ifs.get(ch)行输入cin.getline(buf, n)ifs.getline(buf, n)二进制输出cout.write(buf, n)ofs.write(buf, n)二进制输入cin.read(buf, n)ifs.read(buf, n)文件 I/O 需要额外的步骤将文件连接到流即打开文件以及断开流与文件的连接即关闭文件。一、文件打开1. 文件打开方式方式说明构造函数中指定文件名在创建流对象时直接打开文件使用open()成员函数先创建对象稍后再打开文件使用close()成员函数显式关闭文件析构函数会自动关闭#include fstream #include iostream using namespace std; int main() { // 方式1构造时打开 ofstream ofs(output.txt); // 方式2先构造后打开 ifstream ifs; ifs.open(input.txt); // 使用完后关闭 ifs.close(); ofs.close(); return 0; }2. 打开模式标志可以通过第二个参数指定打开模式多个模式用位或|组合模式标志说明ios::in打开文件用于读取ios::out打开文件用于写入ios::app追加模式写入到文件末尾ios::ate打开后定位到文件末尾ios::trunc如果文件存在清空内容ios::binary以二进制模式打开#include fstream using namespace std; int main() { // 以追加模式打开 ofstream ofs(log.txt, ios::app); // 以二进制模式打开 ofstream ofs_bin(data.bin, ios::binary); // 组合多个模式 fstream fs(file.txt, ios::in | ios::out); return 0; }3.检查文件是否成功打开#include fstream using namespace std; int main() { ifstream file(data.txt); // 方法1直接检查对象推荐 if (!file) { // 文件打开失败 } // 方法2使用 is_open() if (!file.is_open()) { // 文件打开失败 } // 方法3使用 good() if (file.good()) { // 文件打开成功且状态良好 } return 0; }二、 文件指针操作定位随机访问文件与文件指针相关联该指针可以直接移动到文件中的任何位置。随机访问在某些应用如数据库和索引中至关重要。你可以通过seekg()定位输入指针通过seekp()定位输出指针。两者都有两个版本绝对定位和相对定位。函数说明seekg(pos)设置输入流的位置g getseekp(pos)设置输出流的位置p puttellg()获取输入流的当前位置tellp()获取输出流的当前位置1. 什么是文件指针每个文件流维护一个文件指针File Pointer也称为文件位置指示器File Position Indicator它指向文件中下一次读取或写入的位置。指针类型所属流类定位函数获取位置函数输入指针get pointeristream/ifstream/fstreamseekg()tellg()输出指针put pointerostream/ofstream/fstreamseekp()tellp()对于同时支持读写的fstream输入指针和输出指针是独立的可以分别移动。2.seekg()/seekp()的两种定位方式定位类型语法说明示例绝对定位seekg(pos)将指针移动到从文件开头算起的第pos字节处seekg(100)→ 移动到第 100 字节相对定位seekg(offset, dir)从参考位置dir移动offset字节seekg(-20, ios::cur)→ 向后移动 20 字节参考位置dir的取值常量含义ios::beg文件开头beginios::cur当前位置currentios::end文件末尾end#include fstream #include iostream using namespace std; int main() { fstream file(data.txt, ios::in | ios::out); if (!file) { cerr 无法打开文件 endl; return 1; } // 写入一些数据 file ABCDEFGHIJKLMNOPQRSTUVWXYZ; // 绝对定位移动到第 5 个字节从 0 开始 file.seekg(5, ios::beg); char ch; file.get(ch); cout 位置 5 的字符: ch endl; // 输出 F // 相对定位从当前位置向后移动 3 字节 file.seekg(3, ios::cur); file.get(ch); cout 再向后 3 字节: ch endl; // 输出 J // 相对定位从文件末尾向前移动 3 字节 file.seekg(-3, ios::end); file.get(ch); cout 倒数第 3 个字符: ch endl; // 输出 X // 获取当前位置 streampos pos file.tellg(); cout 当前位置: pos endl; return 0; }3. 输入指针与输出指针的独立移动对于fstream输入指针和输出指针可以独立控制#include fstream #include iostream using namespace std; int main() { fstream file(data.txt, ios::in | ios::out); // 写入数据 file Hello World; // 移动输出指针到文件开头覆盖写入 file.seekp(0, ios::beg); file Hi; // 移动输入指针到位置 3读取数据 file.seekg(3, ios::beg); char buffer[10]; file.read(buffer, 5); buffer[5] \0; cout 从位置 3 读取: buffer endl; // 输出 lo Wo return 0; }4.tellg()和tellp()获取当前位置#include fstream #include iostream using namespace std; int main() { ifstream file(data.txt); // 移动到文件末尾获取文件大小 file.seekg(0, ios::end); streampos fileSize file.tellg(); cout 文件大小: fileSize 字节 endl; // 移回文件开头 file.seekg(0, ios::beg); // 逐字符读取并打印位置 char ch; while (file.get(ch)) { streampos pos file.tellg(); cout 字符 ch 的位置: pos endl; } return 0; }5. 注意事项要点说明文件打开模式随机访问通常需要以ios::in和/或ios::out模式打开二进制模式对于精确定位建议使用ios::binary模式避免换行符转换带来的位置偏差指针独立性fstream的输入指针和输出指针是独立的但某些实现中移动一个可能会影响另一个位置有效性移动指针前应确保目标位置在文件范围内// 二进制模式打开确保精确定位 fstream file(data.bin, ios::in | ios::out | ios::binary);三、文件读写1. 文本文件 vs 二进制文件特性文本文件二进制文件数据表示数值转换为可读字符序列数值的内存原始表示示例1234存储为1234(4字节ASCII)int 1234存储为0xD2 0x04 0x00 0x00(4字节原始数据)可读性人类可读不可读乱码文件大小通常较大通常较小精度可能存在精度损失浮点数精确存储跨平台兼容性好需考虑字节序等问题2.文件文件/* Testing Simple File IO (TestSimpleFileIO.cpp) */ #include iostream #include fstream #include cstdlib #include string using namespace std; int main() { string filename test.txt; // Write to File ofstream fout(filename.c_str()); // default mode is ios::out | ios::trunc if (!fout) { cerr error: open file for output failed! endl; abort(); // in cstdlib header } fout apple endl; fout orange endl; fout banana endl; fout.close(); // Read from file ifstream fin(filename.c_str()); // default mode ios::in if (!fin) { cerr error: open file for input failed! endl; abort(); } char ch; while (fin.get(ch)) { // till end-of-file cout ch; } fin.close(); return 0; }3.二进制文件对于二进制文件文件模式为ios::binary我们需要使用read()和write()成员函数它们以原始字节方式读写不对字节进行解释。二进制 I/O 与文本 I/O 的核心区别在于数据按原始内存表示直接写入/读取不进行任何格式转换。/* 测试二进制文件 I/O (TestBinaryFileIO.cpp) */ #include iostream #include fstream #include cstdlib #include string using namespace std; int main() { string filename test.bin; // 写入文件 ofstream fout(filename.c_str(), ios::out | ios::binary); if (!fout.is_open()) { cerr error: open file for output failed! endl; abort(); } int i 1234; double d 12.34; fout.write((char *)i, sizeof(int)); fout.write((char *)d, sizeof(double)); fout.close(); // 读取文件 ifstream fin(filename.c_str(), ios::in | ios::binary); if (!fin.is_open()) { cerr error: open file for input failed! endl; abort(); } int i_in; double d_in; fin.read((char *)i_in, sizeof(int)); cout i_in endl; fin.read((char *)d_in, sizeof(double)); cout d_in endl; fin.close(); return 0; }1 二进制写入的完整流程#include fstream #include vector #include cstring using namespace std; struct Student { char name[32]; int age; double gpa; }; int main() { // 写入多个结构体 ofstream fout(students.bin, ios::binary); Student students[] { {Alice, 20, 3.8}, {Bob, 21, 3.5}, {Charlie, 22, 3.9} }; // 一次性写入整个数组 fout.write(reinterpret_castchar*(students), sizeof(students)); // 或逐个写入 for (const auto s : students) { fout.write(reinterpret_castconst char*(s), sizeof(s)); } fout.close(); return 0; }2二进制读取的完整流程#include fstream #include vector using namespace std; struct Student { char name[32]; int age; double gpa; }; int main() { ifstream fin(students.bin, ios::binary); // 获取文件大小 fin.seekg(0, ios::end); streamsize fileSize fin.tellg(); fin.seekg(0, ios::beg); // 读取整个文件到 vector vectorchar buffer(fileSize); fin.read(buffer.data(), fileSize); // 或者读取结构体数组 Student s; while (fin.read(reinterpret_castchar*(s), sizeof(s))) { cout s.name , s.age , s.gpa endl; } fin.close(); return 0; }3 二进制文件操作的核心函数函数作用返回值write(buf, size)从buf写入size字节到文件ostream可链式调用read(buf, size)从文件读取size字节到bufistream可链式调用gcount()返回最后一次read()实际读取的字节数streamsize6. 二进制 I/O 的常见问题与解决方案问题1字符串的存储// ❌ 错误存储 string 对象本身 string name Alice; fout.write(reinterpret_castchar*(name), sizeof(name)); // 这会存储 string 对象的内部指针等元数据而非字符串内容 // ✅ 正确存储字符串内容 size_t len name.length(); fout.write(reinterpret_castchar*(len), sizeof(len)); fout.write(name.c_str(), len);问题2字节序Endianness不同平台可能使用不同的字节序include bit // C20 int i 0x12345678; if constexpr (std::endian::native std::endian::little) { // 小端低地址存低字节 (0x78, 0x56, 0x34, 0x12) } else { // 大端高地址存低字节 (0x12, 0x34, 0x56, 0x78) } // 跨平台解决方案统一转换为网络字节序大端 int networkOrder htonl(i); // host to network long问题3结构体对齐Paddingstruct Bad { char c; // 1 字节 // 3 字节填充通常 int i; // 4 字节 }; // 总共 8 字节 struct Packed { char c; int i; } __attribute__((packed)); // GCC/Clang禁用填充共 5 字节7.read()的返回值检查ifstream fin(data.bin, ios::binary); // 方法1直接检查流状态 if (fin.read(buffer, size)) { // 读取成功 } // 方法2使用 gcount() fin.read(buffer, size); if (fin.gcount() size) { // 读取了期望的字节数 } // 方法3检查 EOF while (fin.read(buffer, size)) { // 处理数据 } if (fin.eof()) { cout 已到达文件末尾 endl; } else if (fin.fail()) { cout 读取错误格式问题 endl; } else if (fin.bad()) { cout 严重错误流损坏 endl; }四、文件关闭文件关闭是文件操作中至关重要的一步。在 C 中可以通过显式调用close()成员函数或利用析构函数自动完成。1. 为什么需要关闭文件原因说明刷新缓冲区确保所有缓冲区的数据真正写入磁盘释放资源释放操作系统分配的文件句柄等资源避免数据丢失防止程序异常终止时缓冲区数据丢失允许其他进程访问某些系统限制文件同时被打开的进程数满足文件锁要求释放可能持有的文件锁2. 关闭文件的两种方式方式一显式调用close()推荐#include fstream using namespace std; int main() { ofstream fout(data.txt); fout Hello, World! endl; // 显式关闭文件 fout.close(); // 可以重新打开同一个流对象 fout.open(another.txt); fout Another file endl; fout.close(); return 0; }方式二利用析构函数自动关闭RAII#include fstream using namespace std; int main() { { ofstream fout(data.txt); fout Hello, World! endl; // 离开作用域时fout 的析构函数会自动调用 close() } // 文件在这里自动关闭 // 文件已关闭可以安全地被其他程序访问 return 0; }3.close()的行为#include fstream #include iostream using namespace std; int main() { ofstream fout; // 检查文件是否打开 if (!fout.is_open()) { cout 流未打开 endl; } fout.open(test.txt); if (fout.is_open()) { cout 文件已打开 endl; } fout Some data endl; // 关闭文件 fout.close(); // 关闭后检查 if (!fout.is_open()) { cout 文件已关闭 endl; } // close() 不会清除流的状态标志 // 需要手动调用 clear() 来清除错误状态 fout.clear(); return 0; }4.close()会自动刷新缓冲区#include fstream #include thread #include chrono using namespace std; int main() { { ofstream fout(test.txt); fout This data is in buffer; // 此时数据可能还在缓冲区尚未写入磁盘 // 程序暂停 5 秒 this_thread::sleep_for(chrono::seconds(5)); // 离开作用域时析构函数调用 close() // close() 会自动刷新缓冲区数据写入磁盘 } return 0; }6. 常见错误与注意事项错误1重复关闭同一文件ofstream fout(test.txt); fout.close(); fout.close(); // ⚠️ 第二次 close() 通常安全什么也不做但应避免错误2关闭后继续写入ofstream fout(test.txt); fout First endl; fout.close(); fout Second endl; // ❌ 错误文件已关闭写入会被忽略错误3未关闭文件导致数据丢失void badFunction() { ofstream fout(data.txt); fout Important data; // 忘记调用 close() // 如果程序在此处异常终止数据可能丢失 } void goodFunction() { ofstream fout(data.txt); fout Important data; fout.close(); // ✅ 确保数据写入磁盘 }