C++ 流

在 C++ 标准库中,stream(流)是处理输入输出(I/O)的核心抽象,分为输入流(istream)、输出流(ostream)、输入输出流(iostream)等多种类型,且支持面向不同设备(文件、字符串、控制台等)的具体流类型。


Stream 类型

类别 主要类型及说明 头文件
输入流 std::istream:通用输入流基类 <istream>
std::ifstream:文件输入流 <fstream>
std::istringstream:字符串输入流 <sstream>
输出流 std::ostream:通用输出流基类 <ostream>
std::ofstream:文件输出流 <fstream>
std::ostringstream:字符串输出流 <sstream>
输入输出流 std::iostream:通用输入输出流基类 <iostream>
std::fstream:文件输入输出流 <fstream>
std::stringstream:字符串输入输出流 <sstream>
标准流对象 std::cin:标准输入流(全局对象) <iostream>
std::cout:标准输出流(全局对象) <iostream>
std::cerr:标准错误输出流(无缓冲) <iostream>
std::clog:标准错误输出流(带缓冲) <iostream>

继承关系简图(简化版)

std::ios_base
    └── std::ios
           ├── std::istream
           │      ├── std::ifstream
           │      └── std::istringstream
           ├── std::ostream
           │      ├── std::ofstream
           │      └── std::ostringstream
           └── std::iostream
                  ├── std::fstream
                  └── std::stringstream


一、标准流

C++ 标准库定义了四个主要的标准流对象:

对象名 类型 功能描述 缓冲机制 用途
std::cin std::istream 标准输入流,通常关联键盘输入 带缓冲 从键盘或重定向输入读取数据
std::cout std::ostream 标准输出流,通常关联控制台显示 带缓冲 向控制台或重定向输出写数据
std::cerr std::ostream 标准错误输出流,关联控制台显示 无缓冲 用于错误或即时输出提示
std::clog std::ostream 标准日志输出流,关联控制台显示 带缓冲 用于程序日志信息输出

1. std::cin - 标准输入流

  • 类型:std::istream
  • 默认关联设备:键盘(终端输入)
  • 用法:
int x;
std::cin >> x;  // 从标准输入读取一个整数
  • 缓冲:带缓冲。输入时会先缓冲一部分数据,通常是整行读取后交给程序处理。
  • 支持格式化输入:operator>> 会自动跳过空白、换行,进行类型转换。
  • 支持重定向:例如从文件读取时,命令行重定向输入 program < input.txt

2. std::cout - 标准输出流

  • 类型:std::ostream
  • 默认关联设备:控制台(终端显示)
  • 用法:
std::cout << "Hello, world!" << std::endl;
  • 缓冲:带缓冲。缓冲区满或者遇到 std::endl 会刷新缓冲区写到终端。
  • 支持各种类型的格式化输出(整型、浮点、字符串、自定义类型重载 operator<<)。
  • 支持重定向:例如把输出写入文件 program > output.txt

3. std::cerr - 标准错误输出流

  • 类型:std::ostream
  • 默认关联设备:控制台(终端显示)
  • 无缓冲输出:写入时立即发送到终端,适合打印错误或警告信息,确保及时可见。
  • 用法:
std::cerr << "Error: File not found!" << std::endl;
  • 作用是和 cout 分开输出,方便终端或脚本捕获错误信息。
  • 不受缓冲影响,即使程序异常退出也更容易保证错误信息打印出来。

4. std::clog - 标准日志输出流

  • 类型:std::ostream
  • 默认关联设备:控制台(终端显示)
  • 带缓冲输出,适合大量日志信息,效率高。
  • 用法:
std::clog << "Log: Starting the program..." << std::endl;
  • 用于日志信息输出,不同于 cerr 主要是“错误”,clog 主要是“日志”。
  • cerr 区别在于缓冲方式和输出语义。

标准流对象常用操作和特性

操作 示例 说明
读取输入 std::cin >> x; 从键盘读取格式化输入
输出文本 std::cout << "Hello\n"; 向终端打印文本
输出错误信息 std::cerr << "Error!\n"; 立即打印错误信息
输出日志 std::clog << "Log message\n"; 缓冲写入日志信息
刷新缓冲区 std::cout.flush();std::cout << std::flush; 立即将缓冲内容写入设备
操纵符换行+刷新 std::cout << std::endl; 输出换行并刷新缓冲
判断流状态 if (!std::cin) { /* 读取失败 */ } 判断流是否处于错误状态
重定向流 命令行重定向 program < input.txt > output.txt 改变流的输入输出设备

标准流对象的缓冲策略

流对象 缓冲类型 说明
std::cin 带缓冲 读取时先缓冲,优化输入效率
std::cout 带缓冲 输出时缓存,遇换行或缓冲满时刷新
std::cerr 无缓冲 错误信息立即输出,不等待缓冲刷新
std::clog 带缓冲 日志输出缓冲,提高效率

二、输入输出流

在C++中,输入输出流(Input/Output Stream)是对数据流动方向的抽象,用于从某个设备(如键盘、文件、内存)读取数据或向其写入数据。输入流负责读取,输出流负责写入。输入输出流即同时支持输入和输出的流,能既从中读取数据,也向其中写入数据。

基本流层级和具体实现类

std::iostream

  • 继承自 std::istreamstd::ostream。支持标准输入输出流的所有操作。既可以用作输入流,也可以用作输出流。
  • 底层依赖于缓冲区(buffer)管理,避免频繁系统调用,提升效率。iostream 对象拥有一个streambuf缓冲区指针,通过它实现具体读写。iostream 继承了格式化输入输出的能力,比如支持operator>>operator<<对各种类型的重载。
类名 功能描述 头文件
std::ios_base I/O 库的基础类,管理格式化和状态 <ios>
std::ios 继承自 ios_base,增加缓冲等支持 <ios>
std::istream 输入流基类 <istream>
std::ostream 输出流基类 <ostream>
std::iostream 输入输出流基类(继承自 istreamostream <iostream>

iostream继承了输入流和输出流的功能,因此可同时读写。


具体实现类:

类名 用途 头文件
std::fstream 文件输入输出流 <fstream>
std::stringstream 内存字符串输入输出流 <sstream>

1. 文件读写(使用 std::fstream

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

int main() {
    std::fstream file("example.txt", std::ios::in | std::ios::out | std::ios::trunc);
    if (!file) {
        std::cerr << "文件打开失败\n";
        return 1;
    }

    // 写入数据
    file << "Hello, iostream!\n";

    // 重置文件读取位置到开头
    file.seekg(0);

    // 读取数据
    std::string line;
    std::getline(file, line);
    std::cout << "文件内容: " << line << std::endl;

    file.close();
    return 0;
}

2. 内存字符串读写(使用 std::stringstream

#include <sstream>
#include <iostream>

int main() {
    std::stringstream ss;

    // 写入
    ss << "123 456";

    int a, b;
    // 读取
    ss >> a >> b;

    std::cout << "读取到的数字:" << a << ", " << b << std::endl;
}
//输出: 读取到的数字:123, 456

常用成员函数(部分)

函数 说明
operator>> 格式化输入(读取)
operator<< 格式化输出(写入)
read(char*, size_t) 读取原始字节流
write(char*, size_t) 写入原始字节流
get() 读取一个字符
put(char) 写入一个字符
seekg(pos) 设置输入位置
seekp(pos) 设置输出位置
tellg() 获取当前输入位置
tellp() 获取当前输出位置
good(), fail(), eof() 检查流状态
flush() 刷新缓冲区

流状态及错误处理

在 C++ 中,输入输出流(iostream)提供了状态标志和错误处理机制,用于检测和处理 I/O 操作中的异常或错误情况。理解这些机制对于编写健壮的 I/O 代码非常重要。


流的状态标志(Stream State Flags)

C++ 的流对象(如 std::ifstream, std::ofstream, std::cin, std::cout 等)都继承自 std::ios_base 类,并维护一组状态标志来表示流当前的状态。这些状态标志是通过成员函数 rdstate() 获取的,其类型为 std::ios_base::iostate

常见的状态标志包括:

标志 含义
goodbit 没有发生错误(一切正常)
badbit 发生了不可恢复的读写错误(例如底层设备出错)
failbit 输入/输出操作失败(但流仍可用,例如类型不匹配)
eofbit 到达文件末尾(EOF)

示例:

#include <iostream>
#include <fstream>

int main() {
    std::ifstream file("example.txt");
    if (!file) {
        std::cerr << "Failed to open file.\n";
    }

    int value;
    file >> value;

    if (file.fail()) {
        std::cerr << "Input failed: type mismatch or bad input.\n";
    }
    if (file.bad()) {
        std::cerr << "Serious I/O error occurred.\n";
    }
    if (file.eof()) {
        std::cerr << "End of file reached.\n";
    }
    if (file.good()) {
        std::cout << "Everything is fine.\n";
    }

    return 0;
}

状态检查函数

除了直接使用 rdstate() 来获取状态位外,还可以使用以下成员函数进行状态判断:

函数名 返回值说明
good() 是否处于良好状态(没有设置任何错误标志)
bad() 是否发生了不可恢复的错误
fail() 是否发生了可恢复的错误(包括 eofbit
eof() 是否到达文件末尾
operator!() 是否处于失败状态(即 fail() 为真)
operator bool() 是否处于非失败状态(即 !fail() 为真)

清除状态标志

如果你希望继续使用一个已经出错的流对象,可以使用 clear()clear(iostate) 函数来清除状态标志。

示例:

#include <iostream>

int main() {
    int num;
    std::cin >> num;

    if (std::cin.fail()) {
        std::cin.clear(); // 清除 failbit
        std::cin.ignore(10000, '\n'); // 忽略缓冲区中的非法输入
        std::cout << "Invalid input. Please enter an integer: ";
        std::cin >> num;
    }

    std::cout << "You entered: " << num << std::endl;
    return 0;
}

错误处理与异常机制(Exception Handling)

默认情况下,C++ 流不会抛出异常,而是通过状态标志来报告错误。但你可以通过 exceptions() 函数启用异常处理。

示例:启用异常

#include <iostream>
#include <fstream>

int main() {
    std::ifstream file("nonexistent.txt");

    // 启用异常:当流状态变为 badbit 或 failbit 时抛出异常
    file.exceptions(std::ifstream::failbit | std::ifstream::badbit);

    try {
        int value;
        file >> value; // 如果文件不存在或无法读取,将抛出异常
    } catch (const std::ios_base::failure& e) {
        std::cerr << "Caught an exception: " << e.what() << '\n';
    }

    return 0;
}

⚠️ 注意:启用异常后,某些操作(如构造失败)可能不会触发异常。建议结合 if (!file) 进行检查。

三、输入流

在 C++ 中,输入流(Input Stream) 是一种用于从数据源读取数据的机制。C++ 的标准库提供了强大的 I/O 流类库,其中与输入相关的类主要包括:

  • std::istream:基本输入流类。
  • std::ifstream:用于从文件中读取数据。
  • std::istringstream:用于从字符串中读取数据。
  • std::cin:标准输入流对象(通常来自键盘)。

所有输入流都继承自 std::istream 类。它的核心功能是提供读取操作符 >> 和成员函数如 get(), getline(), read() 等。

常用输入流对象:

对象/类 描述
std::cin 标准输入流,默认从控制台读取
std::ifstream 文件输入流
std::istringstream 字符串输入流

常用输入方法

1. 使用 operator >>(提取运算符)

这是最常用的输入方式,用于按类型读取数据。

#include <iostream>

int main() {
    int age;
    std::cout << "Enter your age: ";
    std::cin >> age;
    std::cout << "You are " << age << " years old.\n";
    return 0;
}

注意:

  • 它会自动跳过前导空白字符(空格、换行、制表符等)。
  • 如果输入类型不匹配(比如输入字母而非数字),将设置 failbit 标志。

2. 使用 get() 函数

用于逐个或批量读取字符(包括空白字符)。

单字符读取:

#include <iostream>

int main() {
    char ch;
    while (std::cin.get(ch)) {
        std::cout << ch;
    }
    return 0;
}

多字符读取(带缓冲区):

#include <iostream>

int main() {
    char buffer[100];
    std::cin.get(buffer, 100);
    std::cout << "You entered: " << buffer << std::endl;
    return 0;
}

get() 不会跳过空白字符,并且不会自动添加 \0 结尾。


3. 使用 getline() 函数

用于读取整行输入,直到遇到换行符。

#include <iostream>

int main() {
    char buffer[100];
    std::cin.get(buffer, 100);
    std::cout << "You entered: " << buffer << std::endl;
    return 0;
}

或者使用 std::string 版本:

#include <iostream>
#include <string>

int main() {
    std::string line;
    std::getline(std::cin, line);
    std::cout << "You entered: " << line << std::endl;
    return 0;
}

getline() 更适合读取包含空格的完整句子。


4. 使用 read() 函数(二进制模式)

用于一次性读取指定数量的字节,常用于二进制文件处理。

char buffer[100];
std::cin.read(buffer, sizeof(buffer));
std::streamsize count = std::cin.gcount();
std::cout.write(buffer, count); // 输出已读取的内容

文件输入流ifstream

std::ifstreaminput file stream)是文件输入流,用于从文件中读取数据,就像 std::cin 用于从标准输入读取一样。

它是 C++ 的 IO 库中的一部分,定义在头文件 <fstream> 中。

#include <fstream>

继承关系如下:

std::ios_base
    ↑
std::ios
    ↑
std::istream
    ↑
std::ifstream

常用构造函数

构造函数形式 说明
ifstream() 创建未打开任何文件的流对象
ifstream(const char* filename) 打开一个文件用于读取,使用默认模式(文本模式)
ifstream(const char* filename, ios::openmode mode) 指定模式打开文件
ifstream(const std::string& filename, ios::openmode mode) 使用 std::string 作为文件名

常见打开模式(std::ios::openmode

你可以传给 ifstream 构造函数或者 open() 方法的模式:

模式 含义
ios::in 以读模式打开(默认)
ios::binary 以二进制模式打开
ios::ate 打开文件并定位到文件尾
ios::app 追加模式(ifstream 很少用)
ios::trunc 清空文件(仅用于 ofstream)

⚠️ 注意:ifstream 默认自带 ios::in,所以你不用显式加 unless 和其他模式组合。

常见用法示例

🔸 1. 基本读取一整行

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

int main() {
    std::ifstream infile("example.txt");

    if (!infile.is_open()) {
        std::cerr << "Failed to open file.\n";
        return 1;
    }

    std::string line;
    while (std::getline(infile, line)) {
        std::cout << "Line: " << line << '\n';
    }

    infile.close();
    return 0;
}

🔸 2. 按单词读取

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

int main() {
    std::ifstream infile("example.txt");

    if (!infile.is_open()) {
        std::cerr << "Failed to open file.\n";
    }

    std::string word;
    while (infile >> word) {
        std::cout << "Word: " << word << '\n';
    }

    infile.close();
    return 0;
}

🔸 3. 按数字读取

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

int main() {
    try {
        std::ifstream infile("/home/fxq/untitled3/example.txt");

        if (!infile.is_open()) {
            std::cerr << "Failed to open file.\n";
        }

        int num;
        while (infile >> num) {
            std::cout << "Read number: " << num << '\n';
        }

        infile.close();
    } catch (const std::runtime_error& e) {
         std::cerr << "发生异常: " << e.what() <<  std::endl;
    }
    return 0;
}

🔸 4. 用 .open() 延迟打开

std::ifstream infile;
infile.open("data.txt");

if (!infile) {
    std::cerr << "Failed to open.\n";
}

缓冲区

C++ 的 std::ifstream 默认是带缓冲的,其底层使用了一个缓冲区(buffer)*来优化文件读取效率。但你可以*自定义缓冲区大小和内存,甚至设置成无缓冲。下面是详细讲解。


默认情况下:ifstream 自带缓冲
  • ifstream 会使用系统默认的缓冲区(通常是 4KB 或 8KB)。
  • 当你调用 read()getline()operator>> 等函数时,数据先进入缓冲区,再从中解析或返回给用户程序
  • 好处是:减少系统调用次数,提高读取性能。

自定义缓冲区:rdbuf()->pubsetbuf()

你可以设置自己的缓冲区或取消缓冲区:

std::ifstream file("example.txt");
char buffer[4096];  // 自定义缓冲区

file.rdbuf()->pubsetbuf(buffer, sizeof(buffer));
  • pubsetbuf(buffer, size):设置缓冲区起始地址和大小
  • 只能在 open() 之前调用(对已打开文件无效)

设置无缓冲模式(强烈不推荐除非特殊需求
file.rdbuf()->pubsetbuf(nullptr, 0); // 禁用缓冲

⚠️ 这会让每一次 read()getline() 都变成系统调用,性能会大幅下降,只在需要极低延迟的数据交互中才使用(如串口通信等)。


完整示例:自定义缓冲区读取大文件
#include <iostream>
#include <fstream>

int main() {
    // 有问题试一下全路径
    std::ifstream file("example.txt");

    // 自定义缓冲区
    char custom_buffer[8192];  // 8KB
    file.rdbuf()->pubsetbuf(custom_buffer, sizeof(custom_buffer));

    if (!file) {
        std::cerr << "打开文件失败\n";
        return 1;
    }

    std::string line;
    size_t count = 0;
    while (std::getline(file, line)) {
        std::cout << line << std::endl;
        ++count;
    }

    std::cout << "共读取行数:" << count << "\n";
    file.close();
    return 0;
}

推荐场景
场景 缓冲设置建议
大文件(几百 MB 以上) 自定义大缓冲区(如 64KB)
小文件 / 一次性读取 默认缓冲即可
实时读取(如串口) pubsetbuf(nullptr, 0)
多线程读取(少见) 每线程独立缓冲区

字符串输入流(istringstream)

用于从字符串中解析数据,常用于字符串拆分和格式转换。

#include <sstream>
#include <iostream>

int main() {
    std::string data = "John 25 85.5";
    std::istringstream iss(data);

    std::string name;
    int age;
    double score;

    iss >> name >> age >> score;
    std::cout << name << ", " << age << ", " << score << std::endl;

    return 0;
}

总结

方法 用途
>> 提取数据,自动跳过空白,类型安全
get() 读取字符(含空白)
getline() 读取一行(推荐用于整行输入)
read() 读取二进制数据
gcount() 获取最后一次读取的字符数
clear() 清除错误状态
ignore() 忽略缓冲区中的无效字符

四、输出流

输出流是 C++ I/O 库中的一个核心概念,用于向外部目标输出数据,目标可以是:

  • 控制台(标准输出 std::cout
  • 文件(std::ofstream
  • 字符串(std::ostringstream
  • 自定义设备(如网络、GUI 等)
std::ios_base
  └── std::ios
        └── std::ostream      // 输出流基类
              ├── std::ofstream       // 文件输出流
              ├── std::ostringstream  // 字符串输出流
              └── std::iostream       // 同时支持输入输出

常见输出流类型

类型 功能 头文件
std::ostream 所有输出流的基类 <ostream>
std::cout 标准输出流(控制台) <iostream>
std::cerr 错误输出流(无缓冲) <iostream>
std::clog 日志输出流(有缓冲) <iostream>
std::ofstream 文件输出流 <fstream>
std::ostringstream 字符串输出流 <sstream>

常见输出函数

函数名 功能说明 示例
operator<< 格式化输出 os << x
put(char) 输出一个字符 os.put('A')
write(char*, size_t) 输出字节流(通常用于二进制) os.write(buf, n)
flush() 刷新缓冲区,强制输出 os.flush()

格式化控制(使用 std::iomanip

#include <iostream>
#include <iomanip>
int main() {
    double pi = 3.14159265;
    std::cout << std::fixed << std::setprecision(2) << pi << std::endl; // 输出 3.14
}
控制符 功能
std::setw(n) 设置字段宽度
std::setfill(c) 设置填充字符
std::setprecision(n) 设置浮点数精度
std::fixed 固定小数位格式
std::scientific 科学计数法格式
std::hex/std::dec/std::oct 设置整数进制
std::left / std::right / std::internal 对齐方式

文件输出流(std::ofstream)示例

#include <fstream>
int main() {
    std::ofstream ofs("output.txt");
    if (!ofs) {
        std::cerr << "无法打开文件!" << std::endl;
        return 1;
    }
    ofs << "写入文件的第一行" << std::endl;
    ofs.close();
}

字符串输出流(std::ostringstream

#include <sstream>
#include <iostream>
int main() {
    std::ostringstream oss;
    oss << "Name: " << "Alice" << ", Age: " << 30;

    std::string result = oss.str(); // 获取写入的字符串
    std::cout << "字符串流结果: " << result << std::endl;
}

输出流的缓冲刷新机制

  • 会自动刷新缓冲区的情况:
    • std::endl:输出换行并刷新
    • flush() 手动刷新
    • 缓冲区满时自动刷新
    • 程序正常退出时自动刷新
    • std::cerr无缓冲

五、重定向流

C++ 中流的重定向(redirect)是指改变标准输入输出流(如 std::coutstd::cin)的目标,使它们指向其他设备(如文件、字符串、网络等),而不是默认的控制台(终端)。这对于日志保存、测试、捕获输出都非常有用。


重定向 std::coutstd::cerr 到文件

方法:修改流缓冲区(streambuf

std::cout 等流对象内部都有一个缓冲区指针 rdbuf(),通过替换它,我们可以让输出写到别处。


代码示例:把 std::cout 重定向到文件

#include <iostream>
#include <fstream>

int main() {
    std::ofstream file("example.txt");
    if (!file) {
        std::cerr << "打开文件失败!" << std::endl;
        return 1;
    }

    // 备份原 cout 缓冲区指针
    std::streambuf* old_cout_buf = std::cout.rdbuf();

    // 将 cout 缓冲区指向文件流的缓冲区
    std::cout.rdbuf(file.rdbuf());

    // 现在所有 cout 输出写入文件
    std::cout << "这行写入了 example.txt 文件" << std::endl;

    // 恢复原来的缓冲区(输出回终端)
    std::cout.rdbuf(old_cout_buf);

    std::cout << "这行又输出到终端了" << std::endl;

    return 0;
}

重定向 std::cin 从文件读取

#include <iostream>
#include <fstream>

int main() {
    std::ifstream file("/home/fxq/untitled3/example.txt");
    if (!file) {
        std::cerr << "打开文件失败!" << std::endl;
        return 1;
    }

    // 备份原 cin 缓冲区指针
    std::streambuf* old_cin_buf = std::cin.rdbuf();

    // 将 cin 缓冲区指向文件流
    std::cin.rdbuf(file.rdbuf());

    std::string line;
    std::getline(std::cin, line);
    std::cout << "从文件读取的内容: " << line << std::endl;

    // 恢复原缓冲区
    std::cin.rdbuf(old_cin_buf);

    return 0;
}

重定向流到字符串(内存)

可以用 std::stringstream

#include <iostream>
#include <sstream>

int main() {
    std::stringstream ss;
    
    // 重定向 cout 到 stringstream
    std::streambuf* old_buf = std::cout.rdbuf(ss.rdbuf());

    std::cout << "Hello, 内存流!" << std::endl;

    // 恢复 cout
    std::cout.rdbuf(old_buf);

    // 获取写入的字符串
    std::string captured = ss.str();
    std::cout << "捕获的字符串内容是: " << captured << std::endl;

    return 0;
}

总结重定向关键点

操作步骤 说明
保存旧缓冲区指针 std::streambuf* old_buf = stream.rdbuf();
替换缓冲区指针 stream.rdbuf(new_stream.rdbuf());
使用新的流进行输入输出 输出会重定向到新流
恢复原缓冲区指针 stream.rdbuf(old_buf);

补充:命令行重定向

操作系统层面也支持重定向,不用代码实现,例如:

./program > output.txt  # 重定向 stdout
./program < input.txt   # 重定向 stdin
./program 2> error.log  # 重定向 stderr

posted @ 2025-07-01 17:07  aixueforever  阅读(225)  评论(0)    收藏  举报