在 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 << "Hellon"; |
向终端打印文本 |
| 输出错误信息 | std::cerr << "Error!n"; |
立即打印错误信息 |
| 输出日志 | std::clog << "Log messagen"; |
缓冲写入日志信息 |
| 刷新缓冲区 | 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::istream和std::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 |
输入输出流基类(继承自 istream 和 ostream) |
<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()不会跳过空白字符,并且不会自动添加