文章目录
- [C++ 文件读取函数完全指南](#C++ 文件读取函数完全指南)
-
- [📌 目录](#📌 目录)
- [1. 核心类与打开模式](#1. 核心类与打开模式)
-
- [1.1 主要类](#1.1 主要类)
- [1.2 打开模式(二进制/文本)](#1.2 打开模式(二进制/文本))
- [2. 格式化读取:`operator>>`](#2. 格式化读取:
operator>>) -
- [2.1 基本用法](#2.1 基本用法)
- [2.2 读取失败处理](#2.2 读取失败处理)
- [2.3 读取循环(直到文件尾)](#2.3 读取循环(直到文件尾))
- [3. 无格式读取](#3. 无格式读取)
-
- [3.1 `get()` ------ 读取单个字符](#3.1
get()—— 读取单个字符) - [3.2 `getline()` ------ 读取一行](#3.2
getline()—— 读取一行) - [3.3 `read()` ------ 二进制块读取](#3.3
read()—— 二进制块读取) - [3.4 `peek()` ------ 偷看下一个字符](#3.4
peek()—— 偷看下一个字符) - [3.5 `ignore()` ------ 跳过字符](#3.5
ignore()—— 跳过字符)
- [3.1 `get()` ------ 读取单个字符](#3.1
- [4. 状态检查与异常处理](#4. 状态检查与异常处理)
-
- [4.1 流状态位](#4.1 流状态位)
- [4.2 手动清除状态](#4.2 手动清除状态)
- [4.3 异常模式](#4.3 异常模式)
- [5. 完整代码示例集](#5. 完整代码示例集)
-
- [示例 1:逐行读取文本文件(现代 C++ 风格)](#示例 1:逐行读取文本文件(现代 C++ 风格))
- [示例 2:读取 CSV 文件(格式化 + 忽略)](#示例 2:读取 CSV 文件(格式化 + 忽略))
- [示例 3:二进制读取图片文件头](#示例 3:二进制读取图片文件头)
- [示例 4:使用 `get()` 统计字符频率](#示例 4:使用
get()统计字符频率) - [示例 5:错误处理 + 异常版本](#示例 5:错误处理 + 异常版本)
- [6. 性能对比与最佳实践](#6. 性能对比与最佳实践)
-
- [6.1 `operator>>` vs `getline` + 解析](#6.1
operator>>vsgetline+ 解析) - [6.2 二进制 vs 文本](#6.2 二进制 vs 文本)
- [6.3 缓冲区大小](#6.3 缓冲区大小)
- [6.4 同步与性能(关闭与 stdio 同步)](#6.4 同步与性能(关闭与 stdio 同步))
- [6.1 `operator>>` vs `getline` + 解析](#6.1
- [7. 速查表](#7. 速查表)
- 总结
C++ 文件读取函数完全指南
从
>>到read,从getline到gcount------ 一文搞懂 C++ 中的所有读取接口
C++ 提供了比 C 更丰富、更安全的文件读取方式。它们都封装在 <fstream> 头文件中,核心类是 std::ifstream(输入文件流)。本文将从最常用的格式化输入,到二进制读取,再到底层 streambuf 操作,全方位解析 C++ 的读取函数。
📌 目录
- 核心类与打开模式
- 格式化读取:
operator>> - 无格式读取:
get()、getline()、read() - 状态检查与异常处理
- 完整代码示例集
- 性能对比与最佳实践
- 速查表
1. 核心类与打开模式
1.1 主要类
| 类 | 用途 |
|---|---|
std::ifstream |
只读文件流 |
std::ofstream |
只写文件流 |
std::fstream |
读写文件流 |
1.2 打开模式(二进制/文本)
cpp
#include <fstream>
// 文本模式(默认)
std::ifstream ifs("data.txt"); // 等价于 std::ios::in
// 二进制模式(必须显式指定)
std::ifstream ifs("data.bin", std::ios::binary);
常用模式组合:
| 模式 | 含义 |
|---|---|
std::ios::in |
读(ifstream 默认) |
std::ios::out |
写(ofstream 默认) |
std::ios::binary |
二进制模式(不转换换行符) |
std::ios::ate |
打开后立即移到文件尾 |
std::ios::app |
追加模式 |
2. 格式化读取:operator>>
2.1 基本用法
operator>> 用于读取格式化的数据,它会自动跳过空白字符(空格、制表符、换行符),并根据目标变量类型解析。
cpp
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::ifstream ifs("data.txt");
if (!ifs) {
std::cerr << "打开文件失败\n";
return 1;
}
int age;
double salary;
std::string name;
ifs >> name >> age >> salary; // 读取三个值
std::cout << "姓名: " << name << "\n年龄: " << age << "\n薪水: " << salary << "\n";
return 0;
}
假设 data.txt 内容:
Alice 25 55000.5
输出:
姓名: Alice
年龄: 25
薪水: 55000.5
2.2 读取失败处理
当类型不匹配或文件结束时,operator>> 会设置流状态位,后续读取将失效。
cpp
int value;
if (ifs >> value) {
// 读取成功
} else {
// 读取失败(可能是文件尾或格式错误)
}
2.3 读取循环(直到文件尾)
cpp
int sum = 0, num;
while (ifs >> num) {
sum += num;
}
std::cout << "总和: " << sum << std::endl;
3. 无格式读取
无格式读取不会跳过空白字符,直接读取原始字节序列。
3.1 get() ------ 读取单个字符
cpp
char ch;
while (ifs.get(ch)) { // 读取一个字符(包括空白)
std::cout << ch;
}
get() 的三种重载:
| 原型 | 说明 |
|---|---|
int get() |
返回字符(int),EOF 时返回 EOF |
istream& get(char& c) |
引用方式读取,返回流引用 |
istream& get(char* s, streamsize n) |
读取最多 n-1 个字符到数组,自动添加 \0 |
3.2 getline() ------ 读取一行
cpp
char line[256];
ifs.getline(line, 256); // 最多读 255 个字符,遇到 '\n' 停止
// 推荐使用 std::string 版本
std::string str;
std::getline(ifs, str); // 全局函数,更安全
重要 :getline 会读取并丢弃换行符,但不会将其存入字符串。
3.3 read() ------ 二进制块读取
read() 是 C++ 中最接近 C 的 fread 的函数:
cpp
char buffer[1024];
ifs.read(buffer, 1024); // 尝试读取 1024 字节
std::streamsize bytes = ifs.gcount(); // 实际读取的字节数
参数:
- 第一个参数:
char*,需要强转(C++ 不允许隐式void*转换)。 - 第二个参数:要读取的最大字节数。
示例:读取结构体数组:
cpp
struct Point { double x, y; };
Point pts[100];
ifs.read(reinterpret_cast<char*>(pts), sizeof(pts));
size_t count = ifs.gcount() / sizeof(Point);
3.4 peek() ------ 偷看下一个字符
cpp
char next = ifs.peek(); // 不移动文件指针
if (next == '#') {
// 处理注释行
}
3.5 ignore() ------ 跳过字符
cpp
ifs.ignore(100, '\n'); // 跳过最多 100 个字符,或直到遇到 '\n'
4. 状态检查与异常处理
4.1 流状态位
| 状态 | 成员函数 | 含义 |
|---|---|---|
eofbit |
eof() |
遇到文件尾 |
failbit |
fail() |
逻辑错误(如期望数字但读到字母) |
badbit |
bad() |
致命 I/O 错误(如磁盘损坏) |
goodbit |
good() |
无任何错误 |
cpp
ifs.read(buffer, size);
if (ifs.eof()) {
std::cout << "文件提前结束\n";
} else if (ifs.fail()) {
std::cout << "格式错误\n";
} else if (ifs.bad()) {
std::cout << "致命错误\n";
}
4.2 手动清除状态
cpp
ifs.clear(); // 清除所有错误标志
ifs.seekg(0, std::ios::beg); // 回到文件开头后重新读取
4.3 异常模式
cpp
ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try {
ifs >> value;
} catch (const std::ios_base::failure& e) {
std::cerr << "异常: " << e.what() << "\n";
}
5. 完整代码示例集
示例 1:逐行读取文本文件(现代 C++ 风格)
cpp
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::ifstream ifs("input.txt");
if (!ifs) {
std::cerr << "无法打开文件\n";
return 1;
}
std::string line;
int line_num = 0;
while (std::getline(ifs, line)) {
++line_num;
std::cout << line_num << ": " << line << '\n';
}
}
示例 2:读取 CSV 文件(格式化 + 忽略)
cpp
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
struct Record {
int id;
std::string name;
double score;
};
int main() {
std::ifstream ifs("data.csv");
std::vector<Record> records;
std::string line;
while (std::getline(ifs, line)) {
std::istringstream iss(line);
Record r;
char comma;
if (iss >> r.id >> comma >> r.name >> comma >> r.score) {
records.push_back(r);
}
}
for (const auto& rec : records) {
std::cout << rec.id << ' ' << rec.name << ' ' << rec.score << '\n';
}
}
示例 3:二进制读取图片文件头
cpp
#include <fstream>
#include <iostream>
#include <cstdint>
#pragma pack(push, 1) // 禁止对齐
struct BMPHeader {
uint16_t type;
uint32_t size;
uint16_t reserved1;
uint16_t reserved2;
uint32_t offset;
};
#pragma pack(pop)
int main() {
std::ifstream ifs("image.bmp", std::ios::binary);
if (!ifs) return 1;
BMPHeader header;
ifs.read(reinterpret_cast<char*>(&header), sizeof(header));
if (ifs.gcount() != sizeof(header)) {
std::cerr << "读取 BMP 头失败\n";
return 1;
}
if (header.type != 0x4D42) { // 'BM'
std::cerr << "不是 BMP 文件\n";
return 1;
}
std::cout << "图片大小: " << header.size << " 字节\n";
std::cout << "像素数据偏移: " << header.offset << " 字节\n";
}
示例 4:使用 get() 统计字符频率
cpp
#include <fstream>
#include <map>
int main() {
std::ifstream ifs("text.txt");
std::map<char, int> freq;
char ch;
while (ifs.get(ch)) { // 包括空格和换行
++freq[ch];
}
for (auto& p : freq) {
std::cout << "字符 '" << p.first << "' 出现 " << p.second << " 次\n";
}
}
示例 5:错误处理 + 异常版本
cpp
#include <fstream>
#include <iostream>
int main() try {
std::ifstream ifs("data.bin", std::ios::binary);
ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit);
char buf[4096];
ifs.read(buf, sizeof(buf));
std::streamsize n = ifs.gcount();
std::cout << "成功读取 " << n << " 字节\n";
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << '\n';
return 1;
}
6. 性能对比与最佳实践
6.1 operator>> vs getline + 解析
operator>>:适合结构规整、以空白分隔的数据。getline+stringstream:适合每行格式不同或需要保留原始行内容的场景。
性能 :operator>> 通常稍快,因为它直接解析,但差异不大。
6.2 二进制 vs 文本
- 二进制 (
read/write):速度快,无解析开销,但不跨平台(大小端、对齐、浮点表示)。 - 文本:可读性好,跨平台,但速度慢。
6.3 缓冲区大小
C++ 流默认有缓冲区,通常 4KB 或更大。对于大文件,可以手动设置缓冲区:
cpp
#include <array>
std::array<char, 65536> buffer;
ifs.rdbuf()->pubsetbuf(buffer.data(), buffer.size());
6.4 同步与性能(关闭与 stdio 同步)
在混合 C 和 C++ I/O 时,默认同步会降低性能。可以关闭同步:
cpp
std::ios_base::sync_with_stdio(false);
std::cin.tie(nullptr);
7. 速查表
| 需求 | 使用函数 | 示例 |
|---|---|---|
| 读取格式化数据(整数、字符串等) | operator>> |
ifs >> x >> str; |
读取一行(含空格)到 std::string |
std::getline(ifs, str) |
while(getline(ifs, line)) |
| 读取一行到 C 风格数组 | ifs.getline(buf, size) |
ifs.getline(buf, 256); |
| 读取一个字符(不跳过空白) | ifs.get(ch) |
while(ifs.get(ch)) |
| 读取二进制块 | ifs.read(buf, count) |
ifs.read(buffer, 1024); |
| 获取实际读取字节数 | ifs.gcount() |
size_t n = ifs.gcount(); |
| 跳过字符 | ifs.ignore(n, delim) |
ifs.ignore(100, '\n'); |
| 偷看下一个字符 | ifs.peek() |
if(ifs.peek() == '#') |
| 检查文件是否打开 | if(!ifs) 或 ifs.is_open() |
if(!ifs) return; |
| 检查文件尾 | ifs.eof() |
if(ifs.eof()) break; |
| 清除错误状态 | ifs.clear() |
ifs.clear(); |
| 回到文件开头 | ifs.seekg(0, std::ios::beg) |
ifs.seekg(0); |
| 获取当前读取位置 | ifs.tellg() |
auto pos = ifs.tellg(); |
总结
C++ 提供了多层次的读取接口:
operator>>:最方便,适合文本格式数据。getline:最常用,适合按行处理。read:最底层,适合二进制数据。get/peek/ignore:精细控制。
结合 RAII 自动关闭文件、流状态检查和异常机制,C++ 的文件读取比 C 的 fread 更加安全、现代化。在绝大多数 C++ 项目中,应优先使用 <fstream> 而非 C 的 FILE*。
最后建议 :除非你需要极致的性能且清楚底层细节,否则避免直接使用 POSIX read 系统调用;C++ 流已经足够高效且更安全。
Happy Coding! 🚀