实验6

实验任务1:

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

contestant源代码:

 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

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

运行测试结果:

image

问题1:流操作与代码复用
观察 print() 与 save() 的实现,均在内部调用 write() :
(1) write() 的参数类型是 std::ostream& ,它为什么能同时接受 std::cout 和 std::ofstream 对象作为实参?
答:std::cout是std::ostream的全局对象,std::ofstream是std::ostream的派生类。write()的参数类型是std::ostream&,是基类,基类引用可以接收其派生类对象作为实参。
(2)如果要把结果写到其他设备,只要该设备也提供 std::ostream 接口,还需改动 write() 吗?
答:不需要。
 
问题2:异常处理与捕获
在代码中找到两处 throw 语句,说明:
(1)什么情况下会抛出异常;
答:打开文件失败时会抛出异常。
(2)异常被谁捕获、做了哪些处理。
答:被catch (const std::exception& e)中e捕获,通过e.what()捕获异常信息,输出到错误流cerr中。
 
问题3:替代写法
函数 app 中 std::sort(contestants.begin(), contestants.end(), cmp_by_solve); 参数cmp_by_solve 换成下面的lambda表达式是否可以?功能、性能、结果是否一致?
答:可以,功能和结果一致,性能因为无需另外的函数调用开销,性能会更好。
 
问题4:数据完整性与代码健壮性
把 in_file 改成 "./data_bad.txt" (内含空白行或字段缺失),重新编译运行:
(1)观察运行结果有什么问题?给出测试截图并分析原因。
答:测试截图:
image

txt中7 204942076 Thomas 未来专业6 这一行缺少了解题道数和总罚时数,导致他读取的解题道数和总罚时数其实是下一行的序号和学号。

(2)思考:如何修改函数 std::vector<Contestant> load(const std::string& filename) ,使其提示出错行号并跳过有问题的数据行,同时让程序继续执行?(选答*)
答:将代码改成如下,按行读取,而不是按元素读取,如果该行元素读取失败,就跳过不会影响正常使用。

// 从文件读取信息(跳过标题行)
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;
int line_num = 1;

while (std::getline(is, line))
{
std::istringstream iss(line); // 将行数据转为字符串流
Contestant t;
int seq;

line_num++;

if (!(iss >> seq >> t))
{
std::cerr << "[Warning] 第" << line_num << "行格式错误,跳过:" << line << std::endl;
continue; // 跳过错误行
}
v.push_back(t);
}

return v;
}

测试截图:
image

 


 

实验任务2:

 student.hpp源代码:

 1 #pragma once
 2 
 3 #include <iostream>
 4 #include <string>
 5 #include <iomanip>
 6 
 7 class Student {
 8 public:
 9     Student() = default;
10     ~Student() = default;
11     
12     const std::string get_major() const;
13     int get_grade() const;
14 
15     friend std::ostream& operator<<(std::ostream& os, const Student& s);
16     friend std::istream& operator>>(std::istream& is, Student& s);
17 
18 private:
19     int id;   
20     std::string  name;
21     std::string  major;
22     int          grade;  // 0-100
23 };
24 
25 const std::string Student::get_major() const
26 {
27     return major;
28 }
29 
30 int Student::get_grade() const
31 {
32     return grade;
33 }
34 
35 std::ostream& operator<<(std::ostream& os, const Student& s)
36 {
37     os << std::setw(15) << s.id 
38        << std::setw(15) << s.name 
39        << std::setw(15) << s.major 
40        << std::setw(15) << s.grade;
41     return os;
42 }
43 
44 std::istream& operator>>(std::istream& is, Student& s)
45 {
46     is >> s.id >> s.name >> s.major >> s.grade;
47     return is;
48 }
View Code

stumgr.hpp源代码:

 1 #pragma once
 2 #include <string>
 3 #include <vector>
 4 #include <fstream>
 5 #include <sstream>
 6 #include <stdexcept>
 7 #include <algorithm>
 8 #include "student.hpp"
 9 
10 class StuMgr {
11 public:
12     void load(const std::string& file);  // 加载数据文件(空格分隔)
13     void sort();                         // 排序: 按专业字典序升序、同专业分数降序
14     void print() const;                  // 打印到屏幕
15     void save(const std::string& file) const; // 保存到文件
16 
17 private:
18     void write(std::ostream &os) const;  // 把数据写到任意输出流
19 
20 private:
21     std::vector<Student> students;
22 };
23 
24 void StuMgr::load(const std::string& file)
25 {
26     std::ifstream is(file);
27     if (!is)
28     {
29         throw std::runtime_error("fail to open" + file);
30     }
31 
32     std::string line;
33     std::getline(is, line); // 跳过标题
34 
35     Student s;
36 
37     int line_num = 1;
38 
39     while (std::getline(is, line))
40     {
41         std::istringstream iss(line);
42         
43         line_num++;
44 
45         if (!(iss >> s))
46         {
47             std::cerr << "" << std::to_string(line_num) << "行格式错误,跳过: " << line;
48             continue;
49         }
50         students.push_back(s);
51     }
52 }
53 
54 void StuMgr::sort()
55 {
56     std::sort(students.begin(), students.end(), [](const Student& a, const Student& b) {
57         if (a.get_major() != b.get_major()) {
58             return a.get_major() < b.get_major();
59         }
60         return a.get_grade() > b.get_grade();
61         });
62 }
63 
64 void StuMgr::print() const
65 {
66     for (auto& s:students)
67     {
68         std::cout << s << std::endl;
69     }
70 }
71 
72 void StuMgr::save(const std::string& file) const
73 {
74     std::ofstream os(file);
75     if (!os)
76     {
77         throw std::runtime_error("fail to open" + file);
78     }
79 
80     write(os);
81 }
82 
83 void StuMgr::write(std::ostream& os)const
84 {
85     for (auto& s : students)
86     {
87         os << s << std::endl;
88     }
89 }
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

运行测试截图:

image

image

image

image

image

image

 

实验总结:

给我印象最深的是我尝试在跳过错误行读取的时候使用throw然后后面直接就不读了,连我的continue都没执行,所以改成了cerr,在函数内部写一个try catch会继续执行,这样就不需要continue了。

修改的代码如下:

void StuMgr::load(const std::string& file)
{
std::ifstream is(file);
if (!is)
{
throw std::runtime_error("fail to open" + file);
}

std::string line;
std::getline(is, line); // 跳过标题

Student s;

int line_num = 1;

while (std::getline(is, line))
{
std::istringstream iss(line);

line_num++;

try
{
if (!(iss >> s))
{
throw std::runtime_error ("第" + std::to_string(line_num) + "行格式错误,跳过: " + line);
}
}
catch (const std::runtime_error& e)
{
std::cerr << "捕获异常:" << e.what() << std::endl;
}

students.push_back(s);
}
}

运行截图:

image

image

image

 

posted @ 2025-12-17 10:31  王宝炜  阅读(3)  评论(0)    收藏  举报