iostream
iostream 是 C++ 标准库中最常用的输入输出流库,负责把程序中的数据与外部设备连接起来,例如键盘、终端、文件、字符串缓冲区等。它的设计核心是“流”:数据像水流一样从一个地方流向另一个地方,输入流把数据读进程序,输出流把数据写出程序。
和 C 语言中的 printf、scanf 相比,iostream 更强调类型安全、可扩展性和面向对象设计。它可以直接处理 C++ 类型,也允许用户为自己的类重载输入输出运算符,从而让自定义对象像内置类型一样被读写。
头文件与常用对象
使用标准输入输出流时,通常包含头文件:
#include <iostream>
这个头文件提供了四个最常见的全局流对象:
| 对象 | 类型 | 作用 |
|---|---|---|
std::cin | std::istream | 标准输入,通常对应键盘 |
std::cout | std::ostream | 标准输出,通常对应终端 |
std::cerr | std::ostream | 标准错误输出,通常不缓冲 |
std::clog | std::ostream | 标准日志输出,通常缓冲 |
最基本的例子:
#include <iostream>
int main() {
int age;
std::cout << "请输入年龄: ";
std::cin >> age;
std::cout << "你的年龄是 " << age << '\n';
return 0;
}
其中 << 是输出运算符,表示把右侧数据写入左侧输出流;>> 是输入运算符,表示从左侧输入流读取数据到右侧变量。
流库的基本结构
iostream 并不是一个孤立的类,而是一组围绕输入输出构建的类型体系。常见类型包括:
| 类型 | 说明 |
|---|---|
std::ios | 输入输出流的公共基础类,管理格式、状态等 |
std::istream | 输入流基类,支持 >>、get、getline 等读取操作 |
std::ostream | 输出流基类,支持 <<、put、write 等写入操作 |
std::iostream | 同时支持输入和输出 |
std::ifstream | 文件输入流,定义在 <fstream> |
std::ofstream | 文件输出流,定义在 <fstream> |
std::fstream | 文件输入输出流,定义在 <fstream> |
std::istringstream | 字符串输入流,定义在 <sstream> |
std::ostringstream | 字符串输出流,定义在 <sstream> |
std::stringstream | 字符串输入输出流,定义在 <sstream> |
可以把它理解成一套统一接口:无论数据来自键盘、文件还是字符串,都可以通过相似的方式读取;无论数据写到终端、文件还是字符串,也都可以通过相似的方式输出。
格式化输出
流对象可以控制数字、宽度、填充字符、进制、精度等格式。常用格式控制符定义在 <iomanip> 中。
#include <iomanip>
#include <iostream>
int main() {
double value = 3.1415926;
int number = 255;
std::cout << std::fixed << std::setprecision(2) << value << '\n';
std::cout << std::hex << number << '\n';
std::cout << std::setw(8) << std::setfill('*') << 42 << '\n';
return 0;
}
常用格式控制符:
| 控制符 | 作用 |
|---|---|
std::endl | 输出换行并刷新缓冲区 |
std::flush | 立即刷新缓冲区 |
std::setw(n) | 设置下一个输出字段宽度 |
std::setfill(c) | 设置填充字符 |
std::setprecision(n) | 设置浮点数精度 |
std::fixed | 使用定点小数格式 |
std::scientific | 使用科学计数法 |
std::hex | 使用十六进制 |
std::dec | 使用十进制 |
std::oct | 使用八进制 |
std::boolalpha | 把布尔值输出为 true / false |
需要注意的是,有些格式状态会保留在流对象中,例如 std::fixed、std::hex;有些只影响下一次输出,例如 std::setw。
输入方式
std::cin >> value 是最常见的输入方式,它会跳过前导空白字符,并按目标变量的类型解析数据:
int x;
double y;
std::string name;
std::cin >> x >> y >> name;
这种方式适合读取用空格、换行、制表符分隔的数据,但它不会读取包含空格的一整行文本。如果需要读取整行,应使用 std::getline:
#include <iostream>
#include <string>
int main() {
std::string line;
std::getline(std::cin, line);
std::cout << "输入内容: " << line << '\n';
return 0;
}
一个常见坑是混用 >> 和 std::getline。>> 读取数字后会把换行符留在输入缓冲区,下一次 getline 可能会直接读到空行:
int age;
std::string name;
std::cin >> age;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::getline(std::cin, name);
上面这段代码需要包含:
#include <limits>
流状态
输入输出操作可能失败,因此每个流对象都会维护自己的状态。常见状态包括:
| 状态 | 说明 |
|---|---|
good() | 流状态正常 |
eof() | 到达文件或输入末尾 |
fail() | 格式解析失败,通常可以恢复 |
bad() | 严重错误,通常不可恢复 |
例如读取整数时,如果用户输入了非数字内容,std::cin 会进入失败状态:
#include <iostream>
#include <limits>
int main() {
int value;
while (!(std::cin >> value)) {
std::cout << "请输入一个整数: ";
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
std::cout << "读取成功: " << value << '\n';
return 0;
}
clear() 用来清除错误状态,ignore() 用来丢弃缓冲区中的错误输入。
文件流
虽然 iostream 主要指标准输入输出流,但它的思想也延伸到了文件流。文件流定义在 <fstream> 中。
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::ofstream out("data.txt");
out << "hello file\n";
std::ifstream in("data.txt");
std::string line;
while (std::getline(in, line)) {
std::cout << line << '\n';
}
return 0;
}
文件流的优势是接口和 cin、cout 非常相似。把输出目标从终端换成文件,通常只需要把 std::cout 换成 std::ofstream 对象。
字符串流
字符串流定义在 <sstream> 中,可以把字符串当作输入输出设备使用,常用于解析文本、拼接格式化内容、类型转换等。
#include <iostream>
#include <sstream>
#include <string>
int main() {
std::string text = "Tom 18 92.5";
std::istringstream input(text);
std::string name;
int age;
double score;
input >> name >> age >> score;
std::cout << name << ", " << age << ", " << score << '\n';
return 0;
}
构造输出字符串:
#include <sstream>
#include <string>
std::string make_message(const std::string& name, int score) {
std::ostringstream output;
output << name << " 的分数是 " << score;
return output.str();
}
自定义类型的输入输出
iostream 的一个重要特点是可以通过重载运算符支持自定义类型。
#include <iostream>
#include <string>
struct Person {
std::string name;
int age;
};
std::ostream& operator<<(std::ostream& os, const Person& person) {
os << person.name << " (" << person.age << ")";
return os;
}
std::istream& operator>>(std::istream& is, Person& person) {
is >> person.name >> person.age;
return is;
}
int main() {
Person p;
std::cin >> p;
std::cout << p << '\n';
return 0;
}
重载时通常要返回流对象引用,这样才能支持链式调用:
std::cout << a << b << c;
缓冲区与刷新
输出流通常带有缓冲区。程序先把内容写入缓冲区,再在合适的时机真正输出。这样可以减少频繁 IO 带来的性能开销。
常见刷新方式:
| 写法 | 行为 |
|---|---|
'\n' | 输出换行,不一定立即刷新 |
std::endl | 输出换行并刷新 |
std::flush | 只刷新,不输出换行 |
如果只是换行,通常优先使用 '\n',因为 std::endl 会额外刷新缓冲区,频繁使用可能降低性能。
性能优化
在算法竞赛或大量输入输出场景中,常见写法是:
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
sync_with_stdio(false) 会取消 C++ 流与 C 标准 IO 的同步,从而提高速度;cin.tie(nullptr) 会解除 cin 和 cout 的绑定,避免每次输入前自动刷新输出。
完整示例:
#include <iostream>
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
long long sum = 0;
for (int i = 0; i < n; ++i) {
int x;
std::cin >> x;
sum += x;
}
std::cout << sum << '\n';
return 0;
}
使用这两行后,不建议再混用 scanf、printf、getchar、puts 等 C 风格 IO,否则可能出现输入输出顺序不符合预期的问题。
常见使用建议
- 普通换行优先使用
'\n',需要立即刷新时再使用std::endl。 - 读取整行文本时使用
std::getline,读取空白分隔的数据时使用>>。 - 混用
>>和getline时,注意处理残留换行符。 - 判断输入是否成功时,可以直接把流对象放在条件中,例如
while (std::cin >> x)。 - 大量输入输出时,可以关闭同步并解除绑定来提升性能。
- 自定义类型推荐重载
operator<<,这样调试和日志输出会更自然。 - 需要解析字符串时,优先考虑
std::istringstream,不要手写复杂的字符扫描逻辑。
小结
iostream 是 C++ IO 系统的基础。它提供了统一、类型安全、可扩展的输入输出方式,既能处理终端交互,也能扩展到文件和字符串。掌握 cin、cout、格式控制、流状态、文件流、字符串流和自定义类型重载,基本就能覆盖日常 C++ 开发中大多数输入输出需求。