C++:IO库

一、C++ IO库的架构

C++标准库中的IO系统基于流(Stream)​的概念,分为三层结构:

  1. 流对象 (如cin, cout, fstream
  2. 流缓冲区(streambuf,负责底层数据处理)
  3. 数据源/目的地(如内存、文件、控制台)

主要头文件:

  • <ios>:基础IO类定义
  • <istream>/<ostream>:输入输出流
  • <iostream>:控制台输入输出
  • <fstream>:文件流
  • <sstream>:字符串流

二、C++ IO类的层次结构

1. 基础类

  • ios_base:这是所有 IO 类的基类,它定义了一些通用的状态标志、格式标志和事件处理机制。它不涉及具体的输入输出操作,主要用于管理和维护 IO 流的状态和格式。
  • ios :继承自ios_base,它包含了流的缓冲区指针和一些状态信息,为后续的输入输出操作提供了基础。

2. 输入输出类

  • istream :用于输入操作的类,继承自ios。它定义了一系列用于从流中读取数据的成员函数,如operator>>getgetline等。
  • ostream :用于输出操作的类,同样继承自ios。它定义了一系列用于向流中写入数据的成员函数,如operator<<put等。
  • iostream :继承自istreamostream,它既支持输入操作,也支持输出操作。

3. 文件 IO 类

  • ifstream :继承自istream,用于从文件中读取数据。
  • ofstream :继承自ostream,用于向文件中写入数据。
  • fstream :继承自iostream,支持对文件的读写操作。

4. 字符串 IO 类

  • istringstream :继承自istream,用于从字符串中读取数据,就像从输入流中读取一样。
  • ostringstream :继承自ostream,用于将数据写入字符串中。
  • stringstream :继承自iostream,支持对字符串的读写操作。

层次结构示意图

三、核心基类详解

1. ios_base(位于<ios>

  • 功能:定义所有流类的公共属性和状态

  • 关键成员

    复制代码
    // 格式化标志
    fmtflags setf(fmtflags flags); // 设置格式
    streamsize precision(streamsize prec); // 浮点数精度
    
    // 流状态
    iostate rdstate() const; // 获取当前状态
    bool good() const;       // 流是否正常
    bool eof() const;        // 是否到达流末尾

2. ios(继承自ios_base,位于<ios>

  • 功能:管理流缓冲区和错误状态
  • 关键成员
cpp 复制代码
class ios : public ios_base {
public:
    // 流状态管理
    bool good() const;  // 状态正常
    bool eof() const;   // 到达文件尾
    bool fail() const;  // 可恢复错误
    bool bad() const;   // 严重错误
    
    void clear(iostate state = goodbit);
    iostate rdstate() const;
    
    // 流缓冲区操作
    streambuf* rdbuf() const;
    streambuf* rdbuf(streambuf* sb);
};

职责

  • 维护流状态(goodbit、eofbit、failbit、badbit)
  • 管理关联的streambuf对象

3、输入输出流基类

1. istream(输入流,位于<istream>
  • 功能:处理输入操作

  • 关键成员

    cpp 复制代码
    // 基础输入
    istream& operator>>(T& val); // 重载的提取操作符
    int get();                   // 读取单个字符
    istream& getline(char* s, streamsize n); // 读取一行
    
    // 高级操作
    streamsize readsome(char* s, streamsize n); // 读取可用数据
    
    //简易完整版
    class istream : virtual public ios {
    public:
        // 基础输入
        istream& operator>>(int& val);
        istream& operator>>(string& str);
        
        // 非格式化输入
        int get();
        istream& get(char& c);
        istream& getline(char* s, streamsize n);
        
        // 高级操作
        streamsize readsome(char* s, streamsize n);
        istream& ignore(streamsize n = 1, int delim = EOF);
    };

关键方法

  • >> 运算符重载(格式输入)
  • get() 读取单个字符
2. ostream(输出流,位于<ostream>
  • 功能:处理输出操作

  • 关键成员

    cpp 复制代码
    // 基础输出
    ostream& operator<<(T val);   // 重载的插入操作符
    ostream& put(char c);         // 输出单个字符
    
    // 刷新缓冲区
    ostream& flush();            // 强制刷新缓冲区
    
    class ostream : virtual public ios {
    public:
        // 基础输出
        ostream& operator<<(int val);
        ostream& operator<<(const char* str);
        
        // 非格式化输出
        ostream& put(char c);
        ostream& write(const char* s, streamsize n);
        
        // 控制输出位置
        pos_type tellp();
        ostream& seekp(pos_type pos);
    };

关键方法

  • << 运算符重载(格式输出)
  • put() 输出单个字符
  • write() 原始字节输出
3. iostream(继承自istreamostream,位于<istream>
  • 功能:同时支持输入输出的流(如标准控制台流)
cpp 复制代码
class iostream : public istream, public ostream {
public:
    // 多继承类,支持双向操作
};

典型应用

  • 标准控制台流cin/cout/cerr的实现

四、<< 、>>运算符

1. << 运算符(输出运算符)(像水波扩撒)

基本用途

<< 运算符一般用于向输出流里写入数据,最常见的输出流便是 std::cout,它代表标准输出(通常是控制台)。此运算符把数据发送到输出流,然后输出流会把数据显示在对应的设备上。

语法
cpp 复制代码
output_stream << data;//data->output_stream

这里的 output_stream 是输出流对象,像 std::coutstd::ofstream(用于文件输出)等;data 是要输出的数据,可以是基本数据类型(例如 intdoublechar 等),也可以是自定义类型。

示例代码
cpp 复制代码
#include <iostream>

int main() {
    int num = 10;
    double pi = 3.14;
    std::string str = "Hello, World!";

    // 输出整数
    std::cout << "整数: " << num << std::endl;
    // 输出浮点数
    std::cout << "浮点数: " << pi << std::endl;
    // 输出字符串
    std::cout << "字符串: " << str << std::endl;

    return 0;
}
  • std::cout << "整数: ":把字符串 "整数: " 发送到标准输出流。
  • << num:接着把变量 num 的值发送到标准输出流。
  • << std::endl:向标准输出流发送换行符,同时刷新输出缓冲区。
链式调用

<< 运算符支持链式调用,也就是可以连续使用该运算符来输出多个数据。

cpp 复制代码
std::cout << "整数: " << num << ", 浮点数: " << pi << std::endl;

2. >> 运算符(输入运算符)(像水波扩散)

基本用途

>> 运算符通常用于从输入流读取数据,最常见的输入流是 std::cin,它代表标准输入(通常是键盘)。该运算符从输入流中提取数据,并将其存储到指定的变量中。

语法
cpp 复制代码
input_stream >> variable;//input_stream->variable

这里的 input_stream 是输入流对象,如 std::cinstd::ifstream(用于文件输入)等;variable 是要存储数据的变量。

示例代码
cpp 复制代码
#include <iostream>

int main() {
    int num;
    double pi;
    std::string str;

    // 输入整数
    std::cout << "请输入一个整数: ";
    std::cin >> num;

    // 输入浮点数
    std::cout << "请输入一个浮点数: ";
    std::cin >> pi;

    // 输入字符串
    std::cout << "请输入一个字符串: ";
    std::cin >> str;

    // 输出输入的数据
    std::cout << "你输入的整数是: " << num << std::endl;
    std::cout << "你输入的浮点数是: " << pi << std::endl;
    std::cout << "你输入的字符串是: " << str << std::endl;

    return 0;
}
  • std::cin >> num:从标准输入流中读取一个整数,并将其存储到变量 num 中。
  • std::cin >> pi:从标准输入流中读取一个浮点数,并将其存储到变量 pi 中。
  • std::cin >> str:从标准输入流中读取一个字符串,并将其存储到变量 str 中。
  • 注意,>> 运算符在读取字符串时会忽略空白字符(如空格、制表符、换行符等),直到遇到下一个空白字符为止。
链式调用

>> 运算符也支持链式调用,可以连续使用该运算符来读取多个数据。

cpp 复制代码
std::cin >> num >> pi;

3. 自定义类型的 >><< 运算符重载

对于自定义类型,也可以重载 >><< 运算符,以便支持自定义类型的输入输出操作。

示例代码
cpp 复制代码
#include <iostream>
#include <string>

class Person {
public:
    std::string name;
    int age;

    // 重载 << 运算符
    friend std::ostream& operator<<(std::ostream& os, const Person& p) {
        os << "姓名: " << p.name << ", 年龄: " << p.age;
        return os;
    }

    // 重载 >> 运算符
    friend std::istream& operator>>(std::istream& is, Person& p) {
        std::cout << "请输入姓名: ";
        is >> p.name;
        std::cout << "请输入年龄: ";
        is >> p.age;
        return is;
    }
};

int main() {
    Person p;
    std::cin >> p;
    std::cout << p << std::endl;

    return 0;
}
  • operator<<:这是一个友元函数,用于将 Person 对象的信息输出到输出流中。
  • operator>>:这也是一个友元函数,用于从输入流中读取数据,并将其存储到 Person 对象中。

五、文件流类(<fstream>

1. ifstream(输入文件流)

cpp 复制代码
class ifstream : public istream {
public:
    // 文件操作
    void open(const char* filename, ios_base::openmode mode = ios_base::in);
    void close();
    bool is_open() const;
};
  • 典型用法

    cpp 复制代码
    ifstream fin("data.txt");
    if (fin) { // 检查是否成功打开
        int value;
        fin >> value;
    }

2. ofstream(输出文件流)

cpp 复制代码
class ofstream : public ostream {
public:
    // 支持与ifstream类似的文件操作
};

3. fstream(双向文件流)

  • 继承自iostream,支持读写操作:

    cpp 复制代码
    fstream file("data.txt", ios::in | ios::out);

4.示例:文件复制

cpp 复制代码
ifstream in("source.txt", ios::binary);
ofstream out("dest.txt", ios::binary);

if (in && out) {
    out << in.rdbuf();  // 利用流缓冲区直接复制
}

六、字符串流类(<sstream>

1. istringstream(输入字符串流)
cpp 复制代码
class istringstream : public istream {
public:
    string str() const; // 获取底层字符串
    void str(const string& s); // 设置底层字符串
};
2. ostringstream(输出字符串流)
cpp 复制代码
class ostringstream : public ostream {
public:
    string str() const;
};
3. 典型应用:类型转换
cpp 复制代码
#include <iostream>
#include <sstream>
using namespace std;
int main() {
    // 使用istringstream和ostringstream进行字符串与数值的转换
    string input = "42 3.14";
    istringstream iss(input);
    int a; double b;
    iss >> a >> b; // a=42, b=3.14

    ostringstream oss;
    oss << "Result: " << a*b;
    string output = oss.str(); // "Result: 133.56"
}

七、流缓冲区类(<streambuf>

1.基本概念

流缓冲区类为 I/O 流提供了一个数据缓冲区,起到了数据的临时存储和传输的作用。当程序进行输入操作时,数据会先从输入设备(如文件、键盘)读取到流缓冲区中,然后程序再从缓冲区中获取数据;当进行输出操作时,数据会先被写入到流缓冲区,之后再由缓冲区将数据发送到输出设备(如文件、屏幕)。

2.层次结构

<streambuf> 头文件中定义的流缓冲区类形成了一个层次结构,主要的基类是 std::streambuf,它是所有流缓冲区类的基类,提供了通用的缓冲区管理接口。基于 std::streambuf 派生出了不同类型的流缓冲区类,以适应不同的 I/O 设备和操作:

  • std::filebuf:用于文件 I/O 的流缓冲区类,管理文件的读写操作。
  • std::stringbuf:用于字符串 I/O 的流缓冲区类,管理字符串的读写操作。
  • std::wstreambuf:宽字符版本的 std::streambuf,用于处理宽字符的 I/O 操作。
  • std::wfilebuf:宽字符版本的 std::filebuf,用于宽字符文件的 I/O 操作。
  • std::wstringbuf:宽字符版本的 std::stringbuf,用于宽字符字符串的 I/O 操作。

3.主要成员函数

std::streambuf 类提供了许多成员函数,用于管理缓冲区和进行数据的读写操作:

  • 缓冲区指针操作
    • eback():返回输入缓冲区的起始指针。
    • gptr():返回输入缓冲区的当前读取位置指针。
    • egptr():返回输入缓冲区的结束指针。
    • pbase():返回输出缓冲区的起始指针。
    • pptr():返回输出缓冲区的当前写入位置指针。
    • epptr():返回输出缓冲区的结束指针。
  • 缓冲区填充和刷新操作
    • underflow():当输入缓冲区为空时,该函数负责从输入设备读取数据填充缓冲区。
    • overflow(int c = traits_type::eof()):当输出缓冲区已满时,该函数负责将缓冲区中的数据写入到输出设备,并刷新缓冲区。
    • sync():刷新缓冲区,将缓冲区中的数据写入到输出设备。
  • 数据读写操作
    • sgetc():返回当前输入缓冲区的字符,但不移动读取指针。
    • sbumpc():返回当前输入缓冲区的字符,并将读取指针向后移动一位。
    • sputc(int c):将字符 c 写入到输出缓冲区,并将写入指针向后移动一位。
    • sputn(const char_type* s, std::streamsize n):将字符串 s 中的 n 个字符写入到输出缓冲区。

4.自定义缓冲区(高级用法)

cpp 复制代码
class MyBuffer : public streambuf {
protected:
    int_type overflow(int_type c) override {
        // 自定义输出处理
        return c;
    }
};

// 使用自定义缓冲区
MyBuffer buf;
ostream custom_stream(&buf);
custom_stream << "Hello custom buffer!";

5.使用示例

以下是一个使用 std::filebuf 进行文件读写操作的示例:

cpp 复制代码
#include <iostream>
#include <fstream>
#include <streambuf>

int main() {
    // 打开文件并关联到 filebuf 对象
    std::filebuf fb;
    if (fb.open("test.txt", std::ios::out | std::ios::binary)) {
        // 写入数据到缓冲区
        const char* data = "Hello, World!";
        std::streamsize size = std::char_traits<char>::length(data);
        fb.sputn(data, size);

        // 刷新缓冲区并关闭文件
        fb.pubsync();
        fb.close();
    }

    // 打开文件并关联到 filebuf 对象进行读取操作
    if (fb.open("test.txt", std::ios::in | std::ios::binary)) {
        // 获取缓冲区的大小
        std::streamsize size = fb.pubseekoff(0, std::ios::end, std::ios::in);
        fb.pubseekpos(0, std::ios::in);

        // 读取数据到缓冲区
        char* buffer = new char[size];
        fb.sgetn(buffer, size);

        // 输出读取的数据
        std::cout << "读取的数据: " << buffer << std::endl;

        // 释放缓冲区内存并关闭文件
        delete[] buffer;
        fb.close();
    }

    return 0;
}

八、IO对象

C++标准库中IO对象不允许拷贝或赋值


1、基础现象验证

首先通过代码实验观察现象:

cpp 复制代码
#include <fstream>
using namespace std;

int main() {
    ifstream in1("test.txt");
    ifstream in2 = in1;          // 编译错误:拷贝构造被禁用
    ofstream out;
    out = ofstream("data.txt");  // 编译错误:赋值操作被禁用
}

所有IO类型(如ifstream/ostringstream)均无法进行拷贝构造或赋值操作。


2、底层机制分析

1. 类定义实现(以C++11标准为例)

标准库通过删除拷贝控制成员显式禁用拷贝:

cpp 复制代码
namespace std {
    class basic_ios : public ios_base {
        // ...
        basic_ios(const basic_ios&) = delete;          // 拷贝构造删除
        basic_ios& operator=(const basic_ios&) = delete; // 赋值操作删除
    };
    
    template<typename _CharT, typename _Traits>
    class basic_ostream : virtual public basic_ios<_CharT, _Traits> {
        // 同样禁用拷贝构造和赋值
    };
}

3、禁止拷贝的核心原因

1. ​资源唯一性原则

IO对象(如文件流)管理不可复制资源

  • 文件句柄(File Descriptor)
  • 流缓冲区(streambuf)指针
  • 流状态信息(goodbit/eofbit等)

如果允许拷贝,会导致多个对象操作同一个底层资源:

cpp 复制代码
// 假设允许拷贝的假想场景
ifstream in1("data.txt");
ifstream in2 = in1;

in1 >> x;  // 读取部分数据
in2 >> y;  // 无法确定读取位置,导致竞争状态
2. ​状态一致性维护

每个IO对象维护独立状态:

  • 当前文件读写位置(tellg()/tellp()
  • 错误状态标记(rdstate()
  • 格式控制标志(如精度、进制)

拷贝会导致状态信息的分裂,难以保证一致性。

3. ​避免资源双重释放

如果允许拷贝,析构时会多次关闭同一个文件句柄:

cpp 复制代码
// 假想允许拷贝的析构场景
{
    ifstream in1("data.txt");
    ifstream in2 = in1;
} // in2析构时关闭文件句柄 → in1析构时再次关闭已关闭的句柄 → 未定义行为

4、替代方案与正确用法

1. 使用引用传递
cpp 复制代码
void process_input(istream& is) {  // 通过引用操作流
    int val;
    is >> val;
}

ifstream in("data.txt");
process_input(in);     // 合法:传递引用
process_input(cin);    // 同样适用标准输入流
2. 使用指针传递
cpp 复制代码
void save_data(ostream* os) {
    *os << "Data: " << 42;
}

ofstream out("data.txt");
save_data(&out);       // 显式传递指针
3. 移动语义(C++11+)

虽然标准流对象不可移动,但可自定义包装类:

cpp 复制代码
class StreamWrapper {
    unique_ptr<ostream> os;
public:
    StreamWrapper(ostream&& os) 
      : os(os ? new ostream(os.rdbuf()) : nullptr) {}
    // 实现移动构造/赋值
};

StreamWrapper sw1(ofstream("data.txt"));
StreamWrapper sw2 = std::move(sw1);  // 合法移动
4. 共享流缓冲区

多个对象共享同一个流缓冲区(非拷贝对象):

cpp 复制代码
ifstream in1("data.txt");
ifstream in2(in1.rdbuf());  // 构造新流但共享缓冲区

in1.seekg(100);  // 影响in2的读取位置
char c;
in2 >> c;        // 从位置100开始读取

5、设计模式对比

类型 拷贝语义 移动语义(C++11) 资源管理方式
std::fstream ❌ 禁止 ❌ 禁止 独占资源
std::string ✅ 深拷贝 ✅ 移动 值语义(COW优化)
std::shared_ptr ✅ 引用计数 ✅ 移动 共享所有权

IO对象采用独占所有权模型 ,与unique_ptr类似,禁止拷贝以强制明确资源所有权。


6、陷阱

1. 函数返回值误区
cpp 复制代码
// 错误示例:返回临时流对象
ifstream open_file(const string& name) {
    ifstream f(name);
    return f;  // 尝试拷贝 → 编译错误
}

// 正确做法:返回包装类或使用引用参数
void open_file(const string& name, ifstream& out) {
    out.open(name);
}
2. 容器存储问题
cpp 复制代码
vector<ofstream> streams;  // 编译错误:元素需要可拷贝

// 正确做法:存储指针或包装类
vector<unique_ptr<ofstream>> streams;
streams.emplace_back(make_unique<ofstream>("log1.txt"));

九、条件状态

1、流状态的核心作用

IO流对象(如ifstream/cin)通过状态标志位实时反映操作结果,开发者可通过这些状态:

  1. 检测输入/输出是否成功
  2. 区分错误类型(可恢复错误 vs 致命错误)
  3. 控制程序流(重试/终止)
  4. 实现异常驱动式错误处理

2、状态标志位详解

ios_base类中定义的4个核心状态标志(通过位掩码实现):

标志位 触发场景
goodbit 0x00 流处于正常状态(无错误)
eofbit 0x01 到达文件末尾(End-of-File)或输入流结束(如Ctrl+D)
failbit 0x02 逻辑错误(如类型不匹配),可恢复错误
badbit 0x04 物理错误(如磁盘损坏、无效文件指针),流已不可用

组合状态示例

  • goodbit | eofbit → 无效(实际状态由位运算决定)
  • failbit | badbit → 严重错误

3、状态检测方法

1. 成员函数检测
cpp 复制代码
ifstream in("data.txt");

// 直接检测函数
if (in.good()) { /* 流正常 */ }
if (in.eof())  { /* 到达文件尾 */ }
if (in.fail()) { /* 可恢复错误 */ }
if (in.bad())  { /* 物理损坏 */ }

// 综合检测(等价于 !fail() && !bad() )
if (in) { /* 流可用 */ }  // operator bool()重载
2. 位掩码操作
复制代码
ios_base::iostate state = in.rdstate();

if (state & ios::eofbit) {
    cout << "到达文件末尾" << endl;
}
if (state & (ios::failbit | ios::badbit)) {
    cout << "发生错误" << endl;
}

4、状态管理操作

1. 状态清除(clear)
复制代码
int value;
cin >> value;

if (cin.fail()) {      // 输入非数字时触发
    cin.clear();       // 清除错误状态(恢复到goodbit)
    cin.ignore(1000, '\n'); // 跳过无效输入
    // 重试逻辑...
}

注意clear()默认参数为goodbit,可指定具体状态:

复制代码
in.clear(ios::goodbit | ios::eofbit); // 强制设置特定状态
2. 设置异常掩码(exceptions)
复制代码
ifstream in;
in.exceptions(ios::failbit | ios::badbit); // 设置触发异常的标志位

try {
    in.open("nonexist.txt");  // 打开失败触发ios::failure异常
    int val;
    in >> val;                // 读取失败同样触发异常
} 
catch (const ios::failure& e) {
    cerr << "IO异常: " << e.what() << endl;
}

5、典型应用场景

1. 安全读取循环
复制代码
vector<int> values;
int tmp;

while (cin >> tmp) {  // 自动检测operator>>的返回流状态
    values.push_back(tmp);
}

if (cin.eof()) {
    cout << "正常结束(到达EOF)" << endl;
} 
else if (cin.fail()) {
    cout << "输入格式错误" << endl;
    cin.clear();
}
2. 文件读取容错
复制代码
ifstream in("data.bin", ios::binary);
char buffer[1024];

while (in.read(buffer, sizeof(buffer))) {
    // 正常处理数据块
}

if (in.eof()) {
    // 处理最后不完整的数据块
    streamsize count = in.gcount();
    process(buffer, count);
} 
else if (in.fail()) {
    cerr << "文件读取错误" << endl;
}

6、状态转换机制

1. 状态转换图(简易版)
2. 操作对状态的影响
操作 可能设置的状态位
打开文件失败 failbit
读取到文件末尾 eofbit
类型不匹配 failbit
流缓冲区损坏 badbit
seekg越界 failbit

7、调试

1. 输出流状态
复制代码
void debug_stream_state(const istream& is) {
    ios::iostate state = is.rdstate();
    cout << bitset<4>(state) << " : ";
    
    if (state & ios::badbit)  cout << "bad ";
    if (state & ios::failbit) cout << "fail ";
    if (state & ios::eofbit)  cout << "eof ";
    if (state == ios::goodbit) cout << "good";
    
    cout << endl;
}
2. 结合gdb调试
复制代码
(gdb) p cout.rdstate()
$1 = std::_Ios_Iostate = 0
(gdb) p cerr.rdstate() 
$2 = std::_Ios_Iostate = 4

8、实践

  1. 输入后必检状态 :所有operator>>操作后检查流状态

  2. 优先使用getline :避免>>在行末遗留\n导致状态混乱

  3. 异常慎用原则:文件打开等关键操作使用异常,常规错误使用状态检测

  4. RAII管理状态 :利用作用域自动清除临时状态

    复制代码
    {
        ios::fmtflags old_flags = cout.flags(); // 保存原状态
        cout << hex << 42;                      // 临时修改
        // 自动作用域结束恢复
    }

关键要点:​始终假设IO操作可能失败,并在代码中显式处理所有可能的错误路径。

十、文件的输入和输出

1、基础

1. 包含头文件

首先,需要包含 <fstream> 头文件,该头文件定义了文件流类:

cpp 复制代码
#include <fstream>
#include <string>   // 用于字符串操作
#include <limits>   // 用于numeric_limits
using namespace std;
2. 文件流类
  • ofstream:输出文件流,用于写入文件。
  • ifstream:输入文件流,用于读取文件。
  • fstream:多功能文件流,支持读写操作。

文件流对象生命周期

  1. 对象创建与文件绑定
cpp 复制代码
// 方式1:构造时打开文件
ofstream out("data.txt", ios::app);  // 追加模式

// 方式2:默认构造+后续打开
ifstream in;
in.open("config.ini", ios::in | ios::binary);
  1. 对象销毁
cpp 复制代码
{
    fstream tmp("temp.data");  // 进入作用域时打开文件
    // 操作文件...
} // 离开作用域时自动调用close()

​2**、写入文件(输出操作)​**

文件操作

方法 功能描述
open(filename, mode) 打开文件
is_open() 检查文件是否成功打开
close() 关闭文件并刷新缓冲区

步骤示例:​

cpp 复制代码
// 创建输出文件流对象并打开文件
ofstream outFile("example.txt", ios::out); // ios::out 可省略(默认模式)

// 检查是否成功打开
if (!outFile.is_open()) {
    cerr << "无法打开文件!" << endl;
    return 1;
}

// 写入内容
outFile << "第一行内容" << endl;
outFile << "第二行内容\n";
outFile << 42 << " " << 3.14 << endl;

// 显式关闭文件
outFile.close();

打开模式(ios 标志位):​

模式 描述
ios::out 写入模式(默认,覆盖已有内容)
ios::app 追加模式(在文件末尾添加内容)
ios::binary 二进制模式(非文本文件操作)
ios::trunc 清空文件内容(默认与ios::out共用)

​3**、读取文件(输入操作)​**

步骤示例:​

cpp 复制代码
// 创建输入文件流对象并打开文件
ifstream inFile("example.txt");

// 检查是否成功打开
if (!inFile) { // 等价于 inFile.fail()
    cerr << "无法打开文件!" << endl;
    return 1;
}

// 逐行读取内容
string line;
while (getline(inFile, line)) {
    cout << line << endl;
}

// 重置错误状态并关闭文件
inFile.clear(); // 清除可能的EOF或错误标志
inFile.close();

格式化读取示例:​

cpp 复制代码
int num;
double pi;
if (inFile >> num >> pi) {
    cout << "读取到: " << num << ", " << pi << endl;
} else {
    cerr << "格式错误!" << endl;
    inFile.clear();
    inFile.ignore(numeric_limits<streamsize>::max(), '\n'); // 跳过错误行
}

​4**、二进制文件操作**

写入二进制数据:​

cpp 复制代码
struct Data {
    int id;
    char name[20];
};

Data data = {1, "Alice"};
ofstream binOut("data.bin", ios::binary);
binOut.write(reinterpret_cast<char*>(&data), sizeof(Data));
binOut.close();

读取二进制数据:​

cpp 复制代码
Data data;
ifstream binIn("data.bin", ios::binary);
binIn.read(reinterpret_cast<char*>(&data), sizeof(Data));
cout << "ID: " << data.id << ", Name: " << data.name << endl;
binIn.close();

​5**、文件指针操作**

随机访问文件:​

cpp 复制代码
fstream file("data.txt", ios::in | ios::out);

// 获取当前读取指针位置
streampos pos = file.tellg();

// 移动读取指针到第100字节
file.seekg(100, ios::beg);

// 移动写入指针到文件末尾
file.seekp(0, ios::end);

// 在文件末尾追加内容
file << "\n追加的内容";

file.close();

指针定位常量:​

常量 描述
ios::beg 文件开头(默认)
ios::end 文件末尾
ios::cur 当前位置

​6**、错误处理与状态管理**

检测流状态:​

cpp 复制代码
ifstream file("data.txt");

if (file.rdstate() == ios::eofbit) {
    cout << "到达文件末尾" << endl;
} else if (file.rdstate() & ios::failbit) {
    cerr << "逻辑错误(如类型不匹配)" << endl;
} else if (file.rdstate() & ios::badbit) {
    cerr << "物理错误(如文件损坏)" << endl;
}

file.close();

启用异常处理:​

cpp 复制代码
ifstream file;
file.exceptions(ios::failbit | ios::badbit); // 设置触发异常的标志

try {
    file.open("data.txt");
    // 文件操作...
} catch (const ios::failure& e) {
    cerr << "IO异常: " << e.what() << endl;
}

​7**、实践总结**

  1. RAII原则:利用对象生命周期自动管理资源。

    cpp 复制代码
    { // 自动关闭文件
        ofstream tmpFile("temp.txt");
        tmpFile << "临时数据";
    }
  2. 路径处理:使用原始字符串或正斜杠避免转义

    复制代码
    ofstream log(R"(C:\Users\Logs\app.log)"); // Windows路径
    ofstream config("/etc/app/config.conf");  // Linux路径
  3. 性能优化

    复制代码
    // 减少缓冲区刷新次数
    ofstream log("debug.log");
    log << unitbuf; // 自动刷新(慎用)
  4. 跨平台换行符

    复制代码
    ofstream out("data.txt");
    out << "Line1\r\nLine2"; // Windows换行
    out << "Line3\n";         // Unix/Linux换行

十一、string流

1. 头文件包含

cpp 复制代码
#include <sstream>  // 包含字符串流相关类
#include <string>

2. 三类字符串流

  • istringstream:从字符串读取数据(输入流)

  • ostringstream:向字符串写入数据(输出流)

  • stringstream:同时支持读写操作(输入输出流)


3. istringstream 使用步骤

场景:将字符串解析为其他类型数据

cpp 复制代码
// 创建输入字符串流并绑定字符串
std::istringstream iss("42 3.14 hello");

int num;
double pi;
std::string text;

// 像使用cin一样提取数据
iss >> num >> pi >> text;

// 验证结果
std::cout << num << " " << pi << " " << text;  // 输出:42 3.14 hello

4. ostringstream 使用步骤

场景:将多种类型数据拼接成字符串

cpp 复制代码
std::ostringstream oss;

int age = 25;
double score = 95.5;

// 像使用cout一样插入数据
oss << "Age: " << age << ", Score: " << score;

// 获取最终字符串
std::string result = oss.str();
std::cout << result;  // 输出:Age: 25, Score: 95.5

5. stringstream 综合使用

场景:多次读写字符串内容

cpp 复制代码
std::stringstream ss;

// 写入数据
ss << "PI: " << 3.14159;

// 读取数据
std::string prefix;
double value;
ss >> prefix >> value;

// 清空流(重要!)
ss.str("");  // 清空内容
ss.clear();  // 清除错误状态

// 重新使用
ss << "New data: " << 100;
std::cout << ss.str();  // 输出:New data: 100

6. 关键方法

方法 作用
str() 获取/设置底层字符串
str("content") 重置流内容
clear() 清除错误状态(重要!)

7. 典型应用场景

  1. 数据格式化:将多个变量组合成格式化的字符串

  2. 字符串解析:拆分包含混合数据的字符串(如CSV解析)

  3. 类型转换:实现字符串与其他类型的相互转换

cpp 复制代码
// 字符串转数字
std::istringstream("123") >> number;

// 数字转字符串
std::ostringstream() << 456).str();
安全类型转换
cpp 复制代码
template <typename T>
T string_to(const string& s) {
    istringstream iss(s);
    T value;
    if (!(iss >> value >> ws).eof()) {
        throw invalid_argument("无效转换");
    }
    return value;
}

auto num = string_to<int>("123");  // 成功返回123
auto d = string_to<double>("abc"); // 抛出异常
复杂字符串解析
cpp 复制代码
void parse_csv(const string& line) {
    istringstream iss(line);
    string token;
    
    while (getline(iss, token, ',')) {
        // 处理每个CSV字段
        process_field(token);
    }
}
动态字符串构建
cpp 复制代码
string build_sql(const string& table, int id) {
    ostringstream oss;
    oss << "SELECT * FROM " << quoted(table) 
        << " WHERE id=" << id << ";";
    return oss.str();  // 自动处理SQL注入问题
}
重复使用字符串流
cpp 复制代码
ostringstream oss;
oss << "第一部分";
cout << oss.str();  // 输出"第一部分"

oss.str("");       // 清空内容
oss << "新内容";   // 重新使用
处理带引号的字符串
cpp 复制代码
string input = "\"Hello, World!\" 42";
istringstream iss(input);
string text;
int num;

iss >> quoted(text) >> num;  // text="Hello, World!", num=42
二进制数据转换
cpp 复制代码
vector<uint8_t> bytes = {0x48, 0x65, 0x6c, 0x6c, 0x6f};
ostringstream oss;
oss.write(reinterpret_cast<char*>(bytes.data()), bytes.size());
string binary_str = oss.str();

8. 注意事项

  • 状态管理 :多次使用同一个流时需要调用clear()str("")

  • 错误处理:检查流状态是否有效

cpp 复制代码
if (!(iss >> value)) {
    std::cerr << "格式错误!";
}
  • 性能:频繁创建流对象会影响性能,可复用对象

9.性能优化

1. 预分配缓冲区
cpp 复制代码
ostringstream oss;
oss.rdbuf()->pubsetbuf(nullptr, 1024);  // 预分配1KB缓冲区
2. 减少临时对象
cpp 复制代码
// 低效方式
string s1 = oss1.str() + oss2.str();

// 高效方式
ostringstream final;
final << oss1.rdbuf() << oss2.rdbuf();
string result = final.str();

十二、扩展与自定义

1. 重载操作符实现自定义类型IO
cpp 复制代码
struct Vec3 {
    float x, y, z;
    friend ostream& operator<<(ostream& os, const Vec3& v) {
        return os << "[" << v.x << "," << v.y << "," << v.z << "]";
    }
    friend istream& operator>>(istream& is, Vec3& v) {
        char dummy;
        return is >> dummy >> v.x >> dummy >> v.y >> dummy >> v.z >> dummy;
    }
};

// 使用示例
Vec3 v;
cin >> v;  // 输入格式示例:[1,2,3]
cout << v; // 输出:[1,2,3]
2. 创建自定义流
cpp 复制代码
class TeeStream : public ostream {
    ostream& stream1;
    ostream& stream2;
    TeeStream(ostream& s1, ostream& s2) 
        : ostream(s1.rdbuf()), stream1(s1), stream2(s2) {}
    
    TeeStream& operator<<(ostream& (*manip)(ostream&)) {
        stream1 << manip;
        stream2 << manip;
        return *this;
    }
    // 需要重载其他<<操作符...
};

// 同时输出到控制台和文件
ofstream logfile("log.txt");
TeeStream mycout(cout, logfile);
mycout << "同时输出到两个目标" << endl;

相关推荐
小黄人软件1 小时前
C# ini文件全自动界面配置:打开界面时读ini配置到界面各控件,界面上的控件根据ini文件内容自动生成,点保存时把界面各控件的值写到ini里。
开发语言·c#
二进制人工智能1 小时前
【QT5 网络编程示例】TCP 通信
网络·c++·qt·tcp/ip
Android洋芋3 小时前
C语言深度解析:从零到系统级开发的完整指南
c语言·开发语言·stm32·条件语句·循环语句·结构体与联合体·指针基础
bjxiaxueliang3 小时前
一文详解QT环境搭建:Windows使用CLion配置QT开发环境
开发语言·windows·qt
莫有杯子的龙潭峡谷4 小时前
3.31 代码随想录第三十一天打卡
c++·算法
Run_Teenage4 小时前
C语言 【初始指针】【指针一】
c语言·开发语言
苹果.Python.八宝粥4 小时前
Python第七章02:文件读取的练习
开发语言·python
Python之栈4 小时前
Python 3.13 正式支持 iOS:移动开发的新篇章
python·macos·objective-c·cocoa
AaronZZH4 小时前
【进阶】vscode 中使用 cmake 编译调试 C++ 工程
c++·ide·vscode
J不A秃V头A5 小时前
Redis批量操作详解
开发语言·redis