C++第8章:IO库

C++ IO库详细解析(第8章)

我会为您全面解析C++ Primer第8章的核心知识点,包含详细解释、对比C语言、用法和代码实现。所有代码都有详细注释和整体说明。

一、IO库概述

1.1 C++与C的IO对比

特性 C语言 (stdio.h) C++ (iostream)
基本函数 printf, scanf cin, cout
类型安全 ❌ (格式字符串易错) ✅ (自动类型推导)
扩展性 ❌ (无法扩展) ✅ (支持自定义类型)
面向对象
错误处理 errno 流状态位

1.2 C++ IO核心组件

复制代码

cpp

复制

复制代码
#include <iostream>    // 基础输入输出
#include <fstream>     // 文件操作
#include <sstream>     // 字符串流
#include <iomanip>     // 格式化控制

二、标准输入输出 (iostream)

2.1 基础使用

复制代码

cpp

复制

复制代码
#include <iostream>

int main() {
    int age;
    double salary;
    
    // 输出提示信息
    std::cout << "请输入年龄和工资(空格分隔): ";
    
    // 连续输入多个值
    std::cin >> age >> salary;
    
    // 格式化输出
    std::cout << "年龄: " << age 
              << ", 工资: $" << std::fixed << std::setprecision(2) << salary
              << "\n感谢您的输入!" << std::endl;
    
    return 0;
}

代码说明:​

  1. std::cout用于标准输出
  2. std::cin用于标准输入
  3. <<操作符链式输出多个值
  4. std::fixedstd::setprecision控制小数位数
  5. std::endl插入换行并刷新缓冲区

2.2 流状态管理

复制代码

cpp

复制

复制代码
#include <iostream>

int main() {
    int value;
    
    while (true) {
        std::cout << "请输入一个整数: ";
        std::cin >> value;
        
        // 检查输入是否成功
        if (std::cin.fail()) {
            std::cout << "输入无效!请重新输入。\n";
            
            // 清除错误状态
            std::cin.clear();
            
            // 忽略错误输入(最多忽略100个字符或直到遇到换行)
            std::cin.ignore(100, '\n'); 
        } 
        else {
            std::cout << "有效输入: " << value << "\n";
            break;
        }
    }
    
    return 0;
}

关键状态位:​

  • good(): 流状态正常
  • eof(): 到达文件结尾
  • fail(): 操作失败(可恢复)
  • bad(): 严重错误(不可恢复)

注意事项:​

  • 输入操作后总是检查流状态
  • 错误后使用clear()重置状态
  • 使用ignore()清除无效输入

三、文件流操作 (fstream)

3.1 基础文件读写

复制代码

cpp

复制

复制代码
#include <iostream>
#include <fstream>
#include <string>

int main() {
    // 创建并写入文件
    std::ofstream outFile("data.txt");
    if (!outFile) {
        std::cerr << "无法创建文件!" << std::endl;
        return 1;
    }
    
    outFile << "姓名\t年龄\t工资\n";
    outFile << "张三\t25\t4500.50\n";
    outFile << "李四\t30\t6800.75\n";
    outFile.close();
    
    // 读取文件
    std::ifstream inFile("data.txt");
    if (!inFile) {
        std::cerr << "无法打开文件!" << std::endl;
        return 1;
    }
    
    std::string line;
    std::cout << "\n文件内容:\n";
    while (std::getline(inFile, line)) {
        std::cout << line << '\n';
    }
    inFile.close();
    
    return 0;
}

文件模式:​

模式标志 描述 对应C语言
std::ios::in 读取模式 "r"
std::ios::out 写入模式 "w"
std::ios::app 追加模式 "a"
std::ios::binary 二进制模式 "b"

3.2 二进制文件操作

复制代码

cpp

复制

复制代码
#include <iostream>
#include <fstream>

struct Person {
    char name[50];
    int age;
    double salary;
};

int main() {
    // 二进制写入
    Person people[2] = {{"王五", 28, 5300.50}, {"赵六", 35, 7500.25}};
    
    std::ofstream binOut("people.bin", std::ios::binary);
    binOut.write(reinterpret_cast<const char*>(people), sizeof(people));
    binOut.close();
    
    // 二进制读取
    Person readPeople[2];
    
    std::ifstream binIn("people.bin", std::ios::binary);
    binIn.read(reinterpret_cast<char*>(readPeople), sizeof(readPeople));
    
    std::cout << "\n读取的数据:\n";
    for (const auto& p : readPeople) {
        std::cout << "姓名: " << p.name 
                  << ", 年龄: " << p.age 
                  << ", 工资: " << p.salary << '\n';
    }
    
    return 0;
}

注意事项:​

  1. reinterpret_cast用于原始字节转换
  2. 二进制操作跳过格式化,直接处理原始数据
  3. 结构体必须是POD(普通旧数据类型)

四、字符串流 (sstream)

4.1 字符串解析

复制代码

cpp

复制

复制代码
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

int main() {
    // CSV格式数据解析
    std::string data = "张三,25,4500.50\n李四,30,6800.75";
    std::istringstream iss(data);
    
    std::string line;
    while (std::getline(iss, line)) {
        std::istringstream lineStream(line);
        std::vector<std::string> fields;
        std::string field;
        
        while (std::getline(lineStream, field, ',')) {
            fields.push_back(field);
        }
        
        // 处理解析结果
        if (fields.size() == 3) {
            std::cout << "姓名: " << fields[0]
                      << ", 年龄: " << fields[1]
                      << ", 工资: " << fields[2] << '\n';
        }
    }
    
    return 0;
}

4.2 类型转换

复制代码

cpp

复制

复制代码
#include <iostream>
#include <sstream>

int main() {
    // 数字转字符串
    double pi = 3.14159;
    std::ostringstream oss;
    oss << std::fixed << std::setprecision(3) << pi;
    std::string piStr = "π的值是: " + oss.str();
    std::cout << piStr << '\n';
    
    // 字符串转数字
    std::string numberStr = "42.75";
    std::istringstream iss(numberStr);
    double value;
    iss >> value;
    
    // 验证转换结果
    if (!iss.fail()) {
        std::cout << "转换后的值: " << value * 2 << '\n';
    } else {
        std::cout << "转换失败!\n";
    }
    
    return 0;
}

应用场景:​

  • 复杂字符串解析
  • 数据格式转换
  • 构建复杂输出
  • 模板文本生成

五、格式化控制 (iomanip)

5.1 常用格式化选项

复制代码

cpp

复制

复制代码
#include <iostream>
#include <iomanip>

int main() {
    const double pi = 3.141592653589793;
    const int value = 42;
    
    // 数值格式化
    std::cout << "默认: " << pi << '\n';
    std::cout << "精度(4位): " << std::setprecision(4) << pi << '\n';
    std::cout << "固定小数位(2位): " << std::fixed << std::setprecision(2) << pi << '\n';
    std::cout << "科学计数法: " << std::scientific << pi << '\n';
    
    // 整数格式化
    std::cout << "十进制: " << value << '\n';
    std::cout << "十六进制: " << std::hex << std::showbase << value << '\n';
    std::cout << "八进制: " << std::oct << value << '\n';
    
    // 重置格式化状态
    std::cout << std::dec;
    
    // 宽度和对齐
    std::cout << "\n格式化表格:\n";
    std::cout << std::setw(10) << std::left << "姓名" 
              << std::setw(8) << std::right << "年龄"
              << std::setw(10) << "工资" << '\n';
    
    std::cout << std::setw(10) << std::left << "张三" 
              << std::setw(8) << std::right << 25
              << std::setw(10) << 4500.50 << '\n';
              
    std::cout << std::setw(10) << std::left << "李四" 
              << std::setw(8) << std::right << 30
              << std::setw(10) << 6800.75 << '\n';
    
    // 填充字符
    std::cout << "带填充: |" << std::setfill('*') << std::setw(10) << value << "|\n";
    
    return 0;
}

常用格式化工具:​

函数/操纵符 描述
setprecision(n) 设置浮点精度
fixed 固定小数表示
scientific 科学计数法
setw(n) 设置字段宽度
left 左对齐
right 右对齐
setfill(c) 设置填充字符
hex/oct/dec 数字进制设置
showbase 显示进制前缀
boolalpha 布尔值显示为true/false

六、高级技巧与实战项目

6.1 自定义类型IO支持

复制代码

cpp

复制

复制代码
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>

class Product {
    std::string name;
    double price;
    int stock;
public:
    Product(const std::string& n, double p, int s)
        : name(n), price(p), stock(s) {}
    
    // 重载输出操作符
    friend std::ostream& operator<<(std::ostream& os, const Product& p) {
        // 保持对齐格式
        os << std::setw(20) << std::left << p.name
           << std::setw(10) << std::right << std::fixed << std::setprecision(2) << p.price
           << std::setw(8) << p.stock;
        return os;
    }
    
    // 重载输入操作符
    friend std::istream& operator>>(std::istream& is, Product& p) {
        std::cout << "请输入产品信息 (名称 价格 库存): ";
        if (is >> p.name >> p.price >> p.stock) {
            // 成功读取
        } else {
            // 失败处理
            std::cerr << "输入格式错误!\n";
            is.clear();
        }
        return is;
    }
    
    // CSV格式支持
    std::string toCSV() const {
        std::ostringstream oss;
        oss << name << "," << price << "," << stock;
        return oss.str();
    }
    
    static Product fromCSV(const std::string& csv) {
        std::istringstream iss(csv);
        std::string name;
        double price;
        int stock;
        
        if (std::getline(iss, name, ',') && iss >> price) {
            // 检查是否有逗号后面跟着库存
            if (iss.peek() == ',') iss.ignore();
            iss >> stock;
            return Product(name, price, stock);
        } else {
            throw std::runtime_error("CSV解析错误");
        }
    }
};

int main() {
    // 创建产品并输出
    Product apple("苹果 iPhone 14", 8999.00, 50);
    std::cout << "产品信息: " << apple << "\n\n";
    
    // CSV格式测试
    std::string csv = apple.toCSV();
    std::cout << "CSV格式: " << csv << '\n';
    
    try {
        Product fromCSV = Product::fromCSV(csv);
        std::cout << "从CSV解析的产品信息: " << fromCSV << '\n';
    } catch (const std::exception& e) {
        std::cerr << e.what() << '\n';
    }
    
    return 0;
}

6.2 日志系统实现

复制代码

cpp

复制

复制代码
#include <iostream>
#include <fstream>
#include <sstream>
#include <ctime>
#include <iomanip>

class Logger {
    std::ofstream logFile;
    static std::string getCurrentTime() {
        auto now = std::time(nullptr);
        auto tm = *std::localtime(&now);
        std::ostringstream oss;
        oss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
        return oss.str();
    }
public:
    Logger(const std::string& filename) {
        logFile.open(filename, std::ios::app);
        if (!logFile) {
            throw std::runtime_error("无法打开日志文件!");
        }
    }
    
    ~Logger() {
        if (logFile.is_open()) {
            logFile.close();
        }
    }
    
    void log(const std::string& level, const std::string& message) {
        // 格式: [时间] [级别] 消息
        std::ostringstream oss;
        oss << "[" << getCurrentTime() << "]"
            << "[" << std::setw(5) << level << "] "
            << message << '\n';
            
        logFile << oss.str();
        logFile.flush(); // 确保写入磁盘
        
        // 同时输出到控制台
        std::cout << oss.str();
    }
    
    void info(const std::string& message) {
        log("INFO", message);
    }
    
    void warn(const std::string& message) {
        log("WARN", message);
    }
    
    void error(const std::string& message) {
        log("ERROR", message);
    }
};

int main() {
    try {
        Logger logger("system.log");
        
        logger.info("应用程序启动");
        logger.warn("配置文件缺失,使用默认配置");
        
        // 模拟操作
        for (int i = 0; i < 5; ++i) {
            std::ostringstream oss;
            oss << "处理任务 #" << i + 1;
            logger.info(oss.str());
        }
        
        logger.info("应用程序正常退出");
    } catch (const std::exception& e) {
        std::cerr << "日志错误: " << e.what() << '\n';
    }
    
    return 0;
}

七、最佳实践和常见陷阱

7.1 文件操作注意事项

  1. 打开检查:总是检查文件是否成功打开
复制代码

cpp

复制

复制代码
std::ifstream file("data.txt");
if (!file) {
    // 错误处理
}
  1. 关闭文件:虽然析构函数会自动关闭,但显式关闭更好
复制代码

cpp

复制

复制代码
file.close();
  1. 文件位置:读写操作改变文件位置指针
复制代码

cpp

复制

复制代码
long pos = file.tellg(); // 获取当前位置
file.seekg(0);          // 回到文件开头

7.2 字符串流陷阱

  1. 多次使用:重复使用时要重置状态
复制代码

cpp

复制

复制代码
std::stringstream ss;
ss << "第一个数据";
// 使用后重置
ss.str("");  // 清空内容
ss.clear();  // 清空状态

7.3 性能优化

  1. 减少格式操作:格式操作影响性能
  2. 缓冲区控制
复制代码

cpp

复制

复制代码
// 设置大缓冲区提高性能
char buffer[1024];
std::ofstream file("data.txt");
file.rdbuf()->pubsetbuf(buffer, sizeof(buffer));

总结学习路径

  1. 基础阶段​:

    • 掌握cin/cout基本使用
    • 理解流状态管理
    • 学习基本文件读写
  2. 中级阶段​:

    • 熟练使用字符串流进行数据解析
    • 掌握格式化控制
    • 理解二进制文件操作
  3. 高级阶段​:

    • 为自定义类添加IO支持
    • 构建文件数据库
    • 开发日志系统
    • 实现数据序列化/反序列化

通过系统学习和实践,您将能掌握强大的C++ IO编程能力,这是开发实际应用程序的重要基础技能。

相关推荐
KhalilRuan32 分钟前
浅谈——C++和C#差异
开发语言·c++·c#
遗憾随她而去.2 小时前
js面试题 高频(1-11题)
开发语言·前端·javascript
Dxy12393102165 小时前
Python观察者模式详解:从理论到实战
开发语言·python·观察者模式
不写八个7 小时前
GoLang教程005:switch分支
开发语言·后端·golang
ZeroToOneDev7 小时前
Java(LinkedList和ArrayList底层分析)
java·开发语言
宴之敖者、8 小时前
数组——初识数据结构
c语言·开发语言·数据结构·算法
青小莫8 小时前
c语言-数据结构-二叉树OJ
c语言·开发语言·数据结构·二叉树·力扣
典学长编程8 小时前
Java从入门到精通!第十一天(Java常见的数据结构)
java·开发语言·数据结构
后端小张9 小时前
智谱AI图生视频:从批处理到多线程优化
开发语言·人工智能·ai·langchain·音视频