C++ 文件读取函数完全指南

文章目录

  • [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() —— 跳过字符)
    • [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>> vs getline + 解析)
      • [6.2 二进制 vs 文本](#6.2 二进制 vs 文本)
      • [6.3 缓冲区大小](#6.3 缓冲区大小)
      • [6.4 同步与性能(关闭与 stdio 同步)](#6.4 同步与性能(关闭与 stdio 同步))
    • [7. 速查表](#7. 速查表)
    • 总结

C++ 文件读取函数完全指南

>>read,从 getlinegcount ------ 一文搞懂 C++ 中的所有读取接口

C++ 提供了比 C 更丰富、更安全的文件读取方式。它们都封装在 <fstream> 头文件中,核心类是 std::ifstream(输入文件流)。本文将从最常用的格式化输入,到二进制读取,再到底层 streambuf 操作,全方位解析 C++ 的读取函数。


📌 目录

  1. 核心类与打开模式
  2. 格式化读取:operator>>
  3. 无格式读取:get()getline()read()
  4. 状态检查与异常处理
  5. 完整代码示例集
  6. 性能对比与最佳实践
  7. 速查表

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! 🚀

相关推荐
6Hzlia1 小时前
【Hot 100 刷题计划】 LeetCode 300. 最长递增子序列 | C++ 动态规划 & 贪心二分
c++·leetcode·动态规划
阿正的梦工坊1 小时前
JavaScript 闭包 × C++ 类比:彻底搞懂闭包
开发语言·javascript·c++
6Hzlia1 小时前
【Hot 100 刷题计划】 LeetCode 72. 编辑距离 | C++ 经典 DP 增删改状态转移
c++·算法·leetcode
赵优秀一一2 小时前
SQLAlchemy学习记录
开发语言·数据库·python
无限进步_2 小时前
【C++】寻找字符串中第一个只出现一次的字符
开发语言·c++·ide·windows·git·github·visual studio
孬甭_2 小时前
字符函数及字符串函数
c语言·开发语言
摇滚侠2 小时前
Java 进阶教程,全面剖析 Java 多线程编程
java·开发语言
KevinCyao2 小时前
php彩信接口代码示例:PHP使用cURL调用彩信网关发送图文消息
android·开发语言·php
装疯迷窍_A2 小时前
以举证方位线生成工具为例,分享如何在Arcgis中创建Python工具箱(含源码)
开发语言·python·arcgis·变更调查·举证照片