C++ I/O流
C++ I/O流 是 标准库提供的面向对象的输入输出系统,比 C 语言的
printf/scanf
更安全、更灵活
必要的头文件:
#include <iostream> // 标准输入输出
#include <fstream> // 文件操作
#include <sstream> // 字符串流
对象 | 描述 | 对应设备 |
---|---|---|
cin |
标准输入(键盘) | stdin |
cout |
标准输出(屏幕) | stdout |
cerr |
标准错误(无缓冲) | stderr |
clog |
标准日志(有缓冲) | stderr |
注意:
std::cerr
是无缓冲的:它会立即将内容发送到终端,确保错误信息不被延迟。
std::cout
是行缓冲的:如果没有换行符或手动刷新,可能不会立即显示。
两个都是输出到终端 cerr是错误 输出 即使程序崩溃也会输出
调用代码示例:
int num;
std::cout << "Enter a number: "; // 输出 行缓冲 正常输出
std::cin >> num; // 输入 num=键盘输入值
std::cerr << "Error occurred!"; // 错误输出 无缓冲 立即显示 (即使程序崩溃)
std::clog << "Warning message"; // 日志输出 有缓冲 可能不会立即显示(延迟输出)
格式化输出:
控制符
std::endl 换行并刷新缓冲区
std::setw(n) 设置字段宽度为 n (仅对下一个输出有效)
std::setprecision(n) 设置浮点数精度为 n 默认精度是6位 当n设置为4时 精度4位
std::fixed 固定小数格式
std::scientific 科学计数法格式std::hex 十六进制
std::oct 八进制
std::dec 十进制
示例代码展示:
#include <iostream>
#include <iomanip> // 包含控制符所需的头文件
int main() {
int num = 42;
double pi = 3.141592653589793;
// 1. std::endl:换行并刷新缓冲区
std::cout << "Line 1" << std::endl;
std::cout << "Line 2" << std::endl;
// 2. std::setw(n):设置字段宽度(仅对下一个输出有效)
std::cout << "\n格式化表格:" << std::endl;
std::cout << std::setw(10) << "Name" << std::setw(10) << "Age" << std::endl;
std::cout << std::setw(10) << "Alice" << std::setw(10) << 25 << std::endl;
std::cout << std::setw(10) << "Bob" << std::setw(10) << 30 << std::endl;
// 3. std::setprecision(n) + std::fixed:控制浮点数精度和小数格式
std::cout << "\n默认精度(6位):" << pi << std::endl;
std::cout << "固定小数(4位):" << std::fixed << std::setprecision(4) << pi << std::endl;
// 4. std::scientific:科学计数法格式
std::cout << "科学计数法(2位):" << std::scientific << std::setprecision(2) << pi << std::endl;
// 恢复默认格式(避免影响后续输出)
std::cout.unsetf(std::ios::fixed | std::ios::scientific);
std::cout << "\n恢复默认格式:" << pi << std::endl;
return 0;
}
//输出效果展示
Line 1
Line 2
格式化表格:
Name Age
Alice 25
Bob 30
默认精度(6位):3.14159
固定小数(4位):3.1416
科学计数法(2位):3.14e+00
恢复默认格式:3.14159
操作文件(文件流File I/O)
关键头文件:
#include <fstream> //操作文件流
#include <string> //字符串操作 例如std::getline
#include <iostream>//标准输出 例如 std::cout
示例代码:
std::ofstream out("test1.txt"); //打开该文件如果没有 默认创建文件 out自定义名
if (out.is_open()) //检测是否打开文件
{ out<<"hello world\n 2025年6月17日17:33:05 \n"; //写入值 到文件中
std::cout << "open file success"; //输出提示 文件已打开
}
else
{
std::cerr << "No find file";
}
out.close();
//以下是读取文件
std::ifstream in("test1.txt"); //找不到文件会报错 in是自定义名
if (in.is_open())
{
std::string strs;
while(std::getline(in,strs))
{
std::cout<<strs<<std::endl;
}
}
else
{
std::cerr << "No find file";
}
in.close();
文件处理模式选择:
std::ofstream log("log.txt", std::ios::app); // 追加模式
模式标志 | 描述 | 默认情况 |
---|---|---|
std::ios::in |
读取模式(文件必须存在) | ifstream 默认包含 |
std::ios::out |
写入模式(创建或清空文件) | ofstream 默认包含 |
std::ios::app |
追加模式(写入时保留原内容,总是在末尾添加) | 非默认 |
std::ios::ate |
打开时定位到文件末尾(可读写) | 非默认 |
std::ios::trunc |
清空文件(需配合 out 使用) |
out 隐含此模式 |
std::ios::binary |
二进制模式(避免换行符转换) | 非默认 |
关于二进制模式的使用:
#include <cstddef> // 用于 std::byte c++17 以上版本
std::ofstream binfile("data.bin", std::ios::binary | std::ios::out);
binfile.write(reinterpret_cast<const std::byte*>(&some_data), sizeof(some_data));
或者:
std::ofstream binfile("data.bin", std::ios::binary | std::ios::out);
binfile.write(reinterpret_cast<char*>(&some_data), sizeof(some_data));
或者:
char buffer[1024];
// ... 填充 buffer ...
binfile.write(buffer, sizeof(buffer));
注意事项:
在 C++17 之前 char* 是 C/C++ 中表示“字节”的传统类型。
现代方式(C++17 起):std::byte* 需要包含 <cstddef> 头文件!!!
&some_data
取 some_data 的内存地址,获得指向数据的指针
sizeof(some_data)
计算 some_data 的字节大小。
确保写入的字节数与实际数据大小一致。
std::ofstream::write() 的函数原型要求传入一个 char* 或 const std::byte* 类型的指针,指向要写入的数据的起始地址。但实际数据可能是任意类型(如 int、double、结构体等),因此需要通过 reinterpret_cast 进行指针类型转换。
reinterpret_cast 的语义
将指针 重新解释 为另一种类型,不改变底层二进制数据。
它是低级别的、不安全的转换,但在此场景下是必要的。
组合使用:
in | out
:读写已存在文件(不自动创建)
out | trunc
:等同于默认 ofstream
行为
out | app
:等同于 app
单独使用
举例说明:
将不会再默认创建文件 当文件没有找到时
std::ofstream insaa("test2.txt",std::ios::in);
//将不会再默认创建文件 当文件没有找到时
注意事项:
使用 app
模式时,所有写入都在末尾,无法修改文件中间内容
ate
只影响初始位置,不影响写入行为
文件流对象在析构时会自动关闭文件,但显式调用 close()
是好习惯
字符串流(String Stream)
必要头文件:
#include <sstream>
将数据转换为字符串:
std::ostringstream oss;
oss << "Age: " << 25 << ", PI: " << 3.14; //字符串拼接
std::string str = oss.str(); // 获取字符串
std::cout << str; // 输出: Age: 25, PI: 3.14
//std::ostringstream 是 C++ 标准库中的一个类,用于 将数据以字符串形式写入内存(而不是文件或屏幕)
从字符串解析数据:
std::istringstream iss("10 3.14 hello");
int num; double val; std::string word;
iss >> num >> val >> word; // 解析为变量
//std::istringstream 是 C++ 标准库中用于 从字符串中读取数据 的输入流类(定义在 <sstream> 头文件中)
流状态函数判断
函数 | 作用 | 典型触发场景 |
---|---|---|
stream.good() |
检查流是否完全正常(无任何错误,可继续操作)。 | - 所有操作成功时返回 true 。- 其他状态函数均为 false 时返回 true 。 |
stream.eof() |
检查是否到达文件末尾(End Of File)。 | - 读取文件时遇到文件结束符。 - 注意:仅表示“读完”,不一定是错误! |
stream.fail() |
检查是否发生非致命错误(可恢复,如类型不匹配、格式错误)。 | - 尝试将 "abc" 读入 int 变量。- 输入格式不符合预期。 |
stream.bad() |
检查是否发生致命错误(不可恢复,如文件损坏、流被意外关闭)。 | - 文件读取时磁盘被拔出。 - 流缓冲区内存不足。 |
状态之间的关系:
good() == true
:
所有其他状态函数(eof()
、fail()
、bad()
)必须均为false
,表示流完全正常。状态优先级:
bad() > fail() > eof()
(即bad()
的优先级最高)。
good() |
eof() |
fail() |
bad() |
含义 |
---|---|---|---|---|
true |
false |
false |
false |
流完全正常。 |
false |
true |
false |
false |
正常到达文件末尾。 |
false |
false |
true |
false |
发生非致命错误(如类型错误)。 |
false |
false |
true |
true |
发生致命错误(如文件损坏)。 |
范例程序(直观展示用法):
#include <iostream>
#include <fstream>
#include <sstream>
void check_stream_state(std::istream& stream, const std::string& stream_name) {
std::cout << "\n[" << stream_name << "状态检查]\n";
if (stream.good()) {
std::cout << "✅ 流状态正常,可以继续操作\n";
} else {
std::cout << "⚠️ 流状态异常\n";
}
if (stream.eof()) {
std::cout << "📌 已到达文件/字符串末尾\n";
}
if (stream.fail()) {
std::cout << "🔄 发生非致命错误(如类型不匹配、格式错误)\n";
stream.clear(); // 清除错误状态以便继续操作
}
if (stream.bad()) {
std::cout << "❌ 发生致命错误(如文件损坏、硬件故障)\n";
}
}
int main() {
// 示例1:文件流正常读取
std::cout << "==== 示例1:正常读取文件 ====\n";
std::ifstream file("data.txt");
if (file) {
int value;
file >> value;
check_stream_state(file, "文件流");
file.close();
} else {
std::cout << "无法打开文件\n";
}
// 示例2:类型不匹配错误
std::cout << "\n==== 示例2:类型不匹配 ====\n";
std::istringstream number_stream("abc"); // 不是数字
int number;
number_stream >> number;
check_stream_state(number_stream, "字符串流");
// 示例3:到达字符串末尾
std::cout << "\n==== 示例3:正常到达末尾 ====\n";
std::istringstream eof_stream("123");
int val;
eof_stream >> val;
check_stream_state(eof_stream, "EOF测试流");
return 0;
}
//输出效果
==== 示例1:正常读取文件 ====
[文件流状态检查]
✅ 流状态正常,可以继续操作
==== 示例2:类型不匹配 ====
[字符串流状态检查]
⚠️ 流状态异常
🔄 发生非致命错误(如类型不匹配、格式错误)
==== 示例3:正常到达末尾 ====
[EOF测试流状态检查]
⚠️ 流状态异常
📌 已到达文件/字符串末尾
简化范例程序(更直观):
std::ifstream file("data.txt");
if (!file) { // 等价于 file.fail() 文件是否发生错误
std::cerr << "Failed to open file!";
}
自定义类型I/O
class Point {
public:
int x, y;
// friend 友元声明(可访问 private 成员)
friend std::ostream& operator<<(std::ostream& os, const Point& p) {
return os << "(" << p.x << ", " << p.y << ")";
}
friend std::istream& operator>>(std::istream& is, Point& p) {
return is >> p.x >> p.y;
}
};
// 使用
Point p{1, 2};
std::cout << p; // 输出: (1, 2)
std::cin >> p; // 输入: 3 4 → p={3,4}
=============================================
关键点:
friend 声明:
使这个函数能访问 Point 的 private 成员(虽然这里成员是 public,但习惯上仍用 friend)
返回 ostream&:
支持链式调用(如 cout << p1 << p2)
operator<< 是 输出流运算符,通常用于自定义类型的输出格式化。
operator>> 是 输入流运算符,用于从输入流(如 std::cin 或文件流)中提取数据到自定义类型的对象中
参数:
os:输出流对象(如 std::cout)
p:要输出的 Point 对象(const 引用避免拷贝)
实现:
将点格式化为 (x, y) 的形式(如 (1, 2))
缓冲区管理
操作 | 作用 |
---|---|
stream.flush() |
手动刷新缓冲区 |
std::endl |
换行 + 刷新缓冲区 |
std::unitbuf |
每次操作后自动刷新 |
调用示例:
std::cout << "Hello" << std::flush; // 立即输出
std::cerr << std::unitbuf; // 关闭缓冲(实时输出错误)
总结
功能 | 工具 | 示例 |
---|---|---|
标准 I/O | cin/cout/cerr |
cout << "Hello"; |
文件操作 | ifstream/ofstream |
ofstream file("data.txt"); |
字符串格式化 | istringstream/ostringstream |
oss << "Value: " << 42; |
自定义类型 | 重载 << 和 >> |
cout << myObject; |
格式化控制 | <iomanip> 头文件 |
setprecision(2) |