C++中IO文件输入输出知识详解和注意事项

发布于:2025-05-30 ⋅ 阅读:(24) ⋅ 点赞:(0)

以下内容将从文件流类体系打开模式文本与二进制 I/O随机访问错误处理性能优化等方面,详解 C++ 中文件输入输出的使用要点,并配以示例。


一、文件流类体系

C++ 标准库提供三种文件流类型,均定义在 <fstream> 中:

  • std::ifstream:输入文件流,用于从文件读取(继承自 std::istream)。
  • std::ofstream:输出文件流,用于向文件写入(继承自 std::ostream)。
  • std::fstream:读写文件流,可同时做输入和输出(继承自两者)。

它们的典型用法与标准流 (cin/cout) 类似,但需要先打开文件。


二、打开模式(std::ios_base::openmode

打开时可指定下列模式的按位或(|)组合:

模式 含义
std::ios::in 打开用于读操作
std::ios::out 打开用于写操作
std::ios::app 所有写操作均追加到文件末尾
std::ios::trunc 打开时清空已有内容(默认对 ofstream 生效)
std::ios::binary 二进制模式(屏蔽文本模式下的换行转换)
std::ios::ate 打开后立即定位到文件末尾

示例:

std::ifstream fin("data.txt", std::ios::in);
std::ofstream fout("out.txt", std::ios::out | std::ios::trunc);
std::fstream fs("db.bin", std::ios::in|std::ios::out|std::ios::binary);

三、文本文件 I/O

1. 写入文本

  • operator<< 与格式化

    std::ofstream fout("log.txt");
    fout << "Count = " << count << '\n';
    
  • std::endl'\n'

    • std::endl 会插入换行并刷新缓冲,频繁使用有性能开销;推荐输出 '\n'

2. 读取文本

  • 按词/按行读取

    std::ifstream fin("data.txt");
    std::string word;
    while (fin >> word) { /* 按空白分隔 */ }
    
    fin.clear(); fin.seekg(0);
    std::string line;
    while (std::getline(fin, line)) { /* 读取整行,不含换行符 */ }
    
  • 混合 >>getline
    使用 >> 后会留下换行符,若紧接 getline,会读入一个空行。
    解决:每次切换前调用 fin.ignore() 跳过残留的 '\n'


四、二进制文件 I/O

当读写原始字节或 POD 结构时,选用二进制模式并使用 read/write

struct Record { int id; double value; };

void writeRecords(const std::vector<Record>& recs) {
    std::ofstream fout("rec.bin", std::ios::out|std::ios::binary);
    fout.write(reinterpret_cast<const char*>(recs.data()),
               recs.size()*sizeof(Record));
}

std::vector<Record> readRecords() {
    std::ifstream fin("rec.bin", std::ios::in|std::ios::binary);
    fin.seekg(0, std::ios::end);
    std::size_t size = fin.tellg() / sizeof(Record);
    fin.seekg(0, std::ios::beg);
    std::vector<Record> recs(size);
    fin.read(reinterpret_cast<char*>(recs.data()),
             size*sizeof(Record));
    return recs;
}
  • 注意:直接写整体 vector 只对标准布局(POD)类型安全。若含指针或非平凡类型,需循环写每个字段或序列化。

五、随机访问(定位)

  • seekg / seekp:设置读/写位置

    fin.seekg(offset, std::ios::beg|std::ios::cur|std::ios::end);
    fout.seekp(...);
    
  • tellg / tellp:返回当前位置

    auto pos = fin.tellg();
    
  • 注意:文本模式下各种平台会对换行做转换,seek/tell 不保证以字节为单位精确跳转;二进制模式下则如你所见。


六、错误处理与异常

1. 状态位检查

if (!fin) { /* 打开失败或已处于错误状态 */ }
if (fin.eof()) { /* 到达末尾 */ }
if (fin.fail()) { /* 格式错误或开关失败 */ }

2. 异常模式

fin.exceptions(std::ios::failbit | std::ios::badbit);
try {
    int x;
    fin >> x;  // 失败时抛 std::ios_base::failure
} catch (const std::ios_base::failure& e) {
    std::cerr << "I/O 异常: " << e.what() << "\n";
}

3. 资源管理

  • 文件流析构时会自动 close(),但若需提前关闭或检测错误,可显式:

    fin.close();
    if (fin.fail()) std::cerr<<"关闭失败\n";
    

七、性能优化

  1. 同步关闭

    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    

    可加速与 C 风格 I/O 的混用场景。

  2. 缓冲区大小

    • 默认缓冲区通常足够;若需高性能可自定义:重载流的 rdbuf()->pubsetbuf()
  3. 减少临时与拷贝

    • 尽量一次性 read/write 大块数据;
    • 对文本分词解析可用 std::string_viewstd::getline 配合 std::stringstream
  4. 避免频繁开关文件

    • 对同一文件的多次写入,宜在同一流对象上完成;若交替读写,用 fstream 且调用 seek/flush 而非反复构造流。

八、综合示例

#include <iostream>
#include <fstream>
#include <vector>
#include <string>

struct Record { int id; double value; };

int main() {
    // 1. 文本写入
    {
        std::ofstream log("app.log", std::ios::out|std::ios::app);
        if (!log) throw std::runtime_error("无法打开日志");
        log << "程序启动\n";
    }

    // 2. 文本读取与解析
    {
        std::ifstream fin("config.txt");
        if (!fin) {
            std::cerr<<"配置文件打开失败\n";
        } else {
            std::string line;
            while (std::getline(fin, line)) {
                // 跳过空行和注释
                if (line.empty() || line[0]=='#') continue;
                std::cout<<"配置: "<<line<<"\n";
            }
        }
    }

    // 3. 二进制读写
    std::vector<Record> recs = {{1,1.23},{2,4.56},{3,7.89}};
    {
        std::ofstream fout("data.bin", std::ios::out|std::ios::binary);
        fout.write(reinterpret_cast<const char*>(recs.data()),
                   recs.size()*sizeof(Record));
    }
    {
        std::ifstream fin("data.bin", std::ios::in|std::ios::binary);
        fin.seekg(0, std::ios::end);
        size_t n = fin.tellg()/sizeof(Record);
        fin.seekg(0);
        std::vector<Record> buf(n);
        fin.read(reinterpret_cast<char*>(buf.data()), n*sizeof(Record));
        std::cout<<"读取 "<<buf.size()<<" 条记录\n";
    }

    return 0;
}

九、注意事项汇总

  1. 选择合适模式:文本 vs 二进制;是否截断或追加。
  2. 检查流状态:打开后、读写后、关闭后都应检查 fail() / bad() / eof()
  3. 混合读写:使用 std::fstream 并在切换前调用 flush() / seekg() / seekp()
  4. 异常安全:开启异常模式或自行检测,避免未捕获错误导致数据不一致。
  5. 平台差异:文本模式下换行转换、字符编码(如 Windows 的 CRLF)可能影响跨平台行为。

网站公告

今日签到

点亮在社区的每一天
去签到