一、C++ IO 流整体体系结构
C++ IO 流的核心继承关系如下(简化版):
bash
ios_base(最基类,提供流状态、格式控制等通用功能)
↓
ios(封装流缓冲区streambuf,提供基本流操作)
↓ ↓
istream ostream(输入流基类/输出流基类)
↓ ↓ ↓
ifstream ofstream ostringstream(文件输入/文件输出/字符串输出)
↓ ↓ ↓
fstream iostream istringstream(文件读写/标准IO/字符串输入)
↓
stringstream(字符串读写)
核心概念
- 流(Stream):数据的有序序列,数据从一个位置流向另一个位置
- 缓冲区(Buffer):内存中的一块临时区域,用于减少 IO 操作次数,提高效率
- 流状态:每个流都有状态标志位,用于表示流的当前状态(正常、失败、结束等)
- 格式化控制:通过控制符或成员函数设置输入输出的格式(如进制、宽度、精度等)
二、标准流(控制台 IO)
标准流是预定义的全局对象,用于与控制台进行交互,定义在<iostream>头文件中。
2.1 标准流对象
表格
| 对象 | 类型 | 用途 | 缓冲方式 |
|---|---|---|---|
cin |
istream |
标准输入(键盘) | 行缓冲 |
cout |
ostream |
标准输出(屏幕) | 行缓冲 |
cerr |
ostream |
标准错误输出 | 无缓冲(立即输出) |
clog |
ostream |
标准日志输出 | 行缓冲 |
2.2 标准输入流(cin)的使用
cin是istream类的对象,用于从标准输入读取数据。
基本输入操作
cpp
#include <iostream>
using namespace std;
int main() {
int a;
double b;
string s;
// 1. 使用>>运算符(自动跳过空白字符:空格、制表符、换行符)
cin >> a >> b >> s;
cout << "a=" << a << ", b=" << b << ", s=" << s << endl;
// 2. 读取整行(包含空格)
string line;
cin.ignore(); // 忽略上一次输入留下的换行符
getline(cin, line);
cout << "整行输入:" << line << endl;
// 3. 读取单个字符
char c;
cin.get(c); // 读取一个字符,包括空白字符
cout << "单个字符:" << c << endl;
// 4. 读取多个字符
char buf[100];
cin.get(buf, 100); // 读取最多99个字符,遇到换行符停止
cout << "多个字符:" << buf << endl;
return 0;
}
常用成员函数
get():读取单个字符getline(char* buf, int n):读取一行到字符数组ignore(int n=1, int delim=EOF):忽略 n 个字符,直到遇到 delimpeek():查看下一个字符但不读取putback(char c):将字符放回输入流read(char* buf, int n):读取 n 个字节(二进制模式)
2.3 标准输出流(cout)与控制符
cout是ostream类的对象,用于向标准输出写入数据。
基本输出操作
cpp
#include <iostream>
#include <iomanip> // 包含格式化控制符
using namespace std;
int main() {
// 1. 基本输出
cout << "Hello, C++!" << endl; // endl:换行并刷新缓冲区
cout << "a=" << 10 << ", b=" << 3.14 << '\n'; // '\n':仅换行,不刷新
// 2. 进制控制
int num = 255;
cout << "十进制:" << dec << num << endl;
cout << "八进制:" << oct << num << endl;
cout << "十六进制:" << hex << num << endl;
cout << "十六进制(大写):" << uppercase << hex << num << endl;
cout << nouppercase << dec; // 恢复默认
// 3. 浮点数精度控制
double pi = 3.1415926535;
cout << "默认精度:" << pi << endl; // 默认6位有效数字
cout << "精度3:" << setprecision(3) << pi << endl;
cout << "小数点后3位:" << fixed << setprecision(3) << pi << endl;
cout << "科学计数法:" << scientific << setprecision(4) << pi << endl;
cout << defaultfloat; // 恢复默认
// 4. 宽度和填充
cout << setw(10) << "姓名" << setw(10) << "年龄" << endl;
cout << setw(10) << "张三" << setw(10) << 20 << endl;
cout << setfill('-') << setw(20) << "分隔线" << setfill(' ') << endl;
// 5. 对齐方式
cout << left << setw(10) << "左对齐" << right << setw(10) << "右对齐" << endl;
cout << internal << setw(10) << showpos << -123 << endl; // 符号左对齐,数值右对齐
return 0;
}
常用控制符
表格
| 控制符 | 作用 |
|---|---|
dec |
十进制输出 |
oct |
八进制输出 |
hex |
十六进制输出 |
uppercase |
十六进制大写 |
nouppercase |
十六进制小写 |
fixed |
固定小数格式 |
scientific |
科学计数法 |
setprecision(n) |
设置精度 |
setw(n) |
设置输出宽度(仅对下一个输出有效) |
setfill(c) |
设置填充字符 |
left |
左对齐 |
right |
右对齐 |
internal |
内部对齐 |
showpos |
显示正号 |
noshowpos |
不显示正号 |
endl |
换行并刷新缓冲区 |
flush |
刷新缓冲区 |
三、文件流
文件流用于与磁盘文件进行交互,定义在<fstream>头文件中。
3.1 文件流类
表格
| 类名 | 用途 | 继承自 |
|---|---|---|
ifstream |
文件输入流(只读) | istream |
ofstream |
文件输出流(只写) | ostream |
fstream |
文件输入输出流(读写) | iostream |
3.2 文件打开模式
打开文件时需要指定打开模式,多个模式可以用|组合:
表格
| 模式 | 作用 |
|---|---|
ios::in |
读模式(ifstream 默认) |
ios::out |
写模式(ofstream 默认) |
ios::app |
追加模式(写入到文件末尾) |
ios::ate |
打开后定位到文件末尾 |
ios::trunc |
截断模式(如果文件存在,清空内容) |
ios::binary |
二进制模式(默认是文本模式) |
3.3 文件操作示例
文本文件读写
cpp
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
// 1. 写文件
ofstream outfile("test.txt"); // 默认ios::out | ios::trunc
if (outfile.is_open()) { // 检查文件是否成功打开
outfile << "Hello, File!" << endl;
outfile << "这是一行中文" << endl;
outfile << 123 << " " << 3.14 << endl;
outfile.close(); // 关闭文件(也可以让析构函数自动关闭)
} else {
cerr << "无法打开文件进行写入" << endl;
return 1;
}
// 2. 读文件
ifstream infile("test.txt");
if (infile.is_open()) {
string line;
// 逐行读取
while (getline(infile, line)) {
cout << line << endl;
}
infile.close();
} else {
cerr << "无法打开文件进行读取" << endl;
return 1;
}
// 3. 追加写入
ofstream appendfile("test.txt", ios::app);
if (appendfile.is_open()) {
appendfile << "这是追加的内容" << endl;
appendfile.close();
}
return 0;
}
二进制文件读写
cpp
#include <iostream>
#include <fstream>
using namespace std;
struct Student {
int id;
char name[20];
double score;
};
int main() {
// 二进制写文件
ofstream outfile("students.dat", ios::binary);
if (outfile.is_open()) {
Student s1 = {1, "张三", 90.5};
Student s2 = {2, "李四", 85.0};
outfile.write(reinterpret_cast<char*>(&s1), sizeof(Student));
outfile.write(reinterpret_cast<char*>(&s2), sizeof(Student));
outfile.close();
}
// 二进制读文件
ifstream infile("students.dat", ios::binary);
if (infile.is_open()) {
Student s;
while (infile.read(reinterpret_cast<char*>(&s), sizeof(Student))) {
cout << "ID: " << s.id << ", 姓名: " << s.name << ", 成绩: " << s.score << endl;
}
infile.close();
}
return 0;
}
3.4 文件指针操作
seekg(pos):设置输入文件指针位置seekg(offset, origin):从 origin 偏移 offset 个字节tellg():获取输入文件指针当前位置seekp(pos):设置输出文件指针位置seekp(offset, origin):从 origin 偏移 offset 个字节tellp():获取输出文件指针当前位置
origin 可以是:
ios::beg:文件开头ios::cur:当前位置ios::end:文件末尾
四、字符串流
字符串流用于在内存中对字符串进行 IO 操作,定义在<sstream>头文件中。它将字符串当作流来处理,可以方便地进行字符串与其他类型之间的转换。
4.1 字符串流类
| 类名 | 用途 | 继承自 |
|---|---|---|
istringstream |
字符串输入流(从字符串读取) | istream |
ostringstream |
字符串输出流(写入到字符串) | ostream |
stringstream |
字符串输入输出流(读写) | iostream |
4.2 字符串流使用示例
类型转换
cpp
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main() {
// 1. 数值转字符串
int num = 123;
double pi = 3.14159;
ostringstream oss;
oss << "num=" << num << ", pi=" << pi;
string s = oss.str(); // 获取生成的字符串
cout << s << endl; // 输出:num=123, pi=3.14159
// 2. 字符串转数值
string s2 = "456 7.89";
istringstream iss(s2);
int a;
double b;
iss >> a >> b;
cout << "a=" << a << ", b=" << b << endl; // 输出:a=456, b=7.89
// 3. 清空字符串流
oss.str(""); // 清空内容
oss << "新内容";
cout << oss.str() << endl;
return 0;
}
字符串分割
cpp
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
vector<string> split(const string& s, char delimiter) {
vector<string> tokens;
string token;
istringstream tokenStream(s);
while (getline(tokenStream, token, delimiter)) {
tokens.push_back(token);
}
return tokens;
}
int main() {
string s = "apple,banana,orange,grape";
vector<string> fruits = split(s, ',');
for (const string& fruit : fruits) {
cout << fruit << endl;
}
return 0;
}
五、字符串流应用:生成 JSON 字符串
字符串流非常适合用于生成结构化的文本,如 JSON、XML 等。下面是一个使用ostringstream生成 JSON 字符串的示例:
cpp
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
// 生成JSON对象
string generateJson(const string& name, int age, const vector<string>& hobbies) {
ostringstream oss;
oss << "{" << endl;
oss << " \"name\": \"" << name << "\"," << endl;
oss << " \"age\": " << age << "," << endl;
oss << " \"hobbies\": [" << endl;
for (size_t i = 0; i < hobbies.size(); ++i) {
oss << " \"" << hobbies[i] << "\"";
if (i != hobbies.size() - 1) {
oss << ",";
}
oss << endl;
}
oss << " ]" << endl;
oss << "}" << endl;
return oss.str();
}
int main() {
vector<string> hobbies = {"读书", "编程", "游泳"};
string json = generateJson("张三", 25, hobbies);
cout << json << endl;
return 0;
}
输出结果:
cpp
{
"name": "张三",
"age": 25,
"hobbies": [
"读书",
"编程",
"游泳"
]
}
六、字符缓冲流
C++ IO 流的所有操作都通过缓冲区(streambuf) 来完成。缓冲区是连接流和实际设备(键盘、屏幕、文件、内存)的中间层。
6.1 缓冲区的工作原理
当你向流写入数据时,数据首先被写入缓冲区,而不是直接写入设备。当缓冲区满了、遇到换行符(行缓冲)、或者显式调用flush()时,缓冲区的内容才会被刷新到实际设备。
同样,当你从流读取数据时,流会一次性从设备读取一块数据到缓冲区,然后从缓冲区逐个返回给你,这样可以减少 IO 操作次数,提高效率。
6.2 缓冲区类型
- 无缓冲 :数据立即写入设备(如
cerr) - 行缓冲 :遇到换行符时刷新(如
cout、clog) - 全缓冲:缓冲区满时刷新(如文件流)
6.3 缓冲区操作
cpp
#include <iostream>
#include <fstream>
using namespace std;
int main() {
// 1. 显式刷新缓冲区
cout << "这行内容会立即输出" << flush;
cout << "这行内容会在换行后输出" << endl;
// 2. 关闭缓冲区(不推荐,会降低性能)
cout << "关闭缓冲区前" << endl;
cout.unsetf(ios::unitbuf); // 确保unitbuf标志未设置
cout << "这行内容会被缓冲";
// 此时屏幕上还看不到上面的内容
cout.flush(); // 手动刷新
cout << endl;
// 3. 文件流缓冲区
ofstream outfile("buffer_test.txt");
outfile << "第一行内容";
// 此时内容还在缓冲区,文件中没有
outfile << endl; // 换行刷新缓冲区(文本模式)
outfile << "第二行内容";
outfile.flush(); // 手动刷新
outfile << "第三行内容";
outfile.close(); // 关闭文件时自动刷新
return 0;
}
6.4 自定义缓冲区
你可以通过继承streambuf类来自定义缓冲区,实现特殊的 IO 行为。例如,将输出同时写入到控制台和文件:
cpp
#include <iostream>
#include <fstream>
#include <streambuf>
using namespace std;
class TeeBuf : public streambuf {
private:
streambuf* console_buf;
streambuf* file_buf;
protected:
virtual int overflow(int c) override {
if (c == EOF) {
return !EOF;
}
int r1 = console_buf->sputc(c);
int r2 = file_buf->sputc(c);
return (r1 == EOF || r2 == EOF) ? EOF : c;
}
virtual int sync() override {
int r1 = console_buf->pubsync();
int r2 = file_buf->pubsync();
return (r1 == 0 && r2 == 0) ? 0 : -1;
}
public:
TeeBuf(streambuf* console, streambuf* file)
: console_buf(console), file_buf(file) {}
};
int main() {
ofstream outfile("tee_output.txt");
TeeBuf tee_buf(cout.rdbuf(), outfile.rdbuf());
ostream tee(&tee_buf);
tee << "这行内容会同时输出到控制台和文件" << endl;
tee << "Hello, Tee Buffer!" << endl;
return 0;
}
七、流状态与错误处理
每个流都有状态标志位,用于表示流的当前状态:
表格
| 状态位 | 含义 |
|---|---|
ios::goodbit |
一切正常 |
ios::eofbit |
到达文件末尾 |
ios::failbit |
操作失败(如类型不匹配) |
ios::badbit |
严重错误(如流损坏) |
流状态检查
cpp
#include <iostream>
#include <fstream>
using namespace std;
int main() {
ifstream infile("nonexistent.txt");
if (infile.good()) {
cout << "流状态正常" << endl;
}
if (infile.fail()) {
cout << "操作失败" << endl;
}
if (infile.bad()) {
cout << "严重错误" << endl;
}
if (infile.eof()) {
cout << "到达文件末尾" << endl;
}
// 流对象可以直接作为布尔值使用
if (infile) {
cout << "流可以使用" << endl;
} else {
cout << "流不可使用" << endl;
}
// 清除流状态
infile.clear();
return 0;
}
八、总结
C++ IO 流体系提供了强大而灵活的输入输出功能:
- 标准流:用于控制台交互,是最常用的 IO 方式
- 文件流:用于文件读写,支持文本和二进制模式
- 字符串流:用于内存中的字符串操作,非常适合类型转换和生成结构化文本
- 字符缓冲流:是所有 IO 流的底层实现,通过缓冲区提高 IO 效率