C++:文件操作

在 C++ 中,文件操作主要依赖于标准库中的 <fstream> 头文件,它提供了三个核心类来处理文件,分别是 输入文件流**ifstream** (读取文件)、输出文件流**ofstream** (写入文件)、输入/输出文件流**fstream**(同时读写文件)。

1. 打开、关闭文件

1)打开文件前的准备工作:定义好文件路径(及多字节路径)和 打开方式。

在 C++ 字符串中 \转义字符 ,使用双反斜杠 \\ 或正斜杠 / 表示路径。如果路径中包含中文,标准 std::string(UTF-8/GBK)直接传给 fstream 在 Windows 下极易导致打开失败。在 Windows 下应使用宽字符 std::wstringstd::wfstream,或者在 C++17 及以上版本中使用 std::filesystem::path

cpp 复制代码
std::string path1 = "C:\\data\\config.txt";   // 双反斜杠
std::string path2 = "C:/data/config.txt";     // 正斜杠(现代 C++ 更推荐,跨平台更好)

#include <filesystem>
namespace fs = std::filesystem;
fs::path filePath = u8"D:\\测试\\数据.txt"; // 使用 utf-8 字面量
std::ifstream file(filePath); // fstream 原生支持 fs::path

打开文件的方式有读、写、文件末尾追加、清空、二进制等基本方式,日常使用可多种方式结合。

cpp 复制代码
// 文件标志位
std::ios::in:      读
std::ios::out:     写(默认清空)
std::ios::app:     追加(保留原内容,指针强制在尾部)
std::ios::trunc:   清空
std::ios::binary:  二进制(禁止系统自动转换 \n 为 \r\n)
//组合方式
ios::out | ios::trunc	文件不存在则创建;存在则清空	           覆盖式配置写入、初始化日志
ios::out | ios::app	    文件不存在则创建;存在则在末尾追加	       日记记录、运行流水账记录
ios::in | ios::out	    文件必须存在,允许读写,不自动清空	       修改已有文件的特定位置(如数据库索引)
ios::in | ios::out | ios::trunc	文件不存在则创建;存在则清空,允许读写	       临时缓冲区文件
2. 读、写文件

文件管理函数有open()、is_open()、close(),负责文件与程序之间的通道建立、断开和状态查询。

cpp 复制代码
void open(const string& filename, openmode mode);   显式打开指定文件。如果流已经打开了一个文件,此操作会直接失败并置 failbit。
bool is_open() const;        检查是否成功打开文件。返回 true 表示通道正常。
void close();              关闭文件,释放操作系统句柄。对写文件流,它会隐式触发刷盘(flush),如果磁盘满等原因导致刷盘失败,流会置 failbit。

文本文件读写成员函数:以字符或字符串为单位处理文本数据。

cpp 复制代码
1. 流插入/流提取运算符:<< 、 >>
格式化读写:自动进行类型转换(int -> string 写入,或string -> float 读出)。
>> 默认会跳过所有空白字符(空格、制表符、换行符)。如果想连空格一起读,不能用它。

istream& get(char& ch);  //从文件中读取单个字符,包括空格和换行符。常用于文件复制或逐字符解析。
istream& getline(char* s, streamsize n, char delim = '\n');  
//这是流对象的成员函数,用于读取底层 C 风格字符串(char[]),遇到定界符 delim(默认换行)或读取了 n-1 个字符后结束。
ostream& put(char ch);  //向文件写入单个字符 ch

二进制文件读写成员函数:数据不经过任何文本转换,原封不动地在内存和磁盘之间拷贝。

cpp 复制代码
ostream& write(const char* s, streamsize n); //从内存地址 s 开始,连续写入 n 个字节的数据到文件中。 常用于直接将结构体、数组、整型变量的内存镜像保存到磁盘。
istream& read(char* s, streamsize n);  //从文件中读取 n 个字节,存入以 s 为起始地址的内存中。
streamsize gcount() const;  //返回上一次非格式化输入函数(如 read(), get(), getline())实际成功读取的字节数/字符数。常用于判断最后一次读二进制块时到底读到了多少数据。

文件指针定位与随机访问函数:每个流对象内部都有一个记录读写位置的指针。输入流(Get)和输出流(Put)的指针是分开管理的(在 fstream 中它们独立存在但有时会联动)。

cpp 复制代码
streampos tellg(); (Get)://返回输入流指针当前指向文件的第几个字节。
streampos tellp(); (Put)://返回输出流指针当前指向文件的第几个字节。
istream& seekg(streampos pos);//绝对定位,直接跳到文件的第 pos 个字节。
istream& seekg(streamoff off, ios_base::seekdir dir);//相对定位。off:偏移量(可正可负),dir:相对基准,可选值:ios::beg(文件开头),ios::cur(当前位置),ios::end(文件末尾)
ostream& seekp(streampos pos); / seekp(streamoff off, seekdir dir);//对输出流指针做同样的操作。

流状态与异常处理成员函数:流内部有一个状态字(通常是几位二进制),记录当前流是否健康。

在 C++ 文件流操作中,流状态(Stream State)管理是整个输入/输出体系的灵魂。

当程序在读写文件时,底层发生的任何情况(如读到末尾、类型不匹配、硬件故障)都会实时反映在流的状态位中。C++ 标准库使用一个状态掩码 iostate(通常是内部定义的位图标志)来记录状态。下面我们逐一拆解这些核心成员函数、它们的函数原型、返回值、以及触发条件的底层机制

流内部的 4 个基础状态常量,它们定义在 std::ios_base 中。流完全正常:goodbit (0000)、到达文件末尾:eofbit (0001)、发生逻辑错误可恢复:failbit (0010)、发生严重物理错误不可恢复:badbit (0100)。

流状态查询成员函数

cpp 复制代码
bool good() const;  //返回true(当且仅当没有任何错误标志位被置位时);否则返回 false。
//触发与应用条件:只有在 rdstate() == goodbit 时,此函数才返回 true。
bool eof() const;  //返回true(流已经越过了文件的最后一个字符);否则返回 false。
//触发条件:只有当上一次读取操作尝试去读越过文件末尾(EOF)的字节,且读不到数据时,eofbit 才会被点亮。
bool fail() const;  //返回true(当 failbit 或 badbit 任意一个被点亮时);否则返回 false。
//触发条件:格式解析失败(最常见):文件里存的是字符串 "abc",代码却尝试执行 int n; file >> n;。此时类型不匹配,触发 failbit。打开文件失败:调用 file.open() 时,若文件不存在或无权限,会直接触发 failbit。fail() 为真意味着发生了逻辑错误,但文件通道本身可能没坏,清理掉脏数据并重置状态后,流还可以继续用。
bool bad() const;  //返回true(底层 badbit 被点亮时);否则返回 false。
//触发条件:无法恢复的物理错误:在写入中途,磁盘空间满了(Disk Full);或者读取存储在 U 盘上的文件时,U 盘被强行拔出。流的内部缓冲区(streambuf)分配内存失败或发生严重流失控。
iostate rdstate() const;  //返回 std::ios_base::iostate 类型的位掩码(组合值),代表当前流的所有状态。
//触发条件:精确组合判断时(例如既想知道是否到末尾,又想知道是否出错)
void clear(iostate state = goodbit);  //

示例

cpp 复制代码
#include <iostream>
#include <fstream>
#include <vector>
#include <string>

// 1. 文本文件处理函数(写入与读取)
void handleTextFile() {
    std::cout << "--- 开始文本文件操作 ---" << std::endl;
    const std::string textFilename = "sample_text.txt";
    std::ofstream outFile(textFilename, std::ios::out | std::ios::trunc);
    if (!outFile.is_open()) {     // 判断是否成功打开
        std::cerr << "【错误】无法打开文本文件进行写入!" << std::endl;
        return;
    }
    // 写入数据并判断是否成功
    outFile << "Line 1: Hello C++ File I/O.\n";
    outFile << "Line 2: Test textual data.\n";
    if (!outFile.good()) {
        std::cerr << "【错误】文本文件写入过程中发生错误!" << std::endl;
        outFile.close();
        return;
    }
    // 关闭文件并校验
    outFile.close();
    if (outFile.fail()) {
        std::cerr << "【错误】文本文件关闭(刷盘)失败!" << std::endl;
        return;
    }
    std::cout << "文本文件写入并安全关闭成功。" << std::endl;

    // ---------------- [读取阶段] ----------------
    std::ifstream inFile(textFilename, std::ios::in);
    if (!inFile.is_open()) {
        std::cerr << "【错误】无法打开文本文件进行读取!" << std::endl;
        return;
    }
    std::string line;
    // 正确的循环判定:直接将 getline 放进条件中, 它能同时准确判断"是否读到末尾"和"是否中途出错"
    while (std::getline(inFile, line)) {
        std::cout << "  [读出文本]: " << line << std::endl;
    }

    // 退出循环后,审计原因
    if (inFile.eof()) {
        std::cout << "【状态】文本文件已成功、完整地读到末尾。" << std::endl;
    } else if (inFile.fail()) {
        std::cerr << "【错误】文本文件读取中途因格式或逻辑错误中断!" << std::endl;
    }

    inFile.close();
}

// 2. 二进制文件处理函数(逐字节写入与读取)
void handleBinaryFile() {
    std::cout << "\n--- 开始二进制文件操作 ---" << std::endl;
    const std::string binFilename = "sample_binary.dat";
    
    // 准备一些模拟的原始二进制数据 (4个字节)
    std::vector<uint8_t> writeData = {0xDE, 0xAD, 0xBE, 0xEF}; 

    // ---------------- [写入阶段] ----------------
    std::ofstream outFile(binFilename, std::ios::out | std::ios::binary | std::ios::trunc);
    if (!outFile.is_open()) {
        std::cerr << "【错误】无法打开二进制文件进行写入!" << std::endl;
        return;
    }
    // 逐字节写入
    for (size_t i = 0; i < writeData.size(); ++i) {
        outFile.write(reinterpret_cast<const char*>(&writeData[i]), 1);
        
        // 每写一次都校验流状态
        if (!outFile.good()) {
            std::cerr << "【错误】二进制逐字节写入时在位置 " << i << " 发生异常!" << std::endl;
            outFile.close();
            return;
        }
    }
    outFile.close();
    if (outFile.fail()) {
        std::cerr << "【错误】二进制文件关闭失败!" << std::endl;
        return;
    }
    std::cout << "二进制文件逐字节写入并安全关闭成功。" << std::endl;

    // ---------------- [读取阶段] ----------------
    std::ifstream inFile(binFilename, std::ios::in | std::ios::binary);
    if (!inFile.is_open()) {
        std::cerr << "【错误】无法打开二进制文件进行读取!" << std::endl;
        return;
    }
    std::cout << "  [读出二进制数据 (1字节为单位)]:" << std::endl;
    char byteBuffer; // 用于承载 1 字节的缓冲区

    // 循环判定:使用 read() 尝试读取 1 字节,read() 返回流对象的引用,只要成功读取,流状态就为 good,循环继续
    while (inFile.read(&byteBuffer, 1)) {
        // gcount() 成员函数可以二次确认上一次确实读到了 1 个字节
        if (inFile.gcount() == 1) {
            // 以十六进制格式打印输出查看
            std::cout << "    字节数据: 0x" << std::hex << std::uppercase 
                      << (static_cast<int>(byteBuffer) & 0xFF) << std::endl;
        }
    }
    std::cout << std::dec; // 恢复标准输出的流格式(防止影响后续输出)
    if (inFile.eof()) {   // 退出循环后的精细化状态审计
        std::cout << "【状态】二进制文件已成功、完整地读到末尾。" << std::endl;
    } else if (inFile.fail()) {
        std::cerr << "【错误】二进制文件读取因中途损坏或错误中断!" << std::endl;
    }
    inFile.close();
}

int main() {
    handleTextFile();
    handleBinaryFile();
    return 0;
}
相关推荐
Lhan.zzZ1 小时前
C++多线程——std::thread与condition_variable形象理解
c++
头歌实践平台2 小时前
C++面向对象 - 运算符重载的应用
开发语言·c++·算法
思麟呀2 小时前
C++11并发编程:互斥锁
linux·开发语言·c++·windows
AC赳赳老秦2 小时前
OpenClaw批量任务队列优化:解决任务堆积、执行缓慢、优先级混乱问题
java·大数据·数据库·c++·自动化·php·openclaw
晚风予卿云月2 小时前
《二分答案》算法练习
数据结构·c++·算法·二分·竞赛·算法随笔
郭涤生2 小时前
C++ 各类数据的内存分区与读写性能详解
开发语言·c++
j_xxx404_2 小时前
Linux 线程日志系统设计:从策略模式、RAII 到 pthread 线程安全与内核写入路径|附源码
linux·运维·服务器·开发语言·c++·人工智能·策略模式
飞天狗1112 小时前
2025第十六届蓝桥杯c/c++B组国赛题解
c语言·c++·算法·蓝桥杯
努力努力再努力wz2 小时前
【Qt入门系列】:QLabel控件详解:从文本显示到图片展示,再到内容布局与伙伴机制
android·开发语言·数据结构·数据库·c++·qt·mysql