Bootstrap

C++的 I/O 流

本文把复杂的基类和派生类的作用和关系捋出来,具体的接口请参考相关文档

C++的 I/O 流相关的类,继承关系如下图所示

https://zh.cppreference.com/w/cpp/io

I / O 的概念:内存和外设进行数据交互称为 I / O ,例如:把数据写入磁盘,把数据显示到屏幕,把键盘的数据传到内存等等。

流的概念:可以理解为河水,有源头,有目的,按字节流动。按流传输时,不关心内容,格式,类型等等。

我们重点要掌握的是:输入输出流定义的全局对象 cout cin 等, 还有文件输入输出流,熟悉文件输入输出流相关接口。

成员变量

https://zh.cppreference.com/w/cpp/io/ios_base

我们要熟悉一下 ios_base 中维护的一些成员变量,首先就是流的打开方式

常量

解释

std::ios::app

每次写入前寻位到流结尾

std::ios::binary

二进制模式打开

std::ios::in

为读打开

std::ios::out

为写打开

std::ios::trunc

在打开时舍弃流的内容

std::ios::ate

打开后立即寻位到流结尾

std::ios::noreplace

以独占模式打开

这里是不是有些奇怪,怎么打开流像打开文件一样?

以 Linux 系统为例,文件管理模块会把硬件设备全部抽象成文件:显示器,键盘,网卡等属于字符设备文件,存磁盘,u盘等属于块设备文件。经过虚拟文件系统对具体文件系统抽象后,上层会以统一的视角看待底层设备。

所以,这里的一层理解是:所谓的基于流的 I/O 可以理解为进程对某个文件的输入输出。比如,C++中最常用的 cin, cout, 就是对进程的0号文件描述符和1号文件描述符进行操作

文件有自己的打开方式,对应的,流也就有打开方式

视角再拉回来,ios_base 中的成员变量还有寻位相关的

常量

解释

std::ios::beg

流的开始

std::ios::end

流的结尾

std::ios::cur

流位置指示器的当前位置

还有流的状态

常量

解释

goodbit

无错误

badbit

不可恢复的流错误

failbit

输入/输出操作失败(格式化或提取错误)

eofbit

关联的输出序列已抵达文件尾


输入输出操作

std::basic_streambuf 是输入输出操作的缓冲区

https://zh.cppreference.com/w/cpp/io/basic_streambuf

std::basic_streambuf 关联的缓冲区有两类:

1. 通过操作系统的API访问的实体(文件、TCP 套接字、串行端口、其他字符设备)

2.能解读成缓冲区的对象(std::vector, std::string等)

对于输入操作来说,该缓冲区称为获取区(进程从获取区拿数据)

对于输出操作来说,该缓冲区称为放置区(进程把数据放到放置区)

std::basic_ostream 提供输出操作

https://zh.cppreference.com/w/cpp/io/basic_ostream

标准库提供六个全局 basic_ostream 对象:

在标头 <iostream> 定义

cout | wcout

写入到标准 C 输出流 stdout (全局对象)

cerr | wcerr

写入到标准 C 错误流 stderr,无缓冲 (全局对象)

clog | wclog

写入到标准 C 错误流 stderr (全局对象)

std::basic_istream 提供输入操作

https://zh.cppreference.com/w/cpp/io/basic_istream

标准库提供两个全局 basic_istream 对象:

在标头 <iostream> 定义

cin | wcin

从标准 C 输入流 stdin 读取 (全局对象)

std::basic_iostream 继承了std::basic_ostream 和 std::basic_istream ,提供输入输出操作。
https://zh.cppreference.com/w/cpp/io/basic_iostream

为了理解 std::basic_streambuf std::basic_ostream std::basic_istream 我们看如下程序,下面程序中 MyStreamBuf 是自定义的缓冲区, 封装 std::string 对象。MyStreamBuf 继承了 std::streambuf 可以重写 std::streambuf 的虚函数,std::ostream 接受一个缓冲区 MyStreamBuf 作为参数实例化对象。

注:std::ostream , std::streambuf 是 std::basic_ostream , std::basic_streambuf 的别名

下面封装缓冲区是 重写 overflow 和 xsputn 两个虚函数,可以参考std::streambuf文档

#include <iostream>
#include <streambuf>
#include <string>

// 自定义流缓冲区:将输出内容存储到字符串
class MyStreamBuf : public std::streambuf {
protected:
    std::string buffer_;

    // 处理单个字符输出
    virtual int_type overflow(int_type c) override {
        if (c != traits_type::eof()) {
            buffer_ += traits_type::to_char_type(c);
        }
        return c;
    }

    // 处理多字符输出
    virtual std::streamsize xsputn(const char* s, std::streamsize n) override {
        buffer_.append(s, n);
        return n;
    }

public:
    const std::string& getBuffer() const { return buffer_; }
};

int main() {
    MyStreamBuf buf; // 实例化自定义流缓冲区
    std::ostream myStream(&buf); // 构造ostream,使用自定义缓冲区

    myStream << "Hello World! " << 42 << std::endl; // 输出到流

    // 获取并打印缓冲区内容
    std::cout << "输出流里的内容是: " << buf.getBuffer();

    return 0;
}

文件的输入输出流

下面我们介绍 std::basic_fstream 。 std::basic_fstream 继承关系如下所示

基类我们前文已经介绍了,ios_base 维护必要的变量, basic_istream 提供输入操作, basic_ostream 提供输出操作。

std::basic_fstream 作为派生类,添加了一些文件相关的接口,可以参考文档

https://zh.cppreference.com/w/cpp/io/basic_fstream

下面写一个小程序,可以拷贝图片或视频

#include <iostream>
#include <fstream>
using namespace std;
int main() {
    // 打开源文件(二进制模式)
    fstream inFile("C:\\Users\\34497\\Desktop\\屏幕录制 2025-01-15 213832.mp4", ios::in | ios::binary);  
    if (!inFile) {
        cerr << "无法打开源文件" << endl;
        return 1;
    }

    // 创建目标文件(二进制模式并清空内容)
    fstream outFile("C:\\Users\\34497\\Desktop\\destination.mp4", ios::out | ios::binary | ios::trunc);        
    if (!outFile) {
        cerr << "无法创建目标文件" << endl;
        inFile.close();
        return 1;
    }

    // 使用缓冲区提高读写效率
    const int BUFFER_SIZE = 4096;
    char buffer[BUFFER_SIZE];

    // 循环读写文件内容
    while (inFile.read(buffer, BUFFER_SIZE)) {
        outFile.write(buffer, inFile.gcount());
    }

    // 处理最后一次读取的数据
    if (inFile.eof()) {
        // 写入剩余的有效数据
        outFile.write(buffer, inFile.gcount());
    }
    else {
        // 非EOF错误处理
        cerr << "文件读取过程中发生错误" << endl;
        inFile.close();
        outFile.close();
        return 1;
    }

    // 关闭文件流
    inFile.close();
    outFile.close();

    cout << "拷贝完成!" << endl;   
    return 0;
}

;