C++文件操作详细讲解(完善版)
一、C++风格文件操作(iostream库)
1.1 基础知识
#include <fstream> // 文件流
#include <iostream> // 标准输入输出
#include <string>
#include <sstream> // 字符串流
// 三个主要类:
// ifstream - 输入文件流(读取)
// ofstream - 输出文件流(写入)
// fstream - 输入输出文件流(读写)流的继承关系:
ios是所有流的基类,定义基本的流操作istream继承自ios,提供输入操作ostream继承自ios,提供输出操作iostream继承自istream和ostreamifstream继承自istreamofstream继承自ostreamfstream继承自iostream
1.2 文件打开模式详解
// 基础模式
ios::in // 读取模式(默认)
ios::out // 写入模式(默认会清空文件)
ios::app // 追加模式(指针始终在文件末尾)
ios::ate // 打开文件后定位到文件末尾(但指针可以移动)
ios::trunc // 打开文件时清空内容(与 out 一起使用)
ios::binary // 二进制模式(不进行换行符转换)
// 模式组合示例
ios::in | ios::binary // 二进制读取
ios::out | ios::binary // 二进制写入
ios::in | ios::out // 读写模式
ios::out | ios::trunc // 写入并清空(默认)
ios::in | ios::out | ios::binary // 二进制读写
// 重要区别
// ios::ate - 打开后可移动指针,适合定位
// ios::app - 每次写入都在末尾,指针不可回退模式选择指南:
| 模式 | 文件存在 | 文件不存在 | 指针位置 | 用途 |
|---|---|---|---|---|
in | 打开读取 | 失败 | 开头 | 只读 |
out | 清空覆盖 | 创建 | 开头 | 只写 |
app | 打开追加 | 创建 | 末尾 | 日志/追加 |
ate | 打开 | 创建 | 末尾 | 定位读取 |
in|out | 打开 | 失败 | 开头 | 读写 |
out|trunc | 清空覆盖 | 创建 | 开头 | 覆盖写入 |
1.3 文本文件操作详解
#include <fstream>
#include <iostream>
#include <string>
#include <sstream>
using namespace std;
// ========== 写入文本文件 ==========
void writeTextFile() {
// 方法1:构造函数打开
ofstream outFile("data.txt");
// 方法2:open函数打开(可复用对象)
// ofstream outFile;
// outFile.open("data.txt", ios::out);
// 方法3:创建并立即检查
// ofstream outFile("data.txt");
// if (!outFile) { ... }
if (!outFile.is_open()) {
cerr << "文件打开失败!" << endl;
return;
}
// 基本写入
outFile << "Hello, World!" << endl;
outFile << "这是第二行" << endl;
outFile << "数字: " << 123 << ", 浮点数: " << 3.14 << endl;
// 关闭文件(析构函数会自动关闭,但建议显式关闭)
outFile.close();
// 检查是否成功关闭
if (outFile.fail()) {
cerr << "文件关闭失败" << endl;
}
}
// ========== 读取文本文件 - 逐行读取(推荐) ==========
void readLineByLine() {
ifstream inFile("data.txt");
if (!inFile.is_open()) {
cerr << "文件打开失败!" << endl;
return;
}
string line;
int lineNum = 0;
// getline 是读取一行的标准方法
while (getline(inFile, line)) {
lineNum++;
cout << "第 " << lineNum << " 行: " << line << endl;
// 可以在这里处理每一行
if (line.empty()) {
cout << " (空行)" << endl;
}
}
cout << "共读取 " << lineNum << " 行" << endl;
inFile.close();
}
// ========== 读取文本文件 - 按单词读取 ==========
void readWordByWord() {
ifstream inFile("data.txt");
if (!inFile) return;
string word;
int wordCount = 0;
// 提取由空白符分隔的单词
while (inFile >> word) {
wordCount++;
cout << "单词 " << wordCount << ": " << word << endl;
}
cout << "共读取 " << wordCount << " 个单词" << endl;
inFile.close();
}
// ========== 读取文本文件 - 按字符读取 ==========
void readCharByChar() {
ifstream inFile("data.txt");
if (!inFile) return;
char ch;
int charCount = 0;
// 逐字符读取(包括空白符和换行符)
while (inFile.get(ch)) {
charCount++;
if (ch == '\n') {
cout << "[换行]" << endl;
} else if (ch == '\t') {
cout << "[制表符]";
} else {
cout << ch;
}
}
cout << "\n共读取 " << charCount << " 个字符" << endl;
inFile.close();
}
// ========== 读取文本文件 - 一次性读入全部 ==========
void readEntireFile() {
ifstream inFile("data.txt");
if (!inFile) return;
// 方法1:使用流迭代器
string content((istreambuf_iterator<char>(inFile)),
istreambuf_iterator<char>());
cout << "文件内容:" << endl << content << endl;
cout << "文件大小:" << content.length() << " 字符" << endl;
inFile.close();
}
// ========== 读取文本文件 - 逐行到字符串向量 ==========
void readAllLinesToVector() {
ifstream inFile("data.txt");
if (!inFile) return;
vector<string> lines;
string line;
while (getline(inFile, line)) {
lines.push_back(line);
}
cout << "总行数:" << lines.size() << endl;
for (size_t i = 0; i < lines.size(); ++i) {
cout << "行 " << (i + 1) << ": " << lines[i] << endl;
}
inFile.close();
}
// ========== 追加模式 ==========
void appendToFile() {
// 使用 ios::app 模式
ofstream outFile("data.txt", ios::app);
if (!outFile.is_open()) {
cerr << "打开失败" << endl;
return;
}
outFile << "这是追加的内容" << endl;
outFile << "再追加一行" << endl;
outFile.close();
}
// ========== 检查文件是否存在 ==========
bool fileExists(const string& filename) {
ifstream inFile(filename);
return inFile.good(); // 如果文件存在且可打开,返回true
}
// ========== 计算文件行数 ==========
int countLines(const string& filename) {
ifstream inFile(filename);
if (!inFile) return -1;
int count = 0;
string line;
while (getline(inFile, line)) {
count++;
}
inFile.close();
return count;
}
// ========== 带格式化的读写 ==========
void formattedReadWrite() {
// 写入格式化数据
ofstream outFile("data.txt");
outFile << "姓名: " << "张三" << endl;
outFile << "年龄: " << 25 << endl;
outFile << "分数: " << 89.5f << endl;
outFile.close();
// 读取并解析
ifstream inFile("data.txt");
string name;
int age;
float score;
string dummy; // 用于读取前缀
getline(inFile, dummy); // 读整行
// 使用字符串流解析
stringstream ss(dummy);
ss >> dummy >> name; // 跳过"姓名:",读名字
inFile.close();
}1.4 二进制文件操作详解
#include <fstream>
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
// 定义结构体
struct Student {
char name[50];
int age;
float score;
};
// ========== 写入二进制文件 ==========
void writeBinaryFile() {
ofstream outFile("student.dat", ios::binary);
if (!outFile) {
cerr << "文件打开失败!" << endl;
return;
}
// 单个结构体写入
Student s1;
strcpy(s1.name, "张三");
s1.age = 20;
s1.score = 95.5;
// write(const char* buffer, streamsize size)
// 必须转换为 char* 指针
outFile.write(reinterpret_cast<char*>(&s1), sizeof(Student));
// 批量写入多个结构体
Student students[3] = {
{"李四", 21, 88.0},
{"王五", 19, 92.5},
{"赵六", 22, 85.0}
};
outFile.write(reinterpret_cast<char*>(students), sizeof(students));
// 写入基本类型数据
int number = 12345;
double value = 3.14159;
outFile.write(reinterpret_cast<char*>(&number), sizeof(number));
outFile.write(reinterpret_cast<char*>(&value), sizeof(value));
outFile.close();
}
// ========== 读取二进制文件 ==========
void readBinaryFile() {
ifstream inFile("student.dat", ios::binary);
if (!inFile) {
cerr << "文件打开失败!" << endl;
return;
}
Student s;
// read(char* buffer, streamsize size)
// 返回 istream 对象,可以检查状态
while (inFile.read(reinterpret_cast<char*>(&s), sizeof(Student))) {
cout << "姓名: " << s.name << endl;
cout << "年龄: " << s.age << endl;
cout << "分数: " << s.score << endl;
cout << "-------------------" << endl;
}
// 检查是否因错误而停止(而不是EOF)
if (!inFile.eof()) {
cerr << "读取过程中出现错误" << endl;
}
inFile.close();
}
// ========== 读取二进制数据到向量 ==========
void readBinaryToVector() {
ifstream inFile("student.dat", ios::binary);
if (!inFile) return;
vector<Student> students;
Student s;
while (inFile.read(reinterpret_cast<char*>(&s), sizeof(Student))) {
students.push_back(s);
}
cout << "读取了 " << students.size() << " 个学生记录" << endl;
inFile.close();
}
// ========== 修改二进制文件中的数据 ==========
void modifyBinaryFile() {
fstream file("student.dat", ios::in | ios::out | ios::binary);
if (!file) return;
Student s;
int recordIndex = 0; // 要修改的记录索引
// 定位到指定记录
file.seekg(recordIndex * sizeof(Student), ios::beg);
// 读取记录
file.read(reinterpret_cast<char*>(&s), sizeof(Student));
// 修改数据
s.score = 99.5f;
// 回到同一位置
file.seekp(recordIndex * sizeof(Student), ios::beg);
// 写入修改后的数据
file.write(reinterpret_cast<char*>(&s), sizeof(Student));
file.close();
}
// ========== 比较文本和二进制文件大小 ==========
void compareFileSizes() {
// 文本文件大小可能不等于字节数(因为换行符转换)
// 二进制文件大小精确对应实际数据
ifstream textFile("data.txt");
ifstream binFile("data.dat", ios::binary);
textFile.seekg(0, ios::end);
binFile.seekg(0, ios::end);
cout << "文本文件指针位置: " << textFile.tellg() << endl;
cout << "二进制文件指针位置: " << binFile.tellg() << endl;
}1.5 文件指针操作详解
#include <fstream>
#include <iostream>
using namespace std;
void filePointerOperations() {
fstream file("data.txt", ios::in | ios::out | ios::binary);
if (!file) return;
// ========== 获取文件大小 ==========
file.seekg(0, ios::end);
streampos fileSize = file.tellg();
cout << "文件大小: " << fileSize << " 字节" << endl;
// ========== 输入流指针操作(读取指针) ==========
// seekg(offset, whence) - 移动读取指针
file.seekg(0, ios::beg); // 移到文件开头
file.seekg(10, ios::cur); // 从当前位置向后移动10字节
file.seekg(-5, ios::end); // 从文件末尾向前移动5字节
file.seekg(100); // 直接定位到第100字节(默认从开头)
// tellg() - 获取读取指针当前位置
streampos pos = file.tellg();
cout << "当前读取位置: " << pos << endl;
// ========== 输出流指针操作(写入指针) ==========
// seekp(offset, whence) - 移动写入指针
file.seekp(0, ios::beg);
file.seekp(0, ios::end);
// tellp() - 获取写入指针当前位置
streampos writePos = file.tellp();
cout << "当前写入位置: " << writePos << endl;
// ========== 注意事项 ==========
// 对于 fstream,读写指针是独立的
file.seekg(0, ios::beg); // 移动读取指针
file.seekp(10, ios::beg); // 移动写入指针,不影响读取指针
// 在读写交替时需要小心管理指针
char buffer[100];
file.read(buffer, 10); // 从当前读指针位置读取
file.write(buffer, 10); // 从当前写指针位置写入
file.close();
}
// ========== 从文件中间读取特定位置数据 ==========
void readFromPosition(const string& filename, long position, int size) {
ifstream inFile(filename, ios::binary);
if (!inFile) return;
inFile.seekg(position);
char* buffer = new char[size];
inFile.read(buffer, size);
cout << "读取 " << inFile.gcount() << " 字节" << endl;
delete[] buffer;
inFile.close();
}
// ========== 反向读取文件 ==========
void readFileReverse(const string& filename) {
ifstream inFile(filename, ios::binary);
if (!inFile) return;
// 定位到文件末尾
inFile.seekg(0, ios::end);
streampos size = inFile.tellg();
// 从末尾向前读取
for (long i = size - 1; i >= 0; --i) {
inFile.seekg(i);
char ch;
inFile.get(ch);
cout << ch;
}
inFile.close();
}1.6 错误处理和状态检查详解
#include <fstream>
#include <iostream>
using namespace std;
void errorHandling() {
ifstream file("test.txt");
// ========== 状态检查函数 ==========
// good() - 所有状态位都正常(最严格)
if (file.good()) {
cout << "文件状态完全良好,可继续操作" << endl;
}
// eof() - 到达文件末尾
if (file.eof()) {
cout << "已到达文件末尾" << endl;
}
// fail() - 操作失败(如打开失败、格式错误)
if (file.fail()) {
cout << "读写操作失败(逻辑错误)" << endl;
}
// bad() - 严重错误(如硬件故障、流被破坏)
if (file.bad()) {
cout << "发生严重错误" << endl;
}
// operator bool() - 检查是否可用(推荐)
if (file) {
cout << "文件流处于良好状态" << endl;
}
if (!file) {
cout << "文件流出错或未打开" << endl;
}
// ========== rdstate() - 获取完整状态 ==========
ios::iostate state = file.rdstate();
if (state == ios::goodbit) {
cout << "完全正常" << endl;
} else if (state & ios::failbit) {
cout << "逻辑错误已设置" << endl;
} else if (state & ios::badbit) {
cout << "严重错误已设置" << endl;
}
// ========== setstate() - 设置状态 ==========
file.setstate(ios::failbit);
// ========== 清除错误状态 ==========
file.clear(); // 清除所有错误标志,设置 goodbit
// 清除特定标志
file.clear(ios::goodbit);
// ========== gcount() - 获取最后一次读取的字符数 ==========
char buffer[100];
file.read(buffer, 100);
cout << "实际读取: " << file.gcount() << " 字节" << endl;
// ========== 异常处理 ==========
// 方法1:设置异常掩码
file.exceptions(ifstream::failbit | ifstream::badbit);
try {
ifstream inFile("test.txt");
string line;
while (getline(inFile, line)) {
cout << line << endl;
}
inFile.close();
} catch (ifstream::failure& e) {
cerr << "文件操作异常: " << e.what() << endl;
}
// ========== 手动检查模式 ==========
ifstream file2("test.txt");
while (true) {
char buffer[256];
file2.read(buffer, 256);
if (file2.gcount() > 0) {
// 处理读取的数据
}
if (file2.eof()) {
break; // 正常结束
}
if (file2.fail()) {
cerr << "读取失败" << endl;
break;
}
}
file2.close();
}
// ========== 完整的文件读取示例 ==========
void robustFileRead(const string& filename) {
ifstream file(filename);
// 检查打开状态
if (!file.is_open()) {
cerr << "无法打开文件: " << filename << endl;
return;
}
string line;
int lineNum = 0;
// 使用getline进行安全读取
while (getline(file, line)) {
lineNum++;
cout << "行 " << lineNum << ": " << line << endl;
}
// 判断退出原因
if (file.eof()) {
cout << "成功读取到文件末尾" << endl;
} else if (file.fail()) {
cerr << "读取过程中出现逻辑错误" << endl;
} else if (file.bad()) {
cerr << "读取过程中出现严重错误" << endl;
}
file.close();
}1.7 流状态标志详解
/*
ios::goodbit (0) - 无错误
ios::badbit (1) - 严重错误,流被破坏
ios::failbit (2) - 操作失败
ios::eofbit (4) - 到达文件末尾
状态转换规则:
1. 成功操作 -> goodbit
2. 逻辑错误 -> failbit (failbit会自动设置eofbit)
3. 读到EOF -> eofbit (不是错误状态)
4. 严重错误 -> badbit
*/
void stateFlags() {
ifstream file("test.txt");
// 检查特定标志
if (file.rdstate() & ios::badbit) {
cout << "流已损坏" << endl;
}
if (file.rdstate() & ios::failbit) {
cout << "操作失败" << endl;
}
if (file.rdstate() & ios::eofbit) {
cout << "已到达末尾" << endl;
}
}1.8 缓冲区管理
#include <fstream>
#include <iostream>
using namespace std;
void bufferManagement() {
ofstream outFile("data.txt");
// ========== flush() - 刷新缓冲区 ==========
outFile << "第一行" << flush; // 立即写入磁盘
outFile << "第二行" << endl; // endl 包含了 flush
// 手动刷新
outFile << "第三行";
outFile.flush(); // 等同于 << flush
// ========== 禁用同步 - 提高性能 ==========
cin.tie(nullptr); // 断开 cin 和 cout 的绑定
ios::sync_with_stdio(false); // 加快I/O速度
// ========== 自定义缓冲区大小 ==========
char buffer[1024];
outFile.rdbuf()->pubsetbuf(buffer, sizeof(buffer));
// ========== 获取缓冲区信息 ==========
streamsize bufSize = outFile.rdbuf()->in_avail();
cout << "缓冲区可用字节数: " << bufSize << endl;
outFile.close();
}二、C风格文件操作(stdio.h)
2.1 基本文件操作
#include <cstdio>
#include <cstring>
#include <cerrno>
using namespace std;
void basicFileOperations() {
// fopen(文件名, 模式) - 返回 FILE* 指针
FILE* fp = fopen("data.txt", "w");
if (fp == NULL) {
perror("文件打开失败");
return;
}
// 写入数据
fprintf(fp, "Hello, World!\n");
fprintf(fp, "数字: %d\n", 123);
// 关闭文件
int result = fclose(fp);
if (result == EOF) {
perror("文件关闭失败");
}
// ========== fopen_s (Windows特有) ==========
// FILE* fp_safe;
// errno_t err = fopen_s(&fp_safe, "data.txt", "w");
// if (err == 0) { ... }
}2.2 文件打开模式详解(C风格)
/*
"r" - 只读,文件必须存在
"w" - 只写,创建新文件或覆盖已有文件
"a" - 追加,在文件末尾写入,不存在则创建
"r+" - 读写,文件必须存在
"w+" - 读写,创建新文件或覆盖已有文件
"a+" - 读写,在文件末尾追加
二进制模式(添加 'b'):
"rb" - 二进制只读
"wb" - 二进制只写
"ab" - 二进制追加
"r+b" - 二进制读写(等同于 "rb+")
"w+b" - 二进制读写,覆盖
"a+b" - 二进制读写,追加
重要区别:
- 文本模式:自动转换换行符 (\n <-> \r\n)
- 二进制模式:不进行任何转换
*/2.3 C风格文本文件操作详解
#include <cstdio>
#include <cstring>
// ========== fprintf 格式化写入 ==========
void cFormattedWrite() {
FILE* fp = fopen("data.txt", "w");
if (!fp) return;
fprintf(fp, "姓名: %s\n", "张三");
fprintf(fp, "年龄: %d\n", 25);
fprintf(fp, "分数: %.2f\n", 89.56);
fprintf(fp, "布尔值: %d\n", 1);
fprintf(fp, "十六进制: 0x%X\n", 255);
fclose(fp);
}
// ========== fputs 写入字符串 ==========
void cPutsWrite() {
FILE* fp = fopen("data.txt", "w");
if (!fp) return;
fputs("这是一行文本", fp);
fputs("\n", fp); // 需要手动添加换行符
fputs("第二行", fp);
fputs("\n", fp);
fclose(fp);
}
// ========== fputc 写入单个字符 ==========
void cPutcWrite() {
FILE* fp = fopen("data.txt", "w");
if (!fp) return;
// 逐字符写入
const char* str = "Hello";
for (int i = 0; str[i] != '\0'; i++) {
fputc(str[i], fp);
}
fputc('\n', fp);
fclose(fp);
}
// ========== fscanf 格式化读取 ==========
void cFormattedRead() {
FILE* fp = fopen("data.txt", "r");
if (!fp) return;
char name[50];
int age;
float score;
// 从文件读取格式化数据
int matched = fscanf(fp, "姓名: %s\n", name);
if (matched == 1) {
cout << "成功读取名字: " << name << endl;
}
fscanf(fp, "年龄: %d\n", &age);
fscanf(fp, "分数: %f\n", &score);
printf("读取数据:%s, %d, %.2f\n", name, age, score);
fclose(fp);
}
// ========== fgets 读取一行 ==========
void cGetsRead() {
FILE* fp = fopen("data.txt", "r");
if (!fp) return;
char line[256];
int lineNum = 0;
// fgets 会包含换行符
while (fgets(line, sizeof(line), fp) != NULL) {
lineNum++;
printf("行 %d: %s", lineNum, line); // 不需要加 endl,因为已有换行符
}
fclose(fp);
}
// ========== fgetc 读取单个字符 ==========
void cGetcRead() {
FILE* fp = fopen("data.txt", "r");
if (!fp) return;
char ch;
int charCount = 0;
// 逐字符读取,直到 EOF
while ((ch = fgetc(fp)) != EOF) {
charCount++;
if (ch == '\n') {
printf("[换行符]\n");
} else if (ch == '\t') {
printf("[制表符]");
} else {
putchar(ch);
}
}
printf("\n共读取 %d 个字符\n", charCount);
fclose(fp);
}
// ========== fgetc 与 getc 的区别 ==========
void cGetcVsGetc() {
FILE* fp = fopen("data.txt", "r");
if (!fp) return;
// fgetc 是函数调用
char ch1 = fgetc(fp);
// getc 是宏定义,可能更快
char ch2 = getc(fp);
// putc 是 putchar 的带文件版本
putc(ch1, stdout);
putc(ch2, stdout);
fclose(fp);
}
// ========== ungetc 回退单个字符 ==========
void cUngetc() {
FILE* fp = fopen("data.txt", "r");
if (!fp) return;
char ch1 = fgetc(fp);
char ch2 = fgetc(fp);
// 将 ch2 回退到输入流
ungetc(ch2, fp);
// 下一次 fgetc 会再次读取 ch2
char ch3 = fgetc(fp);
printf("ch2 = %c, ch3 = %c\n", ch2, ch3);
fclose(fp);
}
// ========== fgets 与 fscanf 的区别 ==========
void cGetsVsScanf() {
FILE* fp = fopen("data.txt", "r");
if (!fp) return;
// fgets:读取固定数量的字符或直到换行符
char line[256];
fgets(line, 256, fp); // 读取最多 255 个字符
// fscanf:按照格式字符串读取
char name[50];
int age;
fscanf(fp, "%s %d", name, &age); // 按格式读取
fclose(fp);
}2.4 C风格二进制文件操作详解
#include <cstdio>
#include <cstring>
struct Student {
int id;
char name[50];
float score;
};
// ========== fwrite 写入二进制数据 ==========
void cWriteBinary() {
FILE* fp = fopen("student.dat", "wb");
if (!fp) {
perror("无法打开文件");
return;
}
// 单个结构体写入
Student s1 = {1001, "张三", 95.5f};
// fwrite(数据地址, 单个元素大小, 元素个数, 文件指针)
// 返回成功写入的元素个数
size_t written = fwrite(&s1, sizeof(Student), 1, fp);
if (written != 1) {
printf("写入失败,期望写入 1 个元素,实际写入 %zu 个\n", written);
}
// 数组写入
Student students[3] = {
{1002, "李四", 88.0f},
{1003, "王五", 92.5f},
{1004, "赵六", 85.0f}
};
written = fwrite(students, sizeof(Student), 3, fp);
printf("成功写入 %zu 个学生记录\n", written);
// 写入基本类型
int count = 3;
fwrite(&count, sizeof(int), 1, fp);
fclose(fp);
}
// ========== fread 读取二进制数据 ==========
void cReadBinary() {
FILE* fp = fopen("student.dat", "rb");
if (!fp) {
perror("无法打开文件");
return;
}
Student s;
// fread(数据地址, 单个元素大小, 元素个数, 文件指针)
// 返回成功读取的元素个数
while (fread(&s, sizeof(Student), 1, fp) == 1) {
printf("学号: %d\n", s.id);
printf("姓名: %s\n", s.name);
printf("分数: %.2f\n", s.score);
printf("-------------------\n");
}
fclose(fp);
}
// ========== 混合读写二进制文件 ==========
void cModifyBinary() {
FILE* fp = fopen("student.dat", "r+b");
if (!fp) return;
Student s;
int recordIndex = 1; // 修改第二条记录
// 定位到指定位置
fseek(fp, recordIndex * sizeof(Student), SEEK_SET);
// 读取该记录
if (fread(&s, sizeof(Student), 1, fp) == 1) {
printf("原始分数: %.2f\n", s.score);
// 修改数据
s.score = 99.5f;
// 回到同一位置
fseek(fp, recordIndex * sizeof(Student), SEEK_SET);
// 写入修改
fwrite(&s, sizeof(Student), 1, fp);
printf("新分数: %.2f\n", s.score);
}
fclose(fp);
}
// ========== 计算二进制文件中的记录数 ==========
long cCountRecords(const char* filename, size_t recordSize) {
FILE* fp = fopen(filename, "rb");
if (!fp) return -1;
// 定位到文件末尾
fseek(fp, 0, SEEK_END);
// 获取文件大小
long fileSize = ftell(fp);
// 计算记录数
long recordCount = fileSize / recordSize;
fclose(fp);
return recordCount;
}2.5 C风格文件指针操作详解
#include <cstdio>
void cFilePointer() {
FILE* fp = fopen("data.txt", "r+b");
if (!fp) return;
// ========== fseek 移动文件指针 ==========
// fseek(文件指针, 偏移量, 起始位置)
// 返回 0 表示成功,非 0 表示失败
// 移到文件开头
if (fseek(fp, 0, SEEK_SET) != 0) {
perror("fseek 失败");
}
// 从当前位置向后移动 10 字节
fseek(fp, 10, SEEK_CUR);
// 从文件末尾向前移动 5 字节
fseek(fp, -5, SEEK_END);
// 直接定位到第 100 字节
fseek(fp, 100, SEEK_SET);
// ========== ftell 获取当前位置 ==========
long pos = ftell(fp);
printf("当前文件指针位置: %ld\n", pos);
if (pos == -1L) {
perror("ftell 失败");
}
// ========== rewind 回到文件开头 ==========
rewind(fp); // 等价于 fseek(fp, 0, SEEK_SET)
// ========== 获取文件大小 ==========
fseek(fp, 0, SEEK_END);
long fileSize = ftell(fp);
printf("文件大小: %ld 字节\n", fileSize);
rewind(fp);
// ========== 在特定位置读写 ==========
// 定位到第 50 字节并读取 20 字节
fseek(fp, 50, SEEK_SET);
char buffer[20];
fread(buffer, 1, 20, fp);
// 定位到第 100 字节并写入
fseek(fp, 100, SEEK_SET);
fwrite("TEST", 1, 4, fp);
fclose(fp);
}
// ========== 一次性读取整个文件 ==========
char* cReadEntireFile(const char* filename) {
FILE* fp = fopen(filename, "rb");
if (!fp) return NULL;
// 获取文件大小
fseek(fp, 0, SEEK_END);
long fileSize = ftell(fp);
rewind(fp);
// 分配内存
char* buffer = (char*)malloc(fileSize + 1);
if (!buffer) {
fclose(fp);
return NULL;
}
// 读取文件
size_t readBytes = fread(buffer, 1, fileSize, fp);
buffer[readBytes] = '\0'; // 字符串终结
fclose(fp);
return buffer;
}
// ========== 文件分块读取 ==========
void cChunkedRead(const char* filename) {
FILE* fp = fopen(filename, "rb");
if (!fp) return;
const size_t CHUNK_SIZE = 1024;
char buffer[CHUNK_SIZE];
size_t bytesRead;
long totalBytes = 0;
// 循环读取固定大小的块
while ((bytesRead = fread(buffer, 1, CHUNK_SIZE, fp)) > 0) {
printf("读取 %zu 字节\n", bytesRead);
totalBytes += bytesRead;
// 处理数据...
}
printf("总共读取 %ld 字节\n", totalBytes);
fclose(fp);
}2.6 C风格错误处理详解
#include <cstdio>
#include <cerrno>
#include <cstring>
void cErrorHandling() {
FILE* fp = fopen("nonexistent.txt", "r");
// ========== 检查文件指针 ==========
if (fp == NULL) {
// perror 打印错误信息到 stderr
perror("错误");
// errno 全局变量存储错误代码
printf("错误代码: %d\n", errno);
// strerror 获取错误描述
printf("错误描述: %s\n", strerror(errno));
return;
}
// ========== feof 检查是否到达文件末尾 ==========
if (feof(fp)) {
printf("已到达文件末尾\n");
}
// ========== ferror 检查是否发生读写错误 ==========
if (ferror(fp)) {
printf("发生读写错误\n");
perror("详细信息");
}
// ========== clearerr 清除错误标志 ==========
clearerr(fp); // 重置 EOF 和错误标志
fclose(fp);
}
// ========== 详细的错误处理示例 ==========
int cRobustRead(const char* filename) {
FILE* fp = fopen(filename, "r");
if (fp == NULL) {
fprintf(stderr, "无法打开文件 '%s': %s\n",
filename, strerror(errno));
return -1;
}
char line[256];
int lineNum = 0;
while (fgets(line, sizeof(line), fp) != NULL) {
lineNum++;
printf("行 %d: %s", lineNum, line);
}
// 判断循环退出的原因
if (feof(fp)) {
printf("成功读取到文件末尾\n");
} else if (ferror(fp)) {
fprintf(stderr, "读取过程中出现错误: %s\n",
strerror(errno));
fclose(fp);
return -1;
}
fclose(fp);
return 0;
}
// ========== 处理 errno ==========
void cErrnoHandling() {
// 清除 errno
errno = 0;
FILE* fp = fopen("test.txt", "r");
if (fp == NULL) {
// errno 值对应不同的错误
switch (errno) {
case ENOENT:
printf("文件不存在\n");
break;
case EACCES:
printf("权限不足\n");
break;
case EMFILE:
printf("打开的文件过多\n");
break;
default:
printf("其他错误: %s\n", strerror(errno));
}
}
}2.7 C风格 fprintf 与 printf 对比
#include <cstdio>
void cFprintfComparison() {
// printf 输出到标准输出(屏幕)
printf("这是输出到屏幕\n");
FILE* fp = fopen("output.txt", "w");
// fprintf 输出到指定文件
fprintf(fp, "这是输出到文件\n");
fprintf(fp, "数字: %d, 字符串: %s\n", 123, "hello");
// fprintf 也可以输出到标准输出或错误输出
fprintf(stdout, "标准输出\n");
fprintf(stderr, "标准错误\n");
fclose(fp);
}三、高级文件操作
3.1 文件复制
#include <fstream>
#include <iostream>
#include <cstring>
using namespace std;
// ========== 文本文件复制 ==========
bool copyTextFile(const string& source, const string& dest) {
ifstream srcFile(source);
ofstream destFile(dest);
if (!srcFile || !destFile) {
return false;
}
string line;
while (getline(srcFile, line)) {
destFile << line << "\n";
}
srcFile.close();
destFile.close();
return true;
}
// ========== 二进制文件复制 ==========
bool copyBinaryFile(const string& source, const string& dest) {
ifstream srcFile(source, ios::binary);
ofstream destFile(dest, ios::binary);
if (!srcFile || !destFile) {
return false;
}
// 方法1:使用缓冲区复制
destFile << srcFile.rdbuf();
srcFile.close();
destFile.close();
return true;
}
// ========== 带进度的大文件复制 ==========
bool copyLargeFile(const string& source, const string& dest) {
ifstream srcFile(source, ios::binary);
ofstream destFile(dest, ios::binary);
if (!srcFile || !destFile) {
return false;
}
const size_t BUFFER_SIZE = 1024 * 1024; // 1MB
char* buffer = new char[BUFFER_SIZE];
srcFile.seekg(0, ios::end);
long totalSize = srcFile.tellg();
srcFile.seekg(0, ios::beg);
long copiedSize = 0;
while (srcFile.read(buffer, BUFFER_SIZE) || srcFile.gcount() > 0) {
destFile.write(buffer, srcFile.gcount());
copiedSize += srcFile.gcount();
cout << "进度: " << (copiedSize * 100 / totalSize) << "%" << "\r";
}
cout << "\n复制完成!" << endl;
delete[] buffer;
srcFile.close();
destFile.close();
return true;
}3.2 文件合并和分割
#include <fstream>
#include <iostream>
#include <vector>
using namespace std;
// ========== 合并多个文件 ==========
bool mergeFiles(const vector<string>& fileList, const string& outputFile) {
ofstream outFile(outputFile, ios::binary);
if (!outFile) {
return false;
}
for (const auto& file : fileList) {
ifstream inFile(file, ios::binary);
if (!inFile) {
cerr << "无法打开: " << file << endl;
continue;
}
outFile << inFile.rdbuf();
inFile.close();
}
outFile.close();
return true;
}
// ========== 分割大文件 ==========
bool splitFile(const string& sourceFile, const string& prefix,
long partSize) {
ifstream srcFile(sourceFile, ios::binary);
if (!srcFile) return false;
char* buffer = new char[partSize];
int partNum = 1;
while (srcFile.read(buffer, partSize) || srcFile.gcount() > 0) {
// 生成分片文件名
char partFilename[256];
sprintf(partFilename, "%s.part%d", prefix.c_str(), partNum);
ofstream partFile(partFilename, ios::binary);
partFile.write(buffer, srcFile.gcount());
partFile.close();
cout << "创建分片: " << partFilename
<< " (" << srcFile.gcount() << " 字节)" << endl;
partNum++;
}
delete[] buffer;
srcFile.close();
return true;
}3.3 文件搜索和替换
#include <fstream>
#include <iostream>
#include <string>
using namespace std;
// ========== 搜索文件中的文本 ==========
int searchInFile(const string& filename, const string& keyword) {
ifstream inFile(filename);
if (!inFile) return -1;
string line;
int count = 0;
int lineNum = 0;
while (getline(inFile, line)) {
lineNum++;
// 查找关键词
size_t pos = line.find(keyword);
if (pos != string::npos) {
count++;
cout << "行 " << lineNum << ": " << line << endl;
}
}
inFile.close();
return count;
}
// ========== 替换文件中的文本 ==========
bool replaceInFile(const string& filename,
const string& oldText,
const string& newText) {
ifstream inFile(filename);
if (!inFile) return false;
// 读取所有内容
string content((istreambuf_iterator<char>(inFile)),
istreambuf_iterator<char>());
inFile.close();
// 替换所有出现的文本
size_t pos = 0;
while ((pos = content.find(oldText, pos)) != string::npos) {
content.replace(pos, oldText.length(), newText);
pos += newText.length();
}
// 写回文件
ofstream outFile(filename);
if (!outFile) return false;
outFile << content;
outFile.close();
return true;
}3.4 文件比较
#include <fstream>
#include <iostream>
using namespace std;
// ========== 比较两个文件是否相同 ==========
bool compareFiles(const string& file1, const string& file2) {
ifstream f1(file1, ios::binary);
ifstream f2(file2, ios::binary);
if (!f1 || !f2) return false;
// 检查文件大小
f1.seekg(0, ios::end);
f2.seekg(0, ios::end);
if (f1.tellg() != f2.tellg()) {
return false;
}
// 重置指针
f1.seekg(0, ios::beg);
f2.seekg(0, ios::beg);
// 逐字符比较
const size_t BUFFER_SIZE = 4096;
char buffer1[BUFFER_SIZE];
char buffer2[BUFFER_SIZE];
while (f1.read(buffer1, BUFFER_SIZE) || f1.gcount() > 0) {
f2.read(buffer2, f1.gcount());
if (memcmp(buffer1, buffer2, f1.gcount()) != 0) {
return false;
}
}
return true;
}四、综合示例
4.1 配置文件读取器
#include <fstream>
#include <iostream>
#include <map>
#include <string>
#include <sstream>
using namespace std;
class ConfigFile {
private:
map<string, string> configs;
string filename;
string trim(const string& str) {
size_t first = str.find_first_not_of(" \t\r\n");
if (first == string::npos) return "";
size_t last = str.find_last_not_of(" \t\r\n");
return str.substr(first, (last - first + 1));
}
public:
ConfigFile(const string& fname) : filename(fname) {
load();
}
void load() {
ifstream file(filename);
if (!file.is_open()) {
cerr << "无法打开配置文件" << endl;
return;
}
string line;
while (getline(file, line)) {
// 跳过注释和空行
if (line.empty() || line[0] == '#') {
continue;
}
// 查找 = 号
size_t eqPos = line.find('=');
if (eqPos != string::npos) {
string key = trim(line.substr(0, eqPos));
string value = trim(line.substr(eqPos + 1));
configs[key] = value;
}
}
file.close();
}
string get(const string& key, const string& defaultValue = "") {
auto it = configs.find(key);
return (it != configs.end()) ? it->second : defaultValue;
}
int getInt(const string& key, int defaultValue = 0) {
string value = get(key);
return value.empty() ? defaultValue : stoi(value);
}
float getFloat(const string& key, float defaultValue = 0.0f) {
string value = get(key);
return value.empty() ? defaultValue : stof(value);
}
void set(const string& key, const string& value) {
configs[key] = value;
}
void save() {
ofstream file(filename);
if (!file.is_open()) {
cerr << "无法保存配置文件" << endl;
return;
}
file << "# Configuration file\n";
file << "# 自动生成,请勿手动修改\n\n";
for (const auto& pair : configs) {
file << pair.first << "=" << pair.second << "\n";
}
file.close();
}
};
// 使用示例
int main() {
ConfigFile config("app.conf");
cout << "应用名称: " << config.get("app.name", "默认应用") << endl;
cout << "端口号: " << config.getInt("server.port", 8080) << endl;
cout << "超时时间: " << config.getFloat("server.timeout", 30.0f) << "s" << endl;
return 0;
}4.2 CSV文件处理器
#include <fstream>
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
using namespace std;
class CSVReader {
private:
vector<vector<string>> data;
vector<string> parseLine(const string& line) {
vector<string> fields;
stringstream ss(line);
string field;
while (getline(ss, field, ',')) {
// 去除前后空格
size_t start = field.find_first_not_of(" \t");
size_t end = field.find_last_not_of(" \t");
if (start != string::npos) {
fields.push_back(field.substr(start, end - start + 1));
} else {
fields.push_back("");
}
}
return fields;
}
public:
bool read(const string& filename) {
ifstream file(filename);
if (!file.is_open()) {
cerr << "无法打开CSV文件" << endl;
return false;
}
data.clear();
string line;
while (getline(file, line)) {
if (!line.empty()) {
data.push_back(parseLine(line));
}
}
file.close();
return true;
}
void display() {
for (size_t i = 0; i < data.size(); ++i) {
for (size_t j = 0; j < data[i].size(); ++j) {
cout << data[i][j];
if (j < data[i].size() - 1) cout << " | ";
}
cout << "\n";
}
}
vector<string> getRow(size_t index) {
return (index < data.size()) ? data[index] : vector<string>();
}
size_t rowCount() const {
return data.size();
}
};
// 使用示例
int main() {
CSVReader csv;
if (csv.read("data.csv")) {
cout << "读取了 " << csv.rowCount() << " 行数据" << endl;
csv.display();
}
return 0;
}五、常见问题和解决方案
5.1 文件读写常见问题
#include <fstream>
#include <iostream>
using namespace std;
// 问题1:文件打开失败
void problem1_FileFail() {
ifstream file("nonexistent.txt");
// 错误做法
// string line;
// getline(file, line); // 直接使用,可能导致未定义行为
// 正确做法
if (!file.is_open()) {
cerr << "文件打开失败!" << endl;
return;
}
string line;
getline(file, line);
file.close();
}
// 问题2:混淆 ios::app 和 ios::ate
void problem2_AppVsAte() {
// ios::app - 每次写入都在末尾,不能改变指针
ofstream appFile("data.txt", ios::app);
appFile << "追加内容1\n";
// 下次写入自动在末尾,即使手动修改指针也无效
// ios::ate - 打开后指针在末尾,但可以修改
fstream ateFile("data.txt", ios::ate | ios::in | ios::out);
ateFile.seekg(0, ios::beg); // 可以定位
char ch;
ateFile.get(ch); // 从开头读取
}
// 问题3:文本和二进制模式混淆
void problem3_TextVsBinary() {
// 文本模式:自动转换换行符
// Windows: \n <-> \r\n
// Unix: \n 保持不变
ofstream textFile("text.txt"); // 默认文本模式
textFile << "第一行\n第二行\n";
textFile.close();
// 二进制模式:不进行任何转换
ofstream binFile("binary.bin", ios::binary);
binFile << "第一行\n第二行\n"; // 直接写入 \n
binFile.close();
}
// 问题4:读取后没有清除状态标志
void problem4_StateFlags() {
ifstream file("data.txt");
string line;
while (getline(file, line)) {
cout << line << endl;
}
// 读到 EOF 后,failbit 和 eofbit 都被设置
file.clear(); // 必须清除状态,否则后续操作失败
file.seekg(0, ios::beg); // 现在可以重新读取
while (getline(file, line)) {
cout << line << endl;
}
file.close();
}
// 问题5:getline 包含换行符的误解
void problem5_GetlineNewline() {
ifstream file("data.txt");
string line;
getline(file, line); // 不包含 \n,会自动去掉
cout << "行长度: " << line.length() << endl;
cout << "最后一个字符: " << (int)line.back() << endl; // 不是 \n
}
// 问题6:二进制读写时忘记类型转换
void problem6_BinaryTypeCast() {
struct Data {
int value;
float score;
};
ofstream file("data.bin", ios::binary);
Data d = {100, 3.14f};
// 错误做法(无法编译或产生错误)
// file.write(&d, sizeof(d));
// 正确做法
file.write(reinterpret_cast<char*>(&d), sizeof(d));
file.close();
}
// 问题7:大文件处理导致内存溢出
void problem7_LargeFile() {
// 错误做法:一次性读入整个文件
// ifstream file("huge.dat");
// string content((istreambuf_iterator<char>(file)),
// istreambuf_iterator<char>());
// 可能导致内存不足
// 正确做法:分块读取
ifstream file("huge.dat", ios::binary);
const size_t CHUNK = 1024 * 1024; // 1MB
char buffer[CHUNK];
while (file.read(buffer, CHUNK) || file.gcount() > 0) {
// 处理数据块
size_t bytesRead = file.gcount();
}
file.close();
}
// 问题8:文件指针未正确初始化
void problem8_PointerInit() {
fstream file("data.txt", ios::in | ios::out);
// 问题:直接进行操作,指针位置不确定
// file << "数据";
// 正确做法:显式初始化指针位置
file.seekg(0, ios::beg); // 从开头读取
file.seekp(0, ios::end); // 在末尾写入
}5.2 性能优化技巧
#include <fstream>
#include <iostream>
using namespace std;
// 优化1:禁用同步提高性能
void optimization1_FastIO() {
// 这些设置会加快I/O速度
ios::sync_with_stdio(false);
cin.tie(nullptr);
ifstream file("large.txt");
string line;
while (getline(file, line)) {
// 处理数据
}
file.close();
}
// 优化2:使用二进制模式避免转换
void optimization2_BinaryMode() {
// 文本模式需要处理换行符转换,较慢
// ifstream textFile("data.txt");
// 二进制模式跳过转换,较快
ifstream binFile("data.txt", ios::binary);
}
// 优化3:调整缓冲区大小
void optimization3_BufferSize() {
ifstream file("large.txt");
// 增大缓冲区可提高速度
char buffer[65536]; // 64KB
file.rdbuf()->pubsetbuf(buffer, sizeof(buffer));
string line;
while (getline(file, line)) {
// 处理数据
}
file.close();
}
// 优化4:预分配容量避免重新分配
void optimization4_ReserveCapacity() {
vector<string> lines;
lines.reserve(10000); // 预分配内存
ifstream file("data.txt");
string line;
while (getline(file, line)) {
lines.push_back(line); // 避免频繁重新分配
}
file.close();
}
// 优化5:使用移动语义
void optimization5_MoveSemantics() {
ifstream file("data.txt");
vector<string> lines;
string line;
while (getline(file, line)) {
lines.push_back(move(line)); // 避免复制
}
file.close();
}5.3 安全编程实践
#include <fstream>
#include <iostream>
#include <memory>
using namespace std;
// 安全实践1:RAII管理文件资源
class SafeFileReader {
private:
ifstream file;
public:
SafeFileReader(const string& filename) {
file.open(filename);
if (!file) {
throw runtime_error("无法打开文件: " + filename);
}
}
~SafeFileReader() {
if (file.is_open()) {
file.close();
}
}
string readLine() {
string line;
if (!getline(file, line)) {
throw runtime_error("读取失败");
}
return line;
}
};
// 使用示例
void practice1_RAII() {
try {
SafeFileReader reader("data.txt");
string line = reader.readLine();
cout << line << endl;
// 自动关闭文件
} catch (const exception& e) {
cerr << e.what() << endl;
}
}
// 安全实践2:使用智能指针管理内存
void practice2_SmartPointer() {
ifstream file("data.txt");
// 自动管理动态分配的内存
unique_ptr<char[]> buffer(new char[4096]);
file.read(buffer.get(), 4096);
// 自动释放,无需手动 delete
}
// 安全实践3:验证输入数据
void practice3_ValidateInput() {
ifstream file("data.txt");
int value;
if (file >> value) {
// 验证读取成功
if (!file.fail()) {
cout << "读取的值: " << value << endl;
}
} else {
cerr << "读取失败或格式错误" << endl;
}
file.close();
}
// 安全实践4:避免缓冲区溢出
void practice4_SafeBuffer() {
ifstream file("data.txt");
const size_t BUFFER_SIZE = 256;
char buffer[BUFFER_SIZE];
// 错误做法:直接读取,可能溢出
// file >> buffer;
// 正确做法:限制读取大小
file.read(buffer, BUFFER_SIZE - 1);
buffer[file.gcount()] = '\0';
file.close();
}六、C++ vs C 文件操作对比表
| 特性 | C++ (fstream) | C (FILE*) |
|---|---|---|
| 类型安全 | ✅ 强类型检查 | ❌ 弱类型转换 |
| 异常处理 | ✅ 支持异常机制 | ❌ 返回错误码 |
| 自动资源管理 | ✅ RAII自动关闭 | ❌ 需手动关闭 |
| 操作符重载 | ✅ 支持 << >> | ❌ 不支持 |
| 内存管理 | ✅ 自动管理 | ❌ 需手动分配 |
| 性能 | 稍慢(多重检查) | 稍快 |
| 代码风格 | 面向对象 | 面向过程 |
| 跨平台兼容 | ✅ 更好 | ✅ 标准库 |
| 学习曲线 | 较陡(需理解流) | 较缓(直观简单) |
| 调试难度 | 中等 | 较简单 |
| 适合项目 | 现代C++项目 | 底层系统编程 |