C++:IO库

news2025/3/25 12:07:05

一、C++ IO库的架构

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

  1. 流对象​(如cincoutfstream
  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>

  • 功能:管理流缓冲区和错误状态
  • 关键成员
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>
  • 功能:处理输入操作
  • 关键成员
    // 基础输入
    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>
  • 功能:处理输出操作
  • 关键成员
    // 基础输出
    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>
  • 功能:同时支持输入输出的流(如标准控制台流)
class iostream : public istream, public ostream {
public:
    // 多继承类,支持双向操作
};

典型应用

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

 

四、<< 、>>运算符

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

基本用途

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

语法
output_stream << data;//data->output_stream

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

示例代码
#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:向标准输出流发送换行符,同时刷新输出缓冲区。
链式调用

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

std::cout << "整数: " << num << ", 浮点数: " << pi << std::endl;

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

基本用途

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

语法
input_stream >> variable;//input_stream->variable

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

示例代码
#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 中。
  • 注意,>> 运算符在读取字符串时会忽略空白字符(如空格、制表符、换行符等),直到遇到下一个空白字符为止。
链式调用

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

std::cin >> num >> pi;

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

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

示例代码
#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(输入文件流)

class ifstream : public istream {
public:
    // 文件操作
    void open(const char* filename, ios_base::openmode mode = ios_base::in);
    void close();
    bool is_open() const;
};
  • 典型用法
    ifstream fin("data.txt");
    if (fin) { // 检查是否成功打开
        int value;
        fin >> value;
    }

2. ofstream(输出文件流)

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

3. fstream(双向文件流)

  • 继承自iostream,支持读写操作:
    fstream file("data.txt", ios::in | ios::out);

4.示例:文件复制

ifstream in("source.txt", ios::binary);
ofstream out("dest.txt", ios::binary);

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

六、字符串流类(<sstream>

1. istringstream(输入字符串流)
class istringstream : public istream {
public:
    string str() const; // 获取底层字符串
    void str(const string& s); // 设置底层字符串
};
2. ostringstream(输出字符串流)
class ostringstream : public ostream {
public:
    string str() const;
};
3. 典型应用:类型转换
#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.自定义缓冲区(高级用法)

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 进行文件读写操作的示例:

#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、基础现象验证

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

#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标准为例)

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

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等)

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

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

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

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

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

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

3. ​避免资源双重释放

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

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

4、替代方案与正确用法

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

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

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

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

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. 共享流缓冲区

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

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. 函数返回值误区
// 错误示例:返回临时流对象
ifstream open_file(const string& name) {
    ifstream f(name);
    return f;  // 尝试拷贝 → 编译错误
}

// 正确做法:返回包装类或使用引用参数
void open_file(const string& name, ifstream& out) {
    out.open(name);
}
2. 容器存储问题
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个核心状态标志(通过位掩码实现):

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

组合状态示例

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

3、状态检测方法

1. 成员函数检测
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> 头文件,该头文件定义了文件流类:
#include <fstream>
#include <string>   // 用于字符串操作
#include <limits>   // 用于numeric_limits
using namespace std;
2. 文件流类
  • ofstream:输出文件流,用于写入文件。
  • ifstream:输入文件流,用于读取文件。
  • fstream:多功能文件流,支持读写操作。

文件流对象生命周期

1. 对象创建与文件绑定

// 方式1:构造时打开文件
ofstream out("data.txt", ios::app);  // 追加模式

// 方式2:默认构造+后续打开
ifstream in;
in.open("config.ini", ios::in | ios::binary);

2. 对象销毁

{
    fstream tmp("temp.data");  // 进入作用域时打开文件
    // 操作文件...
} // 离开作用域时自动调用close()

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

 文件操作

方法

功能描述

open(filename, mode)

打开文件

is_open()

检查文件是否成功打开

close()

关闭文件并刷新缓冲区

步骤示例:​

// 创建输出文件流对象并打开文件
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、读取文件(输入操作)​

步骤示例:​

// 创建输入文件流对象并打开文件
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();

格式化读取示例:​

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、二进制文件操作

写入二进制数据:​

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();

读取二进制数据:​

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、文件指针操作

随机访问文件:​

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、错误处理与状态管理

检测流状态:​

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();

启用异常处理:​

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原则:利用对象生命周期自动管理资源。

    { // 自动关闭文件
        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. 头文件包含

#include <sstream>  // 包含字符串流相关类
#include <string>

2. 三类字符串流

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

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

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


3. istringstream 使用步骤

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

// 创建输入字符串流并绑定字符串
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 使用步骤

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

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 综合使用

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

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. 类型转换:实现字符串与其他类型的相互转换

// 字符串转数字
std::istringstream("123") >> number;

// 数字转字符串
std::ostringstream() << 456).str();
安全类型转换
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"); // 抛出异常
 复杂字符串解析
void parse_csv(const string& line) {
    istringstream iss(line);
    string token;
    
    while (getline(iss, token, ',')) {
        // 处理每个CSV字段
        process_field(token);
    }
}
动态字符串构建
string build_sql(const string& table, int id) {
    ostringstream oss;
    oss << "SELECT * FROM " << quoted(table) 
        << " WHERE id=" << id << ";";
    return oss.str();  // 自动处理SQL注入问题
}
 重复使用字符串流
ostringstream oss;
oss << "第一部分";
cout << oss.str();  // 输出"第一部分"

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

iss >> quoted(text) >> num;  // text="Hello, World!", num=42
 二进制数据转换
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("")

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

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

9.性能优化

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

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

 十二、扩展与自定义

1. 重载操作符实现自定义类型IO
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. 创建自定义流
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;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2320533.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

企业级前端架构设计与实战

一、架构设计核心原则 1.1 模块化分层架构 典型目录结构&#xff1a; src/├── assets/ # 静态资源├── components/ # 通用组件├── pages/ # 页面模块├── services/ # API服务层├── store/ # 全局状态管理├── uti…

从入门到精通【MySQL】 CRUD

文章目录 &#x1f4d5;1. Create 新增✏️1.1 单行数据全列插入✏️1.2 单行数据指定列插入✏️1.3 多行数据指定列插入 &#x1f4d5;2. Retrieve 检索✏️2.1 全列查询✏️2.2 指定列查询✏️2.3 查询字段为表达式✏️2.4 为查询结果指定别名✏️2.5 结果去重查询 &#x1f…

08_双向循环神经网络

双向网络 概念 双向循环神经网络&#xff08;Bidirectional Recurrent Neural Network, BiRNN&#xff09;通过同时捕捉序列的正向和反向依赖关系&#xff0c;增强模型对上下文的理解能力。与传统的单向网络不同,BIRNN 能够同时从过去和未来的上下文信息中学习,从而提升模型的…

2025年Postman的五大替代工具

虽然Postman是一个广泛使用的API测试工具&#xff0c;但许多用户在使用过程中会遇到各种限制和不便。因此&#xff0c;可能需要探索替代解决方案。本文介绍了10款强大的替代工具&#xff0c;它们能够有效替代Postman&#xff0c;成为你API测试工具箱的一部分。 什么是Postman&…

(四)---四元数的基础知识-(定义)-(乘法)-(逆)-(退化到二维复平面)-(四元数乘法的导数)

使用四元数的原因 最重要的原因是因为传感器的角速度计得到的是三个轴的角速度, 这三个轴的角速度合成一个角速度矢量, 结果就是在微小时间内绕着这个角速度矢量方向为轴旋转一定角度. 截图来源网址四元数 | Crazepony开源四轴飞行器

汇能感知高品质的多光谱相机VSC02UA

VSC02UA概要 VSC02UA是一款高品质的200万像素的光谱相机&#xff0c;适用于工业检测、农业、医疗等领域。VSC02UA 包含 1600 行1200 列有源像素阵列、片上 10 位 ADC 和图像信号处理器。它带有 USB2.0 接口&#xff0c;配合专门的电脑上位机软件使用&#xff0c;可进行图像采集…

Blazor+PWA技术打造全平台音乐播放器-从音频缓存到离线播放的实践之路

开局三张图… 0.起源 主要是自己现在用的是苹果手机&#xff0c;虽然手机很高级&#xff0c;但是想听自己喜欢的歌曲确是不容易&#xff0c;在线app都要付费&#xff0c;免费的本地播放器都不太好用&#xff08;收费的也不太行&#xff09;&#xff0c;基础功能都不满足。此外…

使用LangChain开发智能问答系统

代码地址见文末 1. 项目配置 1.1 Neo4j 数据库配置 1. 安装与环境变量 解压路径:将neo4j-community-5.x.x.zip解压至D:\neo4j-community-5.x.x环境变量: NEO4J_HOME: D:\neo4j-community-5.x.xJAVA_HOME: D:\neo4j-community-5.x.x\jdk(注意:需指向 JDK 目录)Path 变量…

Centos操作系统安装及优化

Centos操作系统安装及优化 零、环境概述 主机名 centos版本 cpu 内存 Vmware版本 ip地址 test CentOS Linux release 7.6.1810 (Core) 2C 2G 15.5.1 10.0.0.10 一、介质下载 1、7.6版本下载 CentOS7.6标准版下载链接: https://archive.kernel.org/centos-vault/7.6.1810/i…

游戏引擎学习第177天

仓库:https://gitee.com/mrxiao_com/2d_game_4 今日计划 调试代码有时可能会非常困难&#xff0c;尤其是在面对那些难以发现的 bug 时。显然&#xff0c;调试工具是其中一个非常重要的工具&#xff0c;但在游戏开发中&#xff0c;另一个非常常见的工具就是自定义的调试工具&a…

数据结构知识点1

目录 一、时间复杂度和空间复杂度 1.1时间复杂度&#xff1a; 1.2空间复杂度&#xff1a; 二、装箱和拆箱 三、泛型 3.1泛型类的使用&#xff1a; 3.2泛型的上界&#xff1a; 3.3泛型方法&#xff1a; 一、时间复杂度和空间复杂度 1.1时间复杂度&#xff1a; 时间复杂…

自由学习记录(45)

顶点片元着色器&#xff08;important&#xff09; 1.需要在Pass渲染通道中编写着色器逻辑 2.可以使用cG或HLSL两种shader语言去编写Shader逻辑 3.代码量较多&#xff0c;灵活性较强&#xff0c;性能消耗更可控&#xff0c;可以实现更多渲染细节 4.适用于光照处理较少&#xf…

数据源支持远程Excel/CSV,数据集支持分组字段功能,DataEase开源BI工具v2.10.6 LTS版本发布

2025年3月17日&#xff0c;人人可用的开源BI工具DataEase正式发布v2.10.6 LTS版本。 这一版本的功能变动包括&#xff1a;数据源方面&#xff0c;新增支持远程Excel/CSV数据源&#xff0c;支持以HTTP、HTTPS、FTP协议获取远程服务器上的Excel和CSV数据文件&#xff0c;并且可以…

SpringBoot3使用CompletableFuture时java.util.ConcurrentModificationException异常解决方案

问题描述 在Spring Boot 3项目中&#xff0c;使用CompletableFuture进行异步编程时&#xff0c;偶发{"code":500,"msg":"java.util.ConcurrentModificationException"}异常&#xff0c;但代码中并未直接操作List或CopyOnWriteArrayList等集合类…

STM32__红外避障模块的使用

目录 一、红外避障模块 概述 二、直接读取OUT引脚电平 三、使用中断方式触发 一、红外避障模块 概述 引脚解释&#xff1a; VCC接3.3V 或 5.0VGND接开发板的GNDOUT数字量输出(0或1&#xff09;; 低电平时表示前方有障碍 ; 通过可调电阻调整检测距离 产品特点&#xff1a; …

Redis JSON 用id读取content总结(sendCommand())

Redis JSON 读取总结&#xff08;方法 2 - sendCommand()&#xff09; &#x1f4a1; 背景 在 Redis 中&#xff0c;我们存储了 JSON 数据&#xff0c;并希望通过 Jedis sendCommand() 方式读取 JSON 里的 "content" 字段。由于 jedis.jsonGet() 可能在旧版本不支持…

基于3DMax与Vray引擎的轻量级室内场景渲染实践

欢迎踏入3DMAX室内渲染的沉浸式学习之旅!在这个精心设计的实战教程中,我们将携手揭开3DMAX与Vray这对黄金搭档在打造现实室内场景时的核心奥秘。无论您是渴望入门的3D新手,还是追求极致效果的专业设计师,这里都将为您呈现从场景蓝图构建到光影魔法施加的完整技术图谱。我们…

QT Quick(C++)跨平台应用程序项目实战教程 2 — 环境搭建和项目创建

目录 引言 1. 安装Qt开发环境 1.1 下载Qt安装包 1.2 安装Qt 1.3 安装MSVC编译器 2. 创建Qt Quick项目 2.1 创建新项目 2.2 项目结构 2.3 运行项目 3. 理解项目代码 3.1 main.cpp文件 3.2 Main.qml文件 引言 在上一篇文章中&#xff0c;我们介绍了本教程的目标和结…

登山第二十梯:无人机实时自主探索——我是一只小小小鸟

文章目录 一 摘要 二 资源 三 内容 一 摘要 自主探索是无人机 &#xff08;UAV&#xff09; 各种应用的基本问题。最近&#xff0c;基于 LiDAR 的探索因其能够生成大规模环境的高精度点云地图而受到广泛关注。虽然点云本身就为导航提供了信息&#xff0c;但许多现有的勘探方…

word插入Mathtype公式居中和自动更新

word插入公式自动更新 前提&#xff1a;安装Mathtype 1.word中查看页的宽度 出现如下 2.设置样式 出现这个窗口 给样式随便起个名字 3.修改样式 3.1 设置两个制表位 第二个 3.2 修改公式字体 如下所示 4. 修改公式格式 4.1在word中打开 Mathtype 4.2 修改公式的格式 变成…