C++ IO 流

一、C++ IO 流整体体系结构

C++ IO 流的核心继承关系如下(简化版):

bash 复制代码
ios_base(最基类,提供流状态、格式控制等通用功能)
  ↓
ios(封装流缓冲区streambuf,提供基本流操作)
  ↓         ↓
istream     ostream(输入流基类/输出流基类)
  ↓         ↓         ↓
ifstream   ofstream   ostringstream(文件输入/文件输出/字符串输出)
  ↓         ↓         ↓
fstream    iostream   istringstream(文件读写/标准IO/字符串输入)
                          ↓
                        stringstream(字符串读写)

核心概念

  1. 流(Stream):数据的有序序列,数据从一个位置流向另一个位置
  2. 缓冲区(Buffer):内存中的一块临时区域,用于减少 IO 操作次数,提高效率
  3. 流状态:每个流都有状态标志位,用于表示流的当前状态(正常、失败、结束等)
  4. 格式化控制:通过控制符或成员函数设置输入输出的格式(如进制、宽度、精度等)

二、标准流(控制台 IO)

标准流是预定义的全局对象,用于与控制台进行交互,定义在<iostream>头文件中。

2.1 标准流对象

表格

对象 类型 用途 缓冲方式
cin istream 标准输入(键盘) 行缓冲
cout ostream 标准输出(屏幕) 行缓冲
cerr ostream 标准错误输出 无缓冲(立即输出)
clog ostream 标准日志输出 行缓冲

2.2 标准输入流(cin)的使用

cinistream类的对象,用于从标准输入读取数据。

基本输入操作
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 个字符,直到遇到 delim
  • peek():查看下一个字符但不读取
  • putback(char c):将字符放回输入流
  • read(char* buf, int n):读取 n 个字节(二进制模式)

2.3 标准输出流(cout)与控制符

coutostream类的对象,用于向标准输出写入数据。

基本输出操作
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 缓冲区类型

  1. 无缓冲 :数据立即写入设备(如cerr
  2. 行缓冲 :遇到换行符时刷新(如coutclog
  3. 全缓冲:缓冲区满时刷新(如文件流)

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 流体系提供了强大而灵活的输入输出功能:

  1. 标准流:用于控制台交互,是最常用的 IO 方式
  2. 文件流:用于文件读写,支持文本和二进制模式
  3. 字符串流:用于内存中的字符串操作,非常适合类型转换和生成结构化文本
  4. 字符缓冲流:是所有 IO 流的底层实现,通过缓冲区提高 IO 效率
相关推荐
库奇噜啦呼4 小时前
【iOS】源码学习-dyld加载
学习·ios·cocoa
real_haha5 小时前
我做了一个仅有 1.3 MB 的 macOS 原生 AI 助手:AskNow
人工智能·macos
June bug16 小时前
(Mac)macOS x86_64上onnxruntime==1.24.4 安装失败
macos
ACP广源盛1392462567318 小时前
iOS 27 开放 AI 生态@ACP#小型化扩展黄金风口,IX8008全面超越 ASM2806,铸就嵌入式 AI 扩展核心
人工智能·嵌入式硬件·macos·ios·计算机外设·objective-c·cocoa
海的辽阔1 天前
如何在MAC下安装EcomGpt模型
macos·大模型·ecomgpt
zandy10111 天前
hermes agent 安装教程 3.0:Win / Mac / Linux 全平台指南
linux·运维·macos
花开·莫之弃1 天前
Mac安装多版本jdk(jenv)
java·开发语言·macos
June bug1 天前
(Mac)torch==2.1.2 与 Python 3.12 不兼容+onnxruntime-silicon 不支持 Intel Mac
开发语言·python·macos
码农小北1 天前
MAC 配置鸿蒙(HarmonyOS) SDK 环境变量完整指南
macos·华为·harmonyos