实验6 文件I/O与异常处理
##任务1
###1.源代码
#pragma once #include <iomanip> #include <iostream> #include <string> struct Contestant { long id; // 学号 std::string name; // 姓名 std::string major; // 专业 int solved; // 解题数 int penalty; // 总罚时 }; // 重载<< // 要求:姓名/专业里不含空白符 inline std::ostream& operator<<(std::ostream& out, const Contestant& c) { out << std::left; out << std::setw(15) << c.id << std::setw(15) << c.name << std::setw(15) << c.major << std::setw(10) << c.solved << std::setw(10) << c.penalty; return out; } // 重载>> inline std::istream& operator>>(std::istream& in, Contestant& c) { in >> c.id >> c.name >> c.major >> c.solved >> c.penalty; return in; }
#pragma once #include <fstream> #include <iostream> #include <stdexcept> #include <string> #include <vector> #include "contestant.hpp" // ACM 排序规则:先按解题数降序,再按罚时升序 inline bool cmp_by_solve(const Contestant& a, const Contestant& b) { if(a.solved != b.solved) return a.solved > b.solved; return a.penalty < b.penalty; } // 将结果写至任意输出流 inline void write(std::ostream& os, const std::vector<Contestant>& v) { for (const auto& x : v) os << x << '\n'; } // 将结果打印到屏幕 inline void print(const std::vector<Contestant>& v) { write(std::cout, v); } // 将结果保存到文件 inline void save(const std::string& filename, const std::vector<Contestant>& v) { std::ofstream os(filename); if (!os) throw std::runtime_error("fail to open " + filename); write(os, v); } // 从文件读取信息(跳过标题行) inline std::vector<Contestant> load(const std::string& filename) { std::ifstream is(filename); if (!is) throw std::runtime_error("fail to open " + filename); std::string line; std::getline(is, line); // 跳过标题 std::vector<Contestant> v; Contestant t; int seq; while (is >> seq >> t) v.push_back(t); return v; }
#include <algorithm> #include <iostream> #include <stdexcept> #include <vector> #include "contestant.hpp" #include "utils.hpp" const std::string in_file = "./data.txt"; const std::string out_file = "./ans.txt"; void app() { std::vector<Contestant> contestants; try { contestants = load(in_file); std::sort(contestants.begin(), contestants.end(), cmp_by_solve); print(contestants); save(out_file, contestants); } catch (const std::exception& e) { std::cerr << e.what() << '\n'; return; } } int main() { app(); }
序号 学号 姓名 专业 解题数(道) 总罚时(分钟) 1 204942005 Jeny 未来专业1 4 210 2 204942302 Alex 未来专业2 3 180 3 204942059 Bob 未来专业2 5 350 4 204942111 Hellen 未来专业3 2 90 5 204942017 chappie 未来专业4 4 260 6 204942075 Shaw 未来专业5 3 150 7 204942076 Thomas 未来专业6 6 420 8 204942078 Jennie 未来专业7 1 30 9 204942079 Tibby 未来专业7 3 200 10 204942080 Vermont 未来专业8 5 310
序号 学号 姓名 专业 解题数(道) 总罚时(分钟) 1 204942005 Jeny 未来专业1 4 210 2 204942302 Alex 未来专业2 3 180 3 204942059 Bob 未来专业2 5 350 4 204942111 Hellen 未来专业3 2 90 5 204942017 chappie 未来专业4 4 260 6 204942075 Shaw 未来专业5 3 150 7 204942076 Thomas 未来专业6 8 204942078 Jennie 未来专业7 1 30 9 204942079 Tibby 未来专业7 3 200 10 204942080 Vermont 未来专业8 5 310
###2.运行测试截图

###3.回答问题
问题1:流操作与代码复用
观察 print() 与 save() 的实现,均在内部调用 write() :
(1) write() 的参数类型是 std::ostream& ,它为什么能同时接受 std::cout 和 std::ofstream 对象作
为实参?
答:因为std::cout是std::ofstream基类的派生类,可以通过左值引用,使基类绑定到派生类对象
(2)如果要把结果写到其他设备,只要该设备也提供 std::ostream 接口,还需改动 write() 吗?
答:不需要
问题2:异常处理与捕获
在代码中找到两处 throw 语句,说明:
(1)什么情况下会抛出异常;
答:// 将结果保存到文件
std::ofstream os(filename);
if (!os)
throw std::runtime_error("fail to open " + filename);
// 从文件读取信息(跳过标题行)
std::ifstream is(filename);
if (!is)
throw std::runtime_error("fail to open " + filename);
当尝试将流对象和具体文件进行关联,但是关联失败时,抛出异常
(2)异常被谁捕获、做了哪些处理。
答:task.cpp文件的app函数中有如下代码,可能发生异常的代码块被放入try{}中,load和save函数一旦抛出异常
就会被catch捕获并执行相关操作
try {
contestants = load(in_file);
std::sort(contestants.begin(), contestants.end(), cmp_by_solve);
print(contestants);
save(out_file, contestants);
} catch (const std::exception& e) {
std::cerr << e.what() << '\n';
return;
}
问题3:替代写法
函数 app 中 std::sort(contestants.begin(), contestants.end(), cmp_by_solve); 参数
cmp_by_solve 换成下面的lambda表达式是否可以?功能、性能、结果是否一致?
[](const Contestant& a, const Contestant& b) {
return a.solved != b.solved ? a.solved > b.solved
: a.penalty < b.penalty;}
答:可以,功能、结果、性能都一致
问题4:数据完整性与代码健壮性
把 in_file 改成 "./data_bad.txt" (内含空白行或字段缺失),重新编译运行:
(1)观察运行结果有什么问题?给出测试截图并分析原因。
答:编译没报错,但是运行时发现少了一个人,并且第一个人的总罚时变成了缺少的人的学号,不符合预期
原因:data_bad数据文件存在异常:空出来一行没有数据,但是代码中并没有做相应的异常处理把该文件当正常文件处理

(2)思考:如何修改函数 std::vector<Contestant> load(const std::string& filename) ,使其提示出
错行号并跳过有问题的数据行,同时让程序继续执行?(选答*)
答:将load函数中如下代码段
Contestant t;
int seq;
while (is >> seq >> t)
v.push_back(t);
改成
std::vector<Contestant> v;
int line_num = 1;
while (std::getline(is, line)) {
line_num++;
// 跳过空白行
if (line.empty()) continue;
std::istringstream iss(line);
Contestant t;
int seq;
// 尝试读取序号和选手信息,同时校验字段完整性
if (!(iss >> seq >> t.id >> t.name >> t.major >> t.solved >> t.penalty)) {
std::cerr << "[Warning] line " << line_num << " format error, skipped: " << line << std::endl;
continue; // 跳过格式错误的行
}
// 校验数据有效性
if (t.solved < 0 || t.penalty < 0) {
std::cerr << "[Warning] line " << line_num << " invalid data, skipped: " << line << std::endl;
continue;
}
// 所有校验通过,加入容器
v.push_back(t);
}
##任务2
###1.源代码
学号 姓名 专业 成绩 1001 抖森 Acting 80 1002 宝爷 Music 97 1003 大眼仔 Acting 75 1004 本喵 Acting 99 1005 裘花 Acting 89 1006 小李子 Acting 92 1007 无脸男 Acting 85 1008 甜茶 Acting 91 1009 囧瑟夫 Acting 88 1010 霉霉 Music 96
学号 姓名 专业 成绩 1001 抖森 Acting 80 1002 宝爷 Music 97 1003 大眼仔 Acting 1004 本喵 Acting 99 1005 裘花 Acting 89 1006 小李子 Acting 92 1007 无脸男 Acting 105 1008 甜茶 Acting 91 1009 囧瑟夫 Acting 88 1010 霉霉 Music 96
#pragma once #include <algorithm> #include <iostream> #include <string> #include <iomanip> class Student { public: Student() = default; ~Student() = default; const std::string get_major() const{return major;} int get_grade() const{return grade;} friend std::ostream& operator<<(std::ostream& os, const Student& s); friend std::istream& operator>>(std::istream& is, Student& s); private: int id; std::string name; std::string major; int grade; // 0-100 };
#include <iostream> #include <iomanip> #include <stdexcept> #include "student.hpp" std::istream& operator>>(std::istream& is, Student& s) { std::ios::iostate state = is.rdstate(); is >> s.id >> s.name >> s.major >> s.grade; // 校验1:是否4个字段都读取成功(无缺失) if (!is) { is.clear(state); throw std::runtime_error("字段缺失(需学号、姓名、专业、成绩)"); } // 校验2:成绩是否在合法范围(0-100) if (s.grade < 0 || s.grade > 100) { throw std::runtime_error("成绩超出范围(0-100):" + std::to_string(s.grade)); } return is; } std::ostream& operator<<(std::ostream& os, const Student& s) { os << std::left << std::setw(15) << s.id << std::setw(15) << s.name << std::setw(15) << s.major << std::setw(15) << s.grade; return os; }
#pragma once #include <string> #include <vector> #include <iostream> #include <iomanip> #include "student.hpp" class StuMgr { public: void load(const std::string& file); // 加载数据文件(空格分隔) void sort(); // 排序: 按专业字典序升序、同专业分数降序 void print() const; // 打印到屏幕 void save(const std::string& file) const; // 保存到文件 private: void write(std::ostream &os) const; // 把数据写到任意输出流 private: std::vector<Student> students; };
#include "stumgr.hpp" #include <fstream> #include <algorithm> #include <iomanip> #include <stdexcept> #include <string> #include <sstream> void StuMgr::load(const std::string& file) { std::ifstream is(file); if (!is.is_open()) { throw std::runtime_error("cannot open file: " + file); } std::string line; int line_num = 0; // 跳过标题行 if (std::getline(is, line)) { line_num++; } else { throw std::runtime_error("文件为空:" + file); } students.clear(); Student temp; while (true) { line_num++; // 先读取行,再用istringstream解析(避免空格/换行影响) if (!std::getline(is, line)) { break; } std::istringstream iss(line); // 将行内容转为输入流 try { iss >> temp; students.push_back(temp); } catch (const std::exception& e) { // 提示错误行号,跳过无效行 std::cerr << "[Warning] line " << line_num << " format error, skipped: " << e.what() << " (内容:" << line << ")\n"; } } is.close(); if (students.empty()) { throw std::runtime_error("文件无有效数据:" + file); } } void StuMgr::sort() { std::sort(students.begin(), students.end(), [](const Student& a, const Student& b) { if (a.get_major() != b.get_major()) { return a.get_major() < b.get_major(); } return a.get_grade() > b.get_grade(); } ); } void StuMgr::write(std::ostream& os) const { os << std::left; os << std::setw(10) << "学号" << std::setw(15) << "姓名" << std::setw(15) << "专业" << std::setw(5) << "成绩" << '\n'; for (const auto& stu : students) { os << stu << '\n'; } } void StuMgr::print() const { write(std::cout); } void StuMgr::save(const std::string& file) const { std::ofstream os(file); if (!os.is_open()) { throw std::runtime_error("cannot open file for writing: " + file); } write(os); os.close(); }
#include <iostream> #include <limits> #include <string> #include "stumgr.hpp" //此处使用方法2,直接将项目创建在数据文件中,不用把数据文件改成绝对路径 const std::string in_file = "./data.txt"; const std::string out_file = "./ans.txt"; void menu() { std::cout << "\n**********简易应用**********\n" "1. 加载文件\n" "2. 排序\n" "3. 打印到屏幕\n" "4. 保存到文件\n" "5. 退出\n" "请选择:"; } void app() { StuMgr mgr; while(true) { menu(); int choice; std::cin >> choice; try { switch (choice) { case 1: mgr.load(in_file); std::cout << "加载成功\n"; break; case 2: mgr.sort(); std::cout << "排序已完成\n"; break; case 3: mgr.print(); std::cout << "打印已完成\n"; break; case 4: mgr.save(out_file); std::cout << "导出成功\n"; break; case 5: return; default: std::cout << "不合法输入\n"; } } catch (const std::exception& e) { std::cout << "Error: " << e.what() << '\n'; } } } int main() { app(); }
###2.运行测试截图
必做内容:


选做内容:



浙公网安备 33010602011771号