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文档

cpp 复制代码
#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

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

cpp 复制代码
#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;
}
相关推荐
Mr.kanglong2 小时前
【C++】C++11
开发语言·c++
小陈又菜3 小时前
通讯录管理小程序
c++
羚羊角uou3 小时前
【C++】多态详细讲解
开发语言·c++
利刃大大4 小时前
【C++】string类的模拟实现
开发语言·c++
一只小松许️4 小时前
C++ CRTP:奇异递归模板模式的原理与应用
开发语言·c++
源代码•宸6 小时前
Leetcode—734. 句子相似性【简单】Plus
c++·经验分享·算法·leetcode·哈希算法
No0d1es6 小时前
202412 青少年软件编程等级考试C/C++ 二级真题答案及解析
c语言·开发语言·c++·算法·青少年编程·电子学会·二级
CodeClimb6 小时前
【华为OD-E卷 - 113 跳格子2 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
源代码•宸7 小时前
Leetcode—487. 最大连续1的个数 II【中等】Plus
c++·经验分享·算法·leetcode·滑动窗口