17.1 C++ I/O 概述
17.1.1 流的概念
C++ 的 I/O 系统基于**流(Stream)**的概念:
输入流:数据源 ──→ 程序
键盘、文件、字符串 ──→ cin、ifstream、istringstream
输出流:程序 ──→ 数据目标
程序 ──→ cout、ofstream、ostringstream、屏幕、文件
I/O 类层次结构:
ios_base
└── ios
├── istream ──→ ifstream, istringstream
├── ostream ──→ ofstream, ostringstream
└── iostream ──→ fstream, stringstream
17.1.2 标准流对象
cpp
// standard_streams.cpp -- 标准流对象
#include <iostream>
int main()
{
// cin :标准输入流(键盘)
// cout :标准输出流(屏幕)
// cerr :标准错误流(屏幕,不缓冲)
// clog :标准日志流(屏幕,缓冲)
std::cout << "这是标准输出" << std::endl;
std::cerr << "这是错误输出(立即显示)" << std::endl;
std::clog << "这是日志输出(缓冲)" << std::endl;
// cout 和 cerr 的区别:
// cerr 不缓冲,立即输出,适合错误信息
// cout 有缓冲,适合正常输出
return 0;
}
17.2 cout 与输出格式化
17.2.1 基本输出
cpp
// cout_basic.cpp -- cout基本输出
#include <iostream>
int main()
{
using namespace std;
// 基本输出
cout << "字符串" << endl;
cout << 42 << endl;
cout << 3.14 << endl;
cout << true << endl; // 输出1
// 链式输出
cout << "a=" << 1 << " b=" << 2 << " c=" << 3 << endl;
// endl vs "\n"
cout << "endl:刷新缓冲区" << endl;
cout << "\\n:只换行不刷新" << "\n";
// 输出特殊字符
cout << "制表符:\t对齐" << endl;
cout << "反斜杠:\\" << endl;
cout << "引号:\"Hello\"" << endl;
return 0;
}
17.2.2 格式化输出(iomanip)
cpp
// format_output.cpp -- 格式化输出详解
#include <iostream>
#include <iomanip> // 格式化操纵符
int main()
{
using namespace std;
// ===== 进制输出 =====
cout << "===== 进制 =====" << endl;
int n = 255;
cout << "十进制:" << dec << n << endl; // 255
cout << "八进制:" << oct << n << endl; // 377
cout << "十六进制:" << hex << n << endl; // ff
cout << "十六进制大写:" << uppercase << hex << n << endl; // FF
cout << dec; // 恢复十进制
// 显示进制前缀
cout << showbase;
cout << "带前缀十进制:" << dec << n << endl; // 255
cout << "带前缀八进制:" << oct << n << endl; // 0377
cout << "带前缀十六进制:" << hex << n << endl; // 0xff
cout << noshowbase << dec;
// ===== 浮点数格式 =====
cout << "\n===== 浮点数 =====" << endl;
double pi = 3.14159265358979;
cout << "默认:" << pi << endl;
cout << fixed;
cout << "fixed:" << pi << endl;
cout << "fixed(2):" << setprecision(2) << pi << endl;
cout << "fixed(6):" << setprecision(6) << pi << endl;
cout << scientific;
cout << "scientific:" << pi << endl;
cout << defaultfloat; // 恢复默认
// ===== 宽度与对齐 =====
cout << "\n===== 宽度对齐 =====" << endl;
cout << setw(10) << "右对齐" << endl; // 默认右对齐
cout << left << setw(10) << "左对齐" << "|" << endl;
cout << right << setw(10) << "右对齐" << "|" << endl;
cout << internal << setw(10) << -42 << endl; // 符号左对齐,数字右对齐
// 填充字符
cout << setfill('*');
cout << setw(10) << "Hello" << endl; // *****Hello
cout << left << setw(10) << "Hello" << endl; // Hello*****
cout << setfill(' '); // 恢复空格填充
// ===== 综合格式化表格 =====
cout << "\n===== 格式化表格 =====" << endl;
cout << fixed << setprecision(2);
cout << left
<< setw(12) << "商品"
<< right
<< setw(8) << "数量"
<< setw(10) << "单价"
<< setw(10) << "总价" << endl;
cout << string(40, '-') << endl;
struct Item { string name; int qty; double price; };
Item items[] = {
{"苹果", 5, 3.50},
{"香蕉", 12, 1.80},
{"橙子", 8, 4.20}
};
double total = 0;
for (const auto& item : items)
{
double subtotal = item.qty * item.price;
total += subtotal;
cout << left << setw(12) << item.name
<< right << setw(8) << item.qty
<< setw(10) << item.price
<< setw(10) << subtotal << endl;
}
cout << string(40, '-') << endl;
cout << right << setw(30) << "合计:" << setw(10) << total << endl;
return 0;
}
输出:
===== 格式化表格 =====
商品 数量 单价 总价
----------------------------------------
苹果 5 3.50 17.50
香蕉 12 1.80 21.60
橙子 8 4.20 33.60
----------------------------------------
合计: 72.70
17.2.3 流状态标志
cpp
// stream_flags.cpp -- 流状态标志
#include <iostream>
#include <iomanip>
int main()
{
using namespace std;
// 使用 flags() 获取/设置所有标志
ios_base::fmtflags original = cout.flags(); // 保存原始标志
// 设置多个标志
cout.setf(ios_base::left | ios_base::showpos);
cout << setw(10) << 42 << endl; // +42(左对齐,显示正号)
// 恢复原始标志
cout.flags(original);
cout << setw(10) << 42 << endl; // 42(右对齐,不显示正号)
// 常用标志
cout.setf(ios_base::boolalpha);
cout << true << " " << false << endl; // true false
cout.unsetf(ios_base::boolalpha);
cout << true << " " << false << endl; // 1 0
// 精度设置
cout.precision(4);
cout << 3.14159 << endl; // 3.142
return 0;
}
17.3 cin 与输入处理
17.3.1 基本输入
cpp
// cin_basic.cpp -- cin基本输入
#include <iostream>
#include <string>
int main()
{
using namespace std;
// 读取基本类型
int i;
double d;
char c;
string s;
cout << "输入整数:"; cin >> i;
cout << "输入浮点数:"; cin >> d;
cin.ignore(); // 清除缓冲区中的换行符
cout << "输入字符:"; cin.get(c);
cin.ignore();
cout << "输入字符串(含空格):"; getline(cin, s);
cout << "\n你输入的:" << endl;
cout << "整数:" << i << endl;
cout << "浮点:" << d << endl;
cout << "字符:" << c << endl;
cout << "字符串:" << s << endl;
return 0;
}
17.3.2 cin 的成员函数
cpp
// cin_methods.cpp -- cin成员函数详解
#include <iostream>
#include <string>
int main()
{
using namespace std;
// ===== get() 系列 =====
cout << "===== get() =====" << endl;
// cin.get(ch):读取单个字符(含空白)
char ch;
cout << "输入一行文字:";
while (cin.get(ch) && ch != '\n')
cout << ch;
cout << endl;
// cin.get(buf, size):读取到换行符(不读取换行符)
char buf[80];
cout << "再输入一行:";
cin.get(buf, 80);
cout << "读取到:" << buf << endl;
cin.ignore(); // 清除换行符
// ===== getline() =====
cout << "\n===== getline() =====" << endl;
// cin.getline(buf, size):读取到换行符(读取并丢弃换行符)
cout << "输入一行:";
cin.getline(buf, 80);
cout << "读取到:" << buf << endl;
// std::getline(cin, string):读取到string
string line;
cout << "输入一行到string:";
getline(cin, line);
cout << "读取到:" << line << endl;
// ===== peek() 和 putback() =====
cout << "\n===== peek/putback =====" << endl;
cout << "输入内容:";
char first = cin.peek(); // 查看但不读取
cout << "第一个字符是:" << first << endl;
cin.get(ch); // 实际读取
cin.putback(ch); // 放回
cin.get(ch); // 再次读取
cout << "读取到:" << ch << endl;
// ===== ignore() =====
// cin.ignore(n, delim):忽略n个字符或直到delim
cin.ignore(1000, '\n'); // 清除当前行剩余内容
return 0;
}
17.3.3 流状态检测
cpp
// stream_state.cpp -- 流状态检测
#include <iostream>
#include <string>
int main()
{
using namespace std;
// 流状态标志:
// goodbit:无错误
// eofbit :到达文件末尾
// failbit:格式错误(如输入字母给int)
// badbit :严重错误(流损坏)
int sum = 0, count = 0;
int val;
cout << "输入整数(输入非数字结束):" << endl;
while (cin >> val) // 读取失败时返回false
{
sum += val;
count++;
}
// 检查流状态
if (cin.eof())
cout << "到达文件末尾" << endl;
else if (cin.fail())
cout << "输入格式错误" << endl;
else if (cin.bad())
cout << "流损坏" << endl;
// 清除错误状态(必须!否则无法继续读取)
cin.clear();
cin.ignore(1000, '\n'); // 清除错误输入
if (count > 0)
cout << "共" << count << "个数,总和=" << sum
<< " 平均=" << (double)sum/count << endl;
// 继续读取
string rest;
cout << "继续输入字符串:";
getline(cin, rest);
cout << "读取到:" << rest << endl;
return 0;
}
17.4 文件 I/O
17.4.1 写入文件(ofstream)
cpp
// file_write.cpp -- 文件写入详解
#include <iostream>
#include <fstream>
#include <string>
#include <iomanip>
int main()
{
using namespace std;
// ===== 基本写入 =====
ofstream outFile("output.txt");
if (!outFile) // 检查是否成功打开
{
cerr << "无法创建文件!" << endl;
return 1;
}
outFile << "第一行文字" << endl;
outFile << "整数:" << 42 << endl;
outFile << "浮点:" << fixed << setprecision(2) << 3.14 << endl;
outFile.close();
cout << "写入完成:output.txt" << endl;
// ===== 追加模式 =====
ofstream appendFile("output.txt", ios::app);
appendFile << "追加的内容" << endl;
appendFile.close();
cout << "追加完成" << endl;
// ===== 二进制写入 =====
ofstream binFile("data.bin", ios::binary);
int iVal = 12345;
double dVal = 3.14159;
binFile.write(reinterpret_cast<char*>(&iVal), sizeof(int));
binFile.write(reinterpret_cast<char*>(&dVal), sizeof(double));
binFile.close();
cout << "二进制写入完成:data.bin" << endl;
// ===== 写入结构体数组 =====
struct Student {
char name[20];
int age;
double score;
};
ofstream stuFile("students.bin", ios::binary);
Student students[] = {
{"张三", 20, 92.5},
{"李四", 21, 88.0},
{"王五", 19, 95.5}
};
for (const auto& s : students)
stuFile.write(reinterpret_cast<const char*>(&s), sizeof(Student));
stuFile.close();
cout << "结构体写入完成:students.bin" << endl;
return 0;
}
17.4.2 读取文件(ifstream)
cpp
// file_read.cpp -- 文件读取详解
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
int main()
{
using namespace std;
// ===== 逐行读取 =====
cout << "===== 逐行读取 =====" << endl;
ifstream inFile("output.txt");
if (!inFile.is_open())
{
cerr << "无法打开文件!" << endl;
return 1;
}
string line;
int lineNum = 0;
while (getline(inFile, line))
{
lineNum++;
cout << lineNum << ": " << line << endl;
}
inFile.close();
// ===== 逐词读取 =====
cout << "\n===== 逐词读取 =====" << endl;
ifstream wordFile("output.txt");
string word;
int wordCount = 0;
while (wordFile >> word)
wordCount++;
cout << "单词数:" << wordCount << endl;
wordFile.close();
// ===== 读取格式化数据 =====
cout << "\n===== 格式化读取 =====" << endl;
// 假设文件格式:姓名 年龄 成绩
ofstream tempFile("temp.txt");
tempFile << "张三 20 92.5\n李四 21 88.0\n王五 19 95.5\n";
tempFile.close();
ifstream dataFile("temp.txt");
string name;
int age;
double score;
while (dataFile >> name >> age >> score)
{
cout << "姓名:" << name
<< " 年龄:" << age
<< " 成绩:" << score << endl;
}
dataFile.close();
// ===== 二进制读取 =====
cout << "\n===== 二进制读取 =====" << endl;
ifstream binFile("data.bin", ios::binary);
int iVal;
double dVal;
binFile.read(reinterpret_cast<char*>(&iVal), sizeof(int));
binFile.read(reinterpret_cast<char*>(&dVal), sizeof(double));
cout << "读取整数:" << iVal << endl;
cout << "读取浮点:" << dVal << endl;
binFile.close();
return 0;
}
17.4.3 文件打开模式
cpp
// file_modes.cpp -- 文件打开模式
#include <iostream>
#include <fstream>
int main()
{
using namespace std;
/*
文件打开模式(可以用 | 组合):
ios::in 读取
ios::out 写入(默认截断)
ios::app 追加(写入到末尾)
ios::ate 打开后定位到末尾
ios::trunc 截断(清空文件)
ios::binary 二进制模式
*/
// 只读
ifstream f1("file.txt", ios::in);
// 只写(截断)
ofstream f2("file.txt", ios::out);
// 追加
ofstream f3("file.txt", ios::out | ios::app);
// 读写(不截断)
fstream f4("file.txt", ios::in | ios::out);
// 读写(截断)
fstream f5("file.txt", ios::in | ios::out | ios::trunc);
// 二进制读写
fstream f6("file.bin", ios::in | ios::out | ios::binary);
// 演示:追加模式
ofstream log("log.txt", ios::app);
log << "日志条目1" << endl;
log << "日志条目2" << endl;
log.close();
// 读取验证
ifstream readLog("log.txt");
string line;
cout << "日志内容:" << endl;
while (getline(readLog, line))
cout << " " << line << endl;
return 0;
}
17.4.4 随机访问文件
cpp
// random_access.cpp -- 文件随机访问
#include <iostream>
#include <fstream>
#include <string>
struct Record
{
int id;
char name[20];
double score;
};
int main()
{
using namespace std;
const string filename = "records.bin";
// ===== 写入记录 =====
{
ofstream out(filename, ios::binary);
Record records[] = {
{1, "张三", 92.5},
{2, "李四", 88.0},
{3, "王五", 95.5},
{4, "赵六", 78.0},
{5, "钱七", 85.5}
};
for (const auto& r : records)
out.write(reinterpret_cast<const char*>(&r), sizeof(Record));
}
// ===== 随机读取第3条记录 =====
{
ifstream in(filename, ios::binary);
// seekg:设置读取位置
// tellg:获取当前读取位置
int recordIndex = 2; // 第3条(从0开始)
in.seekg(recordIndex * sizeof(Record), ios::beg);
Record r;
in.read(reinterpret_cast<char*>(&r), sizeof(Record));
cout << "第3条记录:ID=" << r.id
<< " 姓名=" << r.name
<< " 成绩=" << r.score << endl;
}
// ===== 随机修改第2条记录 =====
{
fstream file(filename, ios::in | ios::out | ios::binary);
int recordIndex = 1; // 第2条
file.seekp(recordIndex * sizeof(Record), ios::beg);
Record newRecord = {2, "李四(修改)", 99.0};
file.write(reinterpret_cast<const char*>(&newRecord),
sizeof(Record));
}
// ===== 读取所有记录 =====
{
ifstream in(filename, ios::binary);
Record r;
cout << "\n所有记录:" << endl;
while (in.read(reinterpret_cast<char*>(&r), sizeof(Record)))
{
cout << "ID=" << r.id
<< " 姓名=" << r.name
<< " 成绩=" << r.score << endl;
}
}
return 0;
}
💡 seekg vs seekp:
seekg:设置读取位置(get position)seekp:设置写入位置(put position)- 第二个参数:
ios::beg(从头)、ios::cur(当前)、ios::end(从尾)
17.5 字符串流(sstream)
cpp
// stringstream_demo.cpp -- 字符串流
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
int main()
{
using namespace std;
// ===== ostringstream:写入字符串 =====
cout << "===== ostringstream =====" << endl;
ostringstream oss;
oss << "姓名:" << "张三" << endl;
oss << "年龄:" << 25 << endl;
oss << "成绩:" << fixed << 92.5 << endl;
string result = oss.str(); // 获取字符串
cout << result;
// 用于格式化字符串
ostringstream fmt;
fmt << "第" << 3 << "页,共" << 10 << "页";
cout << fmt.str() << endl;
// ===== istringstream:从字符串读取 =====
cout << "\n===== istringstream =====" << endl;
string data = "张三 20 92.5";
istringstream iss(data);
string name;
int age;
double score;
iss >> name >> age >> score;
cout << "姓名:" << name << " 年龄:" << age
<< " 成绩:" << score << endl;
// 解析CSV行
cout << "\n===== 解析CSV =====" << endl;
string csvLine = "苹果,3.50,100,水果";
istringstream csvStream(csvLine);
string field;
vector<string> fields;
while (getline(csvStream, field, ',')) // 以逗号分隔
fields.push_back(field);
for (size_t i = 0; i < fields.size(); i++)
cout << "字段" << i << ":" << fields[i] << endl;
// ===== stringstream:读写 =====
cout << "\n===== stringstream =====" << endl;
stringstream ss;
ss << 42 << " " << 3.14 << " " << "Hello";
int i; double d; string s;
ss >> i >> d >> s;
cout << "读取:" << i << " " << d << " " << s << endl;
// 类型转换(string ↔ 数值)
auto toString = [](double val) -> string {
ostringstream os;
os << val;
return os.str();
};
auto toDouble = [](const string& s) -> double {
istringstream is(s);
double val;
is >> val;
return val;
};
cout << "3.14 → string:\"" << toString(3.14) << "\"" << endl;
cout << "\"2.71\" → double:" << toDouble("2.71") << endl;
return 0;
}
17.6 综合示例:CSV 文件处理系统
cpp
// csv_system.cpp -- 综合示例:CSV文件处理
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <iomanip>
#include <numeric>
struct SalesRecord
{
std::string date;
std::string product;
std::string region;
int quantity;
double price;
double total() const { return quantity * price; }
};
class CSVProcessor
{
private:
std::vector<SalesRecord> records;
std::string filename;
// 解析CSV行
std::vector<std::string> parseLine(const std::string& line)
{
std::vector<std::string> fields;
std::istringstream ss(line);
std::string field;
while (std::getline(ss, field, ','))
fields.push_back(field);
return fields;
}
public:
CSVProcessor(const std::string& file) : filename(file) {}
// 写入CSV文件
void writeCSV()
{
std::ofstream out(filename);
if (!out)
{
std::cerr << "无法创建文件:" << filename << std::endl;
return;
}
// 写入标题行
out << "日期,产品,地区,数量,单价" << std::endl;
// 写入数据
std::vector<SalesRecord> data = {
{"2024-01-15", "苹果", "北京", 100, 3.50},
{"2024-01-15", "香蕉", "上海", 200, 1.80},
{"2024-01-16", "苹果", "上海", 150, 3.50},
{"2024-01-16", "橙子", "北京", 80, 4.20},
{"2024-01-17", "香蕉", "北京", 120, 1.80},
{"2024-01-17", "苹果", "广州", 200, 3.50},
{"2024-01-18", "橙子", "上海", 90, 4.20},
{"2024-01-18", "香蕉", "广州", 160, 1.80}
};
for (const auto& r : data)
out << r.date << "," << r.product << ","
<< r.region << "," << r.quantity << ","
<< r.price << std::endl;
std::cout << "CSV文件已创建:" << filename << std::endl;
}
// 读取CSV文件
bool readCSV()
{
std::ifstream in(filename);
if (!in)
{
std::cerr << "无法打开文件:" << filename << std::endl;
return false;
}
records.clear();
std::string line;
bool firstLine = true;
while (std::getline(in, line))
{
if (firstLine) { firstLine = false; continue; } // 跳过标题
if (line.empty()) continue;
auto fields = parseLine(line);
if (fields.size() >= 5)
{
SalesRecord r;
r.date = fields[0];
r.product = fields[1];
r.region = fields[2];
r.quantity = std::stoi(fields[3]);
r.price = std::stod(fields[4]);
records.push_back(r);
}
}
std::cout << "读取 " << records.size() << " 条记录" << std::endl;
return true;
}
// 打印所有记录
void printAll() const
{
using namespace std;
cout << "\n" << string(60, '=') << endl;
cout << left
<< setw(12) << "日期"
<< setw(8) << "产品"
<< setw(8) << "地区"
<< right
<< setw(6) << "数量"
<< setw(8) << "单价"
<< setw(10) << "小计" << endl;
cout << string(60, '-') << endl;
for (const auto& r : records)
{
cout << left
<< setw(12) << r.date
<< setw(8) << r.product
<< setw(8) << r.region
<< right
<< setw(6) << r.quantity
<< setw(8) << fixed << setprecision(2) << r.price
<< setw(10) << r.total() << endl;
}
cout << string(60, '=') << endl;
}
// 按产品统计
void analyzeByProduct() const
{
using namespace std;
map<string, pair<int, double>> stats; // 产品→{总数量, 总金额}
for (const auto& r : records)
{
stats[r.product].first += r.quantity;
stats[r.product].second += r.total();
}
cout << "\n===== 按产品统计 =====" << endl;
cout << left << setw(10) << "产品"
<< right << setw(8) << "总数量"
<< setw(12) << "总金额" << endl;
cout << string(30, '-') << endl;
for (const auto& [product, data] : stats)
cout << left << setw(10) << product
<< right << setw(8) << data.first
<< setw(12) << fixed << setprecision(2) << data.second
<< endl;
}
// 按地区统计
void analyzeByRegion() const
{
using namespace std;
map<string, double> regionSales;
for (const auto& r : records)
regionSales[r.region] += r.total();
// 按销售额排序
vector<pair<string, double>> sorted(regionSales.begin(),
regionSales.end());
sort(sorted.begin(), sorted.end(),
[](const auto& a, const auto& b) { return a.second > b.second; });
cout << "\n===== 按地区统计(降序)=====" << endl;
for (const auto& [region, sales] : sorted)
cout << region << ":" << fixed << setprecision(2)
<< sales << " 元" << endl;
}
// 总销售额
double totalSales() const
{
return std::accumulate(records.begin(), records.end(), 0.0,
[](double sum, const SalesRecord& r) { return sum + r.total(); });
}
};
int main()
{
CSVProcessor processor("sales.csv");
// 写入数据
processor.writeCSV();
// 读取数据
processor.readCSV();
// 打印所有记录
processor.printAll();
// 分析
processor.analyzeByProduct();
processor.analyzeByRegion();
std::cout << "\n总销售额:" << std::fixed << std::setprecision(2)
<< processor.totalSales() << " 元" << std::endl;
return 0;
}
📝 第17章知识点总结
| 知识点 | 核心要点 |
|---|---|
| 流的概念 | 数据在程序与外部之间的流动,输入流/输出流 |
| 标准流 | cin(输入)、cout(输出)、cerr(错误,不缓冲)、clog(日志,缓冲) |
| 格式化输出 | setw(宽度)、setprecision(精度)、fixed/scientific、left/right、setfill |
| 进制输出 | dec/oct/hex、showbase显示前缀、uppercase |
| cin 读取 | >>跳过空白、get()读取含空白、getline()读取整行 |
| 流状态 | good()/eof()/fail()/bad(),clear()清除错误状态 |
| ofstream | 写入文件,ios::app追加,ios::binary二进制 |
| ifstream | 读取文件,getline逐行,>>逐词,read二进制 |
| 文件模式 | in/out/app/ate/trunc/binary,可用` |
| 随机访问 | seekg/seekp定位,tellg/tellp获取位置,ios::beg/cur/end |
| ostringstream | 写入字符串,str()获取结果,用于格式化字符串 |
| istringstream | 从字符串读取,用于解析CSV等格式化文本 |
| fstream | 同时支持读写,需指定打开模式 |
| 二进制I/O | write()/read(),reinterpret_cast<char*>转换指针 |