实验6 文件I/O与异常处理
实验任务1
源代码
contestant.hpp
1 #pragma once 2 #include <iomanip> 3 #include <iostream> 4 #include <string> 5 6 struct Contestant { 7 long id; // 学号 8 std::string name; // 姓名 9 std::string major; // 专业 10 int solved; // 解题数 11 int penalty; // 总罚时 12 }; 13 14 // 重载<< 15 // 要求:姓名/专业里不含空白符 16 inline std::ostream& operator<<(std::ostream& out, const Contestant& c) { 17 out << std::left; 18 out << std::setw(15) << c.id 19 << std::setw(15) << c.name 20 << std::setw(15) << c.major 21 << std::setw(10) << c.solved 22 << std::setw(10) << c.penalty; 23 24 return out; 25 } 26 27 // 重载>> 28 inline std::istream& operator>>(std::istream& in, Contestant& c) { 29 in >> c.id >> c.name >> c.major >> c.solved >> c.penalty; 30 31 return in; 32 }
utils.hpp
1 #pragma once 2 #include <fstream> 3 #include <iostream> 4 #include <stdexcept> 5 #include <string> 6 #include <vector> 7 #include "contestant.hpp" 8 9 // ACM 排序规则:先按解题数降序,再按罚时升序 10 inline bool cmp_by_solve(const Contestant& a, const Contestant& b) { 11 if(a.solved != b.solved) 12 return a.solved > b.solved; 13 14 return a.penalty < b.penalty; 15 } 16 17 // 将结果写至任意输出流 18 inline void write(std::ostream& os, const std::vector<Contestant>& v) { 19 for (const auto& x : v) 20 os << x << '\n'; 21 } 22 23 // 将结果打印到屏幕 24 inline void print(const std::vector<Contestant>& v) { 25 write(std::cout, v); 26 } 27 28 // 将结果保存到文件 29 inline void save(const std::string& filename, const std::vector<Contestant>& v) { 30 std::ofstream os(filename); 31 if (!os) 32 throw std::runtime_error("fail to open " + filename); 33 34 write(os, v); 35 } 36 37 // 从文件读取信息(跳过标题行) 38 inline std::vector<Contestant> load(const std::string& filename) { 39 std::ifstream is(filename); 40 if (!is) 41 throw std::runtime_error("fail to open " + filename); 42 43 std::string line; 44 std::getline(is, line); // 跳过标题 45 46 std::vector<Contestant> v; 47 Contestant t; 48 int seq; 49 while (is >> seq >> t) 50 v.push_back(t); 51 52 return v; 53 }
task1.cpp
1 #include <algorithm> 2 #include <iostream> 3 #include <stdexcept> 4 #include <vector> 5 #include "contestant.hpp" 6 #include "utils.hpp" 7 8 const std::string in_file = "./data.txt"; 9 const std::string out_file = "./ans.txt"; 10 11 void app() { 12 std::vector<Contestant> contestants; 13 14 try { 15 contestants = load(in_file); 16 std::sort(contestants.begin(), contestants.end(), cmp_by_solve); 17 print(contestants); 18 save(out_file, contestants); 19 } catch (const std::exception& e) { 20 std::cerr << e.what() << '\n'; 21 return; 22 } 23 } 24 25 int main() { 26 app(); 27 }
运行结果


问题1:流操作与代码复用
(1)因为 std::cout 和 std::ofstream 都是 std::ostream 的派生类,由于派生类对象可以隐式转换为基类引用,因此 write() 函数可以接受任何 std::ostream 派生类的对象作为参数
(2)不需要改动 write() 函数。因为 write() 函数接受的是 std::ostream& 类型的参数,只要新的输出设备实现了 std::ostream 接口,就可以直接作为参数传入 write()
问题2:异常处理与捕获
(1)utils.hpp 中 save() 函数:当无法打开输出文件时会抛出异常
utils.hpp 中 load() 函数:当无法打开输入文件时会抛出异常
(2)在 task1.cpp 的 app() 函数中,通过 try-catch 块捕获异常,捕获后将异常信息输出到标准错误流并直接返回结束 app() 函数的执行
问题3:替代写法
可以,功能、性能、结果都一致
问题4:数据完整性与代码健壮性
(1)问题:使用 data_bad.txt 运行会出现部分数据读取后程序异常终止,导致输出不完整且缺失多行有效数据的问题。
原因:流读取遇到格式错误的数据行后进入失败状态,导致后续所有数据都无法读取,程序只能输出已读取的部分结果。
(2)需要添加的头文件:
1 #include <sstream> 2 #include <cctype>
修改 load() 函数:
1 inline std::vector<Contestant> load(const std::string& filename) { 2 std::ifstream is(filename); 3 if (!is) 4 throw std::runtime_error("fail to open " + filename); 5 6 std::string line; 7 std::getline(is, line); // 跳过标题行 8 9 std::vector<Contestant> v; 10 int line_num = 2; // 从第2行开始(标题行是第1行) 11 12 while (std::getline(is, line)) { 13 line_num++; 14 15 // 跳过空行 16 if (line.empty() || std::all_of(line.begin(), line.end(), ::isspace)) { 17 continue; 18 } 19 20 std::istringstream iss(line); 21 Contestant t; 22 int seq; 23 24 // 尝试读取数据 25 if (iss >> seq >> t.id >> t.name >> t.major >> t.solved >> t.penalty) { 26 v.push_back(t); 27 } else { 28 std::cerr << "Warning: Skipping malformed data at line " << line_num 29 << ": " << line << std::endl; 30 } 31 } 32 33 return v; 34 }
实验任务2
源代码
student.hpp
1 #pragma once 2 3 #include <iostream> 4 #include <string> 5 6 class Student { 7 public: 8 Student() = default; 9 ~Student() = default; 10 11 const std::string get_major() const; 12 int get_grade() const; 13 14 friend std::ostream& operator<<(std::ostream& os, const Student& s); 15 friend std::istream& operator>>(std::istream& is, Student& s); 16 17 private: 18 int id; 19 std::string name; 20 std::string major; 21 int grade; // 0-100 22 };
student.cpp
1 #include "student.hpp" 2 #include <stdexcept> 3 4 const std::string Student::get_major() const { 5 return major; 6 } 7 8 int Student::get_grade() const { 9 return grade; 10 } 11 12 std::ostream& operator<<(std::ostream& os, const Student& s) { 13 os << s.id << "\t" << s.name << "\t" << s.major << "\t" << s.grade; 14 return os; 15 } 16 17 std::istream& operator>>(std::istream& is, Student& s) { 18 if (!(is >> s.id >> s.name >> s.major >> s.grade)) { 19 throw std::runtime_error("Failed to read student data from stream"); 20 } 21 22 // 数据完整性检查 23 if (s.id <= 0) { 24 throw std::runtime_error("Invalid student ID"); 25 } 26 27 if (s.name.empty()) { 28 throw std::runtime_error("Student name cannot be empty"); 29 } 30 31 if (s.major.empty()) { 32 throw std::runtime_error("Major cannot be empty"); 33 } 34 35 if (s.grade < 0 || s.grade > 100) { 36 throw std::runtime_error("Grade must be between 0 and 100"); 37 } 38 39 return is; 40 }
stumgr.hpp
1 #pragma once 2 #include <string> 3 #include <vector> 4 #include "student.hpp" 5 6 class StuMgr { 7 public: 8 void load(const std::string& file); // 加载数据文件(空格分隔) 9 void sort(); // 排序: 按专业字典序升序、同专业分数降序 10 void print() const; // 打印到屏幕 11 void save(const std::string& file) const; // 保存到文件 12 13 private: 14 void write(std::ostream &os) const; // 把数据写到任意输出流 15 16 private: 17 std::vector<Student> students; 18 };
stumgr.cpp
1 #include "stumgr.hpp" 2 #include <fstream> 3 #include <algorithm> 4 #include <stdexcept> 5 6 void StuMgr::load(const std::string& file) { 7 std::ifstream in(file); 8 if (!in.is_open()) { 9 throw std::runtime_error("cannot open file: " + file); 10 } 11 12 students.clear(); 13 14 // 跳过表头行 15 std::string header; 16 std::getline(in, header); 17 std::getline(in, header); // 跳过分隔线 18 19 Student stu; 20 while (in >> stu) { 21 students.push_back(stu); 22 } 23 24 if (!in.eof() && in.fail()) { 25 throw std::runtime_error("Error reading data from file"); 26 } 27 } 28 29 void StuMgr::sort() { 30 std::sort(students.begin(), students.end(), 31 [](const Student& a, const Student& b) { 32 // 先按专业字典序升序 33 if (a.get_major() != b.get_major()) { 34 return a.get_major() < b.get_major(); 35 } 36 // 同专业按成绩降序 37 return b.get_grade() < a.get_grade(); 38 }); 39 } 40 41 void StuMgr::print() const { 42 if (students.empty()) { 43 std::cout << "(empty)\n"; 44 return; 45 } 46 47 for (const auto& stu : students) { 48 std::cout << stu << "\n"; 49 } 50 } 51 52 void StuMgr::save(const std::string& file) const { 53 std::ofstream out(file); 54 if (!out.is_open()) { 55 throw std::runtime_error("cannot open file: " + file); 56 } 57 58 // 写入表头 59 out << "学号\t姓名\t专业\t成绩\n"; 60 out << "----------------------------------------\n"; 61 62 // 写入数据 63 write(out); 64 } 65 66 void StuMgr::write(std::ostream& os) const { 67 for (const auto& stu : students) { 68 os << stu << "\n"; 69 } 70 }
task2.cpp
1 #include <iostream> 2 #include <limits> 3 #include <string> 4 #include "stumgr.hpp" 5 6 const std::string in_file = "./data.txt"; 7 const std::string out_file = "./ans.txt"; 8 9 void menu() { 10 std::cout << "\n**********简易应用**********\n" 11 "1. 加载文件\n" 12 "2. 排序\n" 13 "3. 打印到屏幕\n" 14 "4. 保存到文件\n" 15 "5. 退出\n" 16 "请选择:"; 17 } 18 19 void app() { 20 StuMgr mgr; 21 22 while(true) { 23 menu(); 24 int choice; 25 std::cin >> choice; 26 27 try { 28 switch (choice) { 29 case 1: mgr.load(in_file); 30 std::cout << "加载成功\n"; break; 31 case 2: mgr.sort(); 32 std::cout << "排序已完成\n"; break; 33 case 3: mgr.print(); 34 std::cout << "打印已完成\n"; break; 35 case 4: mgr.save(out_file); 36 std::cout << "导出成功\n"; break; 37 case 5: return; 38 default: std::cout << "不合法输入\n"; 39 } 40 } 41 catch (const std::exception& e) { 42 std::cout << "Error: " << e.what() << '\n'; 43 } 44 } 45 } 46 47 int main() { 48 app(); 49 }
运行结果



拓展
源代码(改动部分)
student.cpp
1 #include "student.hpp" 2 #include <stdexcept> 3 #include <sstream> 4 5 const std::string Student::get_major() const { 6 return major; 7 } 8 9 int Student::get_grade() const { 10 return grade; 11 } 12 13 std::ostream& operator<<(std::ostream& os, const Student& s) { 14 os << s.id << "\t" << s.name << "\t" << s.major << "\t" << s.grade; 15 return os; 16 } 17 18 std::istream& operator>>(std::istream& is, Student& s) { 19 // 直接从流中读取字段,不处理行 20 if (!(is >> s.id >> s.name >> s.major >> s.grade)) { 21 throw std::runtime_error("format error"); 22 } 23 24 // 数据完整性检查 25 if (s.id <= 0) { 26 throw std::runtime_error("ID invalid"); 27 } 28 29 if (s.name.empty()) { 30 throw std::runtime_error("name invalid"); 31 } 32 33 if (s.major.empty()) { 34 throw std::runtime_error("major invalid"); 35 } 36 37 if (s.grade < 0 || s.grade > 100) { 38 throw std::runtime_error("grade invalid"); 39 } 40 41 return is; 42 }
stumgr.cpp
1 #include "stumgr.hpp" 2 #include <fstream> 3 #include <algorithm> 4 #include <stdexcept> 5 #include <sstream> 6 #include <iomanip> 7 8 void StuMgr::load(const std::string& file) { 9 std::ifstream in(file); 10 if (!in.is_open()) { 11 throw std::runtime_error("cannot open file: " + file); 12 } 13 14 students.clear(); 15 16 int line_num = 0; 17 int valid_count = 0; 18 int error_count = 0; 19 20 std::string line; 21 22 // 读取并处理每一行 23 while (std::getline(in, line)) { 24 line_num++; 25 26 // 跳过空行和纯空格行 27 bool is_empty = true; 28 for (char c : line) { 29 if (!std::isspace(static_cast<unsigned char>(c))) { 30 is_empty = false; 31 break; 32 } 33 } 34 35 if (is_empty) { 36 continue; 37 } 38 39 // 检查是否是表头行(包含"学号"或"姓名"等表头关键词) 40 if (line.find("学号") != std::string::npos || 41 line.find("姓名") != std::string::npos || 42 line.find("专业") != std::string::npos || 43 line.find("成绩") != std::string::npos || 44 line.find('|') != std::string::npos) { 45 continue; // 跳过表头行和分隔线 46 } 47 48 // 移除行首的行号和竖线(如果存在) 49 // 例如:"| 2 | 1001 | 抖森 | Acting | 80 |" 50 size_t first_pipe = line.find('|'); 51 if (first_pipe != std::string::npos) { 52 // 找到第一个'|'后的内容 53 size_t start = line.find_first_not_of(" \t", first_pipe + 1); 54 if (start != std::string::npos) { 55 // 找到最后一个'|' 56 size_t last_pipe = line.find_last_of('|'); 57 if (last_pipe != std::string::npos && last_pipe > start) { 58 line = line.substr(start, last_pipe - start); 59 } 60 } 61 } 62 63 // 清理行中的多余空格 64 std::istringstream iss_clean(line); 65 std::ostringstream oss; 66 std::string token; 67 bool first_token = true; 68 69 while (iss_clean >> token) { 70 if (!first_token) { 71 oss << " "; 72 } 73 oss << token; 74 first_token = false; 75 } 76 77 std::string cleaned_line = oss.str(); 78 if (cleaned_line.empty()) { 79 continue; // 跳过空行 80 } 81 82 // 使用字符串流解析这一行 83 std::istringstream iss(cleaned_line); 84 Student stu; 85 86 try { 87 // 尝试从字符串流中读取学生数据 88 iss >> stu; 89 90 // 检查是否还有多余数据 91 std::string extra; 92 if (iss >> extra) { 93 throw std::runtime_error("format error, extra data: " + extra); 94 } 95 96 // 如果成功读取,添加到列表 97 students.push_back(stu); 98 valid_count++; 99 } 100 catch (const std::runtime_error& e) { 101 error_count++; 102 std::cout << "\n[Warning] line " << line_num << " " << e.what() 103 << ", skipped: " << line; 104 } 105 } 106 107 std::cout << "\n加载成功,有效数据: " << valid_count 108 << " 条,跳过: " << error_count << " 条\n"; 109 } 110 111 // 其他方法保持不变... 112 void StuMgr::sort() { 113 std::sort(students.begin(), students.end(), 114 [](const Student& a, const Student& b) { 115 // 先按专业字典序升序 116 if (a.get_major() != b.get_major()) { 117 return a.get_major() < b.get_major(); 118 } 119 // 同专业按成绩降序 120 return b.get_grade() < a.get_grade(); 121 }); 122 } 123 124 void StuMgr::print() const { 125 if (students.empty()) { 126 std::cout << "(empty)\n"; 127 return; 128 } 129 130 // 打印表头 131 std::cout << "\n学号\t姓名\t专业\t成绩\n"; 132 std::cout << "----------------------------------------\n"; 133 134 for (const auto& stu : students) { 135 std::cout << stu << "\n"; 136 } 137 138 std::cout << "----------------------------------------\n"; 139 std::cout << "总计: " << students.size() << " 条记录\n"; 140 } 141 142 void StuMgr::save(const std::string& file) const { 143 std::ofstream out(file); 144 if (!out.is_open()) { 145 throw std::runtime_error("cannot open file: " + file); 146 } 147 148 // 写入表头 149 out << "学号\t姓名\t专业\t成绩\n"; 150 out << "----------------------------------------\n"; 151 152 // 写入数据 153 write(out); 154 } 155 156 void StuMgr::write(std::ostream& os) const { 157 for (const auto& stu : students) { 158 os << stu << "\n"; 159 } 160 }
运行结果



1.student.cpp 中的 operator>>:
改为逐行读取并验证四个字段
添加数据有效性检查:ID>0、姓名非空、专业非空、成绩0-100范围
增加额外字段检测,防止格式错误
2.stumgr.cpp 中的 load() 方法:
改为逐行读取,准确跟踪行号
智能表头识别:跳过含"学号"、"姓名"、"专业"、"成绩"或"|"的行
表格格式清理:移除行号、竖线和多余空格
异常隔离:单行错误不影响其他行处理
添加加载统计:显示有效/无效数据数量
3.错误处理机制:
从文件级异常扩展到数据级异常
详细错误报告:显示行号、错误类型和原始行内容

浙公网安备 33010602011771号