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;
}
代码说明:
std::cout
用于标准输出std::cin
用于标准输入<<
操作符链式输出多个值std::fixed
和std::setprecision
控制小数位数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;
}
注意事项:
reinterpret_cast
用于原始字节转换- 二进制操作跳过格式化,直接处理原始数据
- 结构体必须是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 文件操作注意事项
- 打开检查:总是检查文件是否成功打开
cpp
复制
std::ifstream file("data.txt");
if (!file) {
// 错误处理
}
- 关闭文件:虽然析构函数会自动关闭,但显式关闭更好
cpp
复制
file.close();
- 文件位置:读写操作改变文件位置指针
cpp
复制
long pos = file.tellg(); // 获取当前位置
file.seekg(0); // 回到文件开头
7.2 字符串流陷阱
- 多次使用:重复使用时要重置状态
cpp
复制
std::stringstream ss;
ss << "第一个数据";
// 使用后重置
ss.str(""); // 清空内容
ss.clear(); // 清空状态
7.3 性能优化
- 减少格式操作:格式操作影响性能
- 缓冲区控制:
cpp
复制
// 设置大缓冲区提高性能
char buffer[1024];
std::ofstream file("data.txt");
file.rdbuf()->pubsetbuf(buffer, sizeof(buffer));
总结学习路径
-
基础阶段:
- 掌握cin/cout基本使用
- 理解流状态管理
- 学习基本文件读写
-
中级阶段:
- 熟练使用字符串流进行数据解析
- 掌握格式化控制
- 理解二进制文件操作
-
高级阶段:
- 为自定义类添加IO支持
- 构建文件数据库
- 开发日志系统
- 实现数据序列化/反序列化
通过系统学习和实践,您将能掌握强大的C++ IO编程能力,这是开发实际应用程序的重要基础技能。