实验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 }
View Code

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 }
View Code

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 }
View Code

运行结果

屏幕截图 2025-12-17 084059

屏幕截图 2025-12-17 084250

问题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 运行会出现部分数据读取后程序异常终止,导致输出不完整且缺失多行有效数据的问题。屏幕截图 2025-12-17 092723

原因:流读取遇到格式错误的数据行后进入失败状态,导致后续所有数据都无法读取,程序只能输出已读取的部分结果。

(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 }
View Code

实验任务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 };
View Code

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 }
View Code

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 };
View Code

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 }
View Code

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 }
View Code

运行结果

屏幕截图 2025-12-17 162620屏幕截图 2025-12-17 162647

 

屏幕截图 2025-12-17 163141

拓展

源代码(改动部分)

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 }
View Code

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 }
View Code

运行结果

屏幕截图 2025-12-17 165453屏幕截图 2025-12-17 165500

屏幕截图 2025-12-17 165525

1.student.cpp 中的 operator>>:

改为逐行读取并验证四个字段

添加数据有效性检查:ID>0、姓名非空、专业非空、成绩0-100范围

增加额外字段检测,防止格式错误

2.stumgr.cpp 中的 load() 方法:

改为逐行读取,准确跟踪行号

智能表头识别:跳过含"学号"、"姓名"、"专业"、"成绩"或"|"的行

表格格式清理:移除行号、竖线和多余空格

异常隔离:单行错误不影响其他行处理

添加加载统计:显示有效/无效数据数量

3.错误处理机制:

从文件级异常扩展到数据级异常

详细错误报告:显示行号、错误类型和原始行内容

 

posted @ 2025-12-17 17:04  南方之木  阅读(4)  评论(0)    收藏  举报