oop-实验6

task1

contestant.hpp

 1 #pragma once
 2 
 3 #include<iomanip>
 4 #include<iostream>
 5 #include<string>
 6 
 7 struct Contestant{
 8     //学号/姓名/专业
 9     long id;
10     std::string name; 
11     std::string major; 
12     int solved;   //解题数
13     int penalty;  //总罚时
14 };
15 
16 //重载<<,要求:姓名/专业里不含空白符
17 inline std::ostream& operator<<(std::ostream &out,const Contestant& c){
18     out<<std::left;
19     out<<std::setw(15)<<c.id
20        <<std::setw(15)<<c.name
21        <<std::setw(20)<<c.major
22        <<std::setw(10)<<c.solved
23        <<std::setw(10)<<c.penalty;
24     return out;
25 }
26 
27 //重载>>,这个函数就是输入Contestant对象
28 inline std::istream& operator>>(std::istream& in,Contestant& c){
29     in>>c.id>>c.name>>c.major>>c.solved>>c.penalty;
30     return in;
31 }

utils.hpp

 1 #pragma once
 2 
 3 #include<fstream>
 4 #include<iostream>
 5 #include<stdexcept>
 6 #include<string>
 7 #include<vector>
 8 #include"contestant.hpp"
 9 
10 //ACM排序规则:先按解题数降序,再按罚时升序
11 inline bool cmp_by_solve(const Contestant& a,const Contestant& b){
12     return a.solved!=b.solved?a.solved>b.solved:a.penalty<b.penalty;
13 }
14 
15 //将结果写至任意输出流
16 inline void write(std::ostream& os,const std::vector<Contestant>& v){
17     for(const auto& x:v)
18         os<<x<<"\n";
19 }
20 
21 //将结果打印到屏幕,其实就相当于调用了write函数只不过函数参数传递是cout就变成了将结果打印到屏幕上了
22 inline void print(const std::vector<Contestant>& v){
23     write(std::cout,v);
24 }
25 
26 //将结果保存到文件
27 inline void save(const std::string& filename,const std::vector<Contestant>& v){
28     std::ofstream os(filename);
29     if(!os){
30         throw std::runtime_error("fail to open"+filename);
31     }
32     write(os,v);
33 }
34 
35 //从文件读取信息(跳过标题行)
36 inline std::vector<Contestant> load(const std::string& filename){
37     std::ifstream is(filename); 
38     if(!is){
39         throw std::runtime_error("fail to open"+filename);
40     }
41     std::string line;
42     std::getline(is,line); //跳过标题,这里getline的第一个参数只是需要一个输入流对象
43 
44     std::vector<Contestant> v;
45     Contestant t;
46     int seq;
47     //这里is>>会根据读到的内容和读取变量
48     while(is>>seq>>t) v.push_back(t);
49     return v; 
50 }

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     try{
14         contestants=load(in_file);
15         std::sort(contestants.begin(),contestants.end(),cmp_by_solve);
16         print(contestants);
17         save(out_file,contestants);
18     }catch(const std::exception& e){
19         std::cerr<<e.what()<<'\n';
20         return;
21     }
22 }
23 
24 int main(){
25     app();
26 }

终端打印结果

image

 ans.txt

image

 回答问题

问题1:流操作与代码复用
观察 print() 与 save() 的实现,均在内部调用 write() :
(1) write() 的参数类型是 std::ostream& ,它为什么能同时接受 std::cout 和 std::ofstream 对象作为实参?
  std::cout是std::ostream本身创建的实例,std::ofstream是std::ostream的派生类,基类的引用是可以绑定到派生类对象上面的,也就是多态。

(2)如果要把结果写到其他设备,只要该设备也提供 std::ostream 接口,还需改动 write() 吗?
  不用改动,write()函数需要一个ostream&,只要该设备的接口继承自std::ostream就ok。

问题2:异常处理与捕获
在代码中找到两处 throw 语句,说明:
throw std::runtime_error("fail to open"+filename);
(1)什么情况下会抛出异常;
  文件打开失败时会抛出异常。
(2)异常被谁捕获、做了哪些处理。
  这两个异常都是在app()函数中try{}模块调用的save(),load()函数里的,发生异常时会被catch块捕捉。
  然后catch块中cerr是用来输出错误信息的流,调用e.what()获取异常的具体描述信息,最后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;}
  嗯结果是一致的,性能方面lambda表达式通常是更快的,没有调用函数的额外开销。

问题4:数据完整性与代码健壮性
把 in_file 改成 "./data_bad.txt" (内含空白行或字段缺失),重新编译运行:
(1)观察运行结果有什么问题?给出测试截图并分析原因。

image
  打印出来的结果数据是错误的,紊乱的。
  load函数中的is输入流对象在读取的时候会自动忽略前导空百符(如空格和换行),所以读到第4行的时候因为整行的数据都是缺失的就直接一起都跳过了
  但是当读到第10行时,这一行的数据中的解题数和总罚时都缺失,所以就跳过空白将第10行的序号8读成了解题数,将学号读成了罚时,这两个能成功读取是因为类型能跟int匹配上
  但是接下来的该读到int的时候读到了string类型的name,就读取失败了,退出。后面三个人的数据直接就没读进去。
(2)思考:如何修改函数 std::vector<Contestant> load(const std::string& filename) ,使其提示出错行号并跳过有问题的数据行,同时让程序继续执行?(选答*)

  我想的是每行读取,然后放到sstream对象中判断该行是否正确

image

 测试一下

image

 

 

task2

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 private:
17     int id;
18     std::string name;
19     std::string major;
20     int grade; //0-100
21 };

student.cpp

 1 #include<iostream>
 2 #include<string>
 3 #include<iomanip>
 4 #include"student.hpp"
 5 
 6 const std::string& Student::get_major() const{
 7     return major;
 8 }
 9 
10 int Student::get_grade() const{
11     return grade;
12 }
13 
14 std::ostream& operator<<(std::ostream& os,const Student& s){
15     os<<std::left;
16     os<<std::setw(10)<<s.id
17       <<std::setw(10)<<s.name
18       <<std::setw(10)<<s.major
19       <<std::setw(10)<<s.grade;
20     return os;
21 }
22 std::istream& operator>>(std::istream& is,Student& s){
23     is>>s.id>>s.name>>s.major>>s.grade;
24     return is;
25 }

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     std::vector<Student> students;
17 };

stumgr.cpp

 1 #include<string>
 2 #include<vector>
 3 #include<fstream>
 4 #include<algorithm>
 5 #include<sstream>
 6 #include"student.hpp"
 7 #include"stumgr.hpp"
 8 void StuMgr::load(const std::string& file){
 9     std::ifstream is(file);
10     if(!is){
11         throw std::runtime_error("fail to open"+file);
12     }
13     std::string str;
14     std::getline(is,str);
15     Student s;
16     while(is>>s) students.push_back(s);
17 }
18 
19 void StuMgr::sort(){
20     std::sort(students.begin(),students.end(),[](const Student& s1,const Student& s2){
21         return s1.get_major()!=s2.get_major()?s1.get_major()<s2.get_major():s1.get_grade()>s2.get_grade();
22     });
23 }
24 
25 void StuMgr::print() const{ //打印到屏幕
26     write(std::cout);
27 }
28  
29 void StuMgr::save(const std::string& file) const{ //保存到文件
30     std::ofstream os(file);
31     if(!os){
32         throw std::runtime_error("fail to open"+file);
33     }
34     write(os);
35 }
36 
37 void StuMgr::write(std::ostream &os) const{ //把数据写到任意输出流
38     for(auto s:students){
39         os<<s<<"\n";
40     }
41 }

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     //这里出现的多引号连续放置编译器会自动拼接"+"为一个字符串,所以这里并没有加<<
11     std::cout<<"\n**********简易应用**********\n"
12                 "1. 加载文件\n"
13                 "2. 排序\n"
14                 "3. 打印到屏幕\n"
15                 "4. 保存到文件\n"
16                 "5. 退出\n"
17                 "请选择:";
18 }
19 
20 void app(){
21     StuMgr mgr;
22     while(true){
23         menu();
24         int choice;
25         std::cin>>choice;
26         try{
27             switch(choice){
28                 case 1:mgr.load(in_file);
29                     std::cout<<"加载成功\n"; break;
30                 case 2:mgr.sort();
31                     std::cout<<"排序已完成\n"; break;
32                 case 3:mgr.print();
33                     std::cout << "打印已完成\n"; break;
34                 case 4:mgr.save(out_file);
35                     std::cout << "导出成功\n"; break;
36                 case 5: return;
37                 default: std::cout<<"不合法输入\n";
38             }
39         }catch(const std::exception& e){
40             std::cout<<"Error:"<<e.what()<<'\n';
41         }
42     }
43 }
44 
45 int main(){
46     app();
47 }

终端运行结果

屏幕截图 2025-12-19 002317

屏幕截图 2025-12-19 002349

ans.txt

image

选做*:为数据完整性、有效性(如文件字段是否有缺失、成绩值是否合法)提供异常处理
 方法我想的是还是用字符串流...跟task1有点像吧
 主要改动的代码其实就是load函数中读取数据的部分,先判断一下是否合理,如果合理的话再读取,不合理的话直接打印出错误信息然后跳到下一行
 1 void StuMgr::load(const std::string& file){
 2     std::ifstream is(file);
 3     if(!is){
 4         throw std::runtime_error("fail to open"+file);
 5     }
 6     std::string str;
 7     std::getline(is,str); //先读取第一行标题
 8     /*
 9     Student s;
10     while(is>>s) students.push_back(s);
11     */
12     /*
13     上面注释部分是未进行数据校验的写法,下面是更安全的做法
14     
15     题目:如希望加载文件时提供异常处理,包括对字段缺失、成绩值不在有效范围内内做校验,使得代码更健壮:有字段缺失
16     或异常值不会参与计算,如何完善代码,使其预期效果如下:报告错误行信息,同时并不影响其它有效数据计算。
17     */
18     std::string line;
19     int num=1; //记录此时所在的行数
20     while(std::getline(is,line)){
21         num++;
22         if(line.empty()) continue;
23         std::istringstream iss(line);
24         int id,grade;
25         std::string name,major;
26         if(!(iss>>id>>name>>major>>grade)){
27             std::cout<<"[Warning] line"<<num<<" format error,skipped: "<<line+"\n";
28             continue;
29         }else if(grade<0||grade>100){
30             std::cout<<"[Warning] line"<<num<<" grade invalid,skipped:"<<line+"\n";
31             continue;
32         }
33         Student s;
34         std::istringstream new_iss(line);
35         new_iss>>s;
36         students.push_back(s);
37     }
38 }

终端测试,错误信息正确打印

image

 其余正确的数据正常排序&print

image

 ans.txt导出也成功

image

 

感悟

<sstream>用得有点顺手了😊

 一学期真快...

 一年真快...去年这个时候也差不多写完c语言的IO实验

posted @ 2025-12-19 01:26  fg-ever  阅读(3)  评论(0)    收藏  举报