实验6

一、实验任务1

源代码contestant.hpp

 1 #pragma once
 2 #include<iomanip>
 3 #include<iostream>
 4 #include<string>
 5 struct Contestant{
 6     long id;
 7     std::string name;
 8     std::string major;
 9     int solved;
10     int penalty;
11 };
12 inline std::ostream& operator<<(std::ostream& out,const Contestant& c){
13     out<<std::left;
14     out<<std::setw(15)<<c.id
15        <<std::setw(15)<<c.name
16        <<std::setw(15)<<c.major
17        <<std::setw(10)<<c.solved
18        <<std::setw(10)<<c.penalty;
19     return out;    
20 }
21 inline std::istream& operator>>(std::istream& in,Contestant& c){
22     in>>c.id>>c.name>>c.major>>c.solved>>c.penalty;
23     return in;    
24 }
contestant.hpp

源代码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 inline bool cmp_by_solve(const Contestant& a,const Contestant& b){
 9     if(a.solved!=b.solved)
10         return a.solved>b.solved;
11     return a.penalty<b.penalty;
12 }
13 inline void write(std::ostream& os,const std::vector<Contestant>& v){
14     for(const auto& x:v)
15         os<<x<<'\n';
16 }
17 inline void print(const std::vector<Contestant>& v){
18     write(std::cout,v);
19 }
20 inline void save(const std::string& filename,const std::vector<Contestant>& v){
21     std::ofstream os(filename);
22     if(!os)
23         throw std::runtime_error("fail to open"+filename);
24     write(os,v);    
25 }
26 inline std::vector<Contestant> load(const std::string& filename){
27     std::ifstream is(filename);
28     if(!is)
29         throw std::runtime_error("fail to open"+filename);
30     std::string line;
31     std::getline(is,line);
32     std::vector<Contestant> v;
33     Contestant t;
34     int seq;
35     while(is>>seq>>t)
36         v.push_back(t);
37     return v;
38 }
utils.hpp

源代码task1.cpp

 1 #include<algorithm>
 2 #include<iostream>
 3 #include<stdexcept>
 4 #include<vector>
 5 #include"contestant.hpp"
 6 #include"utils.hpp"
 7 const std::string in_file="./data.txt";
 8 const std::string out_file="./ans.txt";
 9 void app(){
10     std::vector<Contestant> contestants;
11     try{
12         contestants=load(in_file);
13         std::sort(contestants.begin(),contestants.end(),cmp_by_solve);
14         print(contestants);
15         save(out_file,contestants);
16     }catch(const std::exception& e){
17         std::cerr<<e.what()<<'\n';
18         return;
19     }
20 }
21 int main(){
22     app();
23 }
task1.cpp

运行成果展示

image

image

 

问题1: 流操作与代码复用

观察 print() 与 save() 的实现,均在内部调用 write() :
(1) write() 的参数类型是 std::ostream& ,它为什么能同时接受 std::cout 和 std::ofstream 对象作为实参?

答:std::ostream是c++标准输出流的一个基类,这里write()的参数类型是 std::ostream&表示是对基类std::ostream的引用。根据我在c++参考手册上查到的,std::cout是标准库提供的全局ostream对象(标准输出流),std::ofstream是继承自ostream的一个派生对象(文件输出流),所以都可以被ostream的引用绑定。 

image

image

image

(2)如果要把结果写到其他设备,只要该设备也提供 std::ostream 接口,还需改动 write() 吗?

答:不需要改动,有了std::ostream接口可以将结果写进任意输出流。

 

问题2:异常处理与捕获
在代码中找到两处 throw 语句,说明:
(1)什么情况下会抛出异常:
答:两处都是在无法打开文件时抛出runtime_error异常。

image

image

(2)异常被谁捕获、做了哪些处理。
答:异常会被task1.cpp中的catch捕获。当load或save函数抛出异常时,catch会捕获到并通过异常类基类std::exception的成员函数what()返回相应的异常原因。

image

 
问题3:替代写法
函数 app 中 std::sort(contestants.begin(), contestants.end(), cmp_by_solve); 参数cmp_by_solve 换成下面的lambda表达式是否可以?功能、性能、结果是否一致?

image

答:可以。功能、性能、结果都一致,只不过不需要在外部定义一个比较函数了。

 
问题4:数据完整性与代码健壮性
把 in_file 改成 "./data_bad.txt" (内含空白行或字段缺失),重新编译运行:
(1)观察运行结果有什么问题?给出测试截图并分析原因。
答:当遇到某些数据空缺或出现空行时没有办法识别,所以出现了类似把学号打印到总罚时栏等错乱的问题。

image

(2)思考:如何修改函数 std::vector<Contestant> load(const std::string& filename) ,使其提示出错行号并跳过有问题的数据行,同时让程序继续执行?

答:主要是利用了string中的empty()判断空行和利用istringstream(添加头文件"sstream")从字符串中提取数据来判断有没有数据缺失。每读入一行,先判断是否空行,是的话直接退出访问下一行;反之用istringstream获取此行中的所有元素,若完美匹配到每一个数据,放入容器中,否则就直接输出错误信息。

修改后的源代码utils.hpp(主要改了load函数,其他的源代码都没变)

 1 #pragma once
 2 #include<fstream>
 3 #include<iostream>
 4 #include<stdexcept>
 5 #include<string>
 6 #include<vector>
 7 #include<sstream>
 8 #include"contestant.hpp"
 9 inline bool cmp_by_solve(const Contestant& a,const Contestant& b){
10     if(a.solved!=b.solved)
11         return a.solved>b.solved;
12     return a.penalty<b.penalty;
13 }
14 inline void write(std::ostream& os,const std::vector<Contestant>& v){
15     for(const auto& x:v)
16         os<<x<<'\n';
17 }
18 inline void print(const std::vector<Contestant>& v){
19     write(std::cout,v);
20 }
21 inline void save(const std::string& filename,const std::vector<Contestant>& v){
22     std::ofstream os(filename);
23     if(!os)
24         throw std::runtime_error("fail to open"+filename);
25     write(os,v);    
26 }
27 inline std::vector<Contestant> load(const std::string& filename){
28     std::ifstream is(filename);
29     if(!is)
30         throw std::runtime_error("fail to open"+filename);
31     std::string line;
32     std::getline(is,line);
33     std::vector<Contestant> v;
34     Contestant t;
35     int seq=1;
36     int num=1;
37     while(std::getline(is,line))
38     {
39         num++;
40         if(line.empty()){
41             std::cerr<<"警告:出现空行!出错行号为:"<<num<<",成功跳过此数据行\n"; 
42             continue; 
43         }
44         std::istringstream a(line);
45         if(a>>seq>>t.id>>t.name>>t.major>>t.solved>>t.penalty) 
46             v.push_back(t);
47         else
48             std::cerr<<"警告:数据有缺!出错行号为:"<<num<<",成功跳过此数据行\n";
49         seq++; 
50     }
51     return v;
52 }
utils.hpp

image

image

 

 

二、实验任务2

源代码student.hpp

 1 #pragma once
 2 #include<iostream>
 3 #include<string>
 4 class Student{
 5 public:
 6     Student()=default;
 7     ~Student()=default;
 8     const std::string get_major() const;
 9     int get_grade() const;
10     friend std::ostream& operator<<(std::ostream& os,const Student& s);
11     friend std::istream& operator>>(std::istream& is,Student& s);
12 private:
13     int id;
14     std::string name;
15     std::string major;
16     int grade;
17 };
student.hpp

源代码student.cpp

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

源代码stumgr.hpp

 1 #pragma once
 2 #include<string>
 3 #include<vector>
 4 #include"student.hpp"
 5 class StuMgr{
 6 public:
 7     void load(const std::string& file);
 8     void sort();
 9     void print() const;
10     void save(const std::string& file) const;
11 private:
12     void write(std::ostream &os) const;
13 private:
14     std::vector<Student> students;
15 };
stumgr.hpp

源代码stu.mgr.cpp

 1 #include<fstream>
 2 #include<iostream>
 3 #include<stdexcept>
 4 #include<string>
 5 #include<vector>
 6 #include<algorithm>
 7 #include"stumgr.hpp"
 8 #include"student.hpp"
 9 const std::string in_file="./data2.txt";
10 const std::string out_file="./ans2.txt";
11 void StuMgr::load(const std::string& file){
12     std::ifstream is(file);
13     if(!is)
14         throw std::runtime_error("fail to open"+file);
15     std::string line;
16     std::getline(is,line);
17     Student s;
18     while(is>>s)
19         students.push_back(s);
20 }
21 void StuMgr::sort(){
22     load(in_file);
23     std::sort(students.begin(),students.end(),
24     [](const Student& a,const Student& b){
25        return a.get_major()!=b.get_major()?a.get_major()<b.get_major()
26               :a.get_grade()>b.get_grade();
27     });
28 }
29 void StuMgr::print() const{
30     write(std::cout);
31 }
32 void StuMgr::save(const std::string& file) const{
33     std::ofstream os(file);
34     if(!os)
35         throw std::runtime_error("fail to open"+file);
36     write(os);
37 }
38 void StuMgr::write(std::ostream &os) const{
39     for(const auto& x:students)
40         os<<x<<'\n';
41 }
stumgr.cpp

源代码task2.cpp

 1 #include<iostream>
 2 #include<limits>
 3 #include<string>
 4 #include"stumgr.hpp"
 5 const std::string in_file="./data2.txt";
 6 const std::string out_file="./ans2.txt";
 7 void menu(){
 8     std::cout<<"\n************简易应用************\n"
 9                "1.加载文件\n"
10                "2.排序\n"
11                "3.打印到屏幕\n"
12                "4.保存到文件\n"
13                "5.退出\n"
14                "请选择:"; 
15 }
16 void app(){
17     StuMgr mgr;
18     while(true){
19         menu();
20         int choice;
21         std::cin>>choice;
22         try{
23             switch(choice){
24             case 1:mgr.load(in_file);
25                    std::cout<<"加载成功\n";break;
26             case 2:mgr.sort();
27                    std::cout<<"排序已完成\n";break;
28             case 3:mgr.print();
29                    std::cout<<"打印已完成\n";break;
30             case 4:mgr.save(out_file);
31                    std::cout<<"导出成功\n";break;
32             case 5:return;
33             default:std::cout<<"不合法输入\n"; 
34             }
35         }
36         catch(const std::exception& e){
37             std::cout<<"Error: "<<e.what()<<'\n';
38         } 
39     }
40 }
41 int main(){
42     app();
43 }
task2.cpp

运行成果展示

image

image

image

image

 

 拓展:

在stumgr.cpp的load函数中增加判断条件,首先利用getline读取文件中每一行的信息,如果这一行是空行,输出空行错误;利用istringstream读取一行中的数据信息,如果读取的能对应每一个元素,再判断成绩的范围,输出越界错误,反之将这一行存放到容器中;如果一行中有数据空缺,输出格式错误。

源代码stumger.cpp(其他的源代码都没有修改,只调整了load函数的功能,在加载数据时可以报错)

 1 #include<fstream>
 2 #include<iostream>
 3 #include<stdexcept>
 4 #include<string>
 5 #include<vector>
 6 #include<algorithm>
 7 #include<sstream>
 8 #include"stumgr.hpp"
 9 #include"student.hpp"
10 const std::string in_file="./data2.txt";
11 const std::string out_file="./ans2.txt";
12 void StuMgr::load(const std::string& file){
13     std::ifstream is(file);
14     if(!is)
15         throw std::runtime_error("fail to open"+file);
16     std::string line;
17     std::getline(is,line);
18     Student s;
19     int seq=1;
20     while(std::getline(is,line))
21     {
22         seq++;
23         if(line.empty()){
24             std::cerr<<"[Warning] line "<<seq<<" empty line, skipped\n";  
25         }
26         else{
27             std::istringstream a(line);
28             if(a>>s)
29             {
30                 if(s.get_grade()<0||s.get_grade()>100){
31                     std::cout<<"error: "<<s.get_grade()<<std::endl;
32                     std::cerr<<"[Warning] line "<<seq<<" grade invalid, skipped: "<<line<<std::endl;
33                 } 
34                 else 
35                     students.push_back(s);
36             }
37             else
38                 std::cerr<<"[Warning] line "<<seq<<" format error, skipped: "<<line<<std::endl;
39             } 
40     }
41 }
42 void StuMgr::sort(){
43     std::sort(students.begin(),students.end(),
44     [](const Student& a,const Student& b){
45        return a.get_major()!=b.get_major()?a.get_major()<b.get_major()
46               :a.get_grade()>b.get_grade();
47     });
48 }
49 void StuMgr::print() const{
50     write(std::cout);
51 }
52 void StuMgr::save(const std::string& file) const{
53     std::ofstream os(file);
54     if(!os)
55         throw std::runtime_error("fail to open"+file);
56     write(os);
57 }
58 void StuMgr::write(std::ostream &os) const{
59     for(const auto& x:students)
60         os<<x<<'\n';
61 }
stumgr.cpp

image

image

image

image

 

实验总结:

1.本次实验实践了利用标准IO流完成文件的读写来进行后续操作。在写文件的地址时有两种方法,一种直接按实例中给出的"./data.txt"(这是文件和代码要放在同一个文件夹里),另一种是绝对路径R"(C:\Users\Lenovo\Desktop\c++面向对象\data.txt)",就是要注意加上R和括号。

2.在重载运算符时要注意写清楚对应的元素,一开始我在任务2std::istream& operator>>中多加了s.seq,但没有由于没有这个元素导致文件整个无法打印出来。

3.在判断数据格式异常时用到了std::istringstream来获取一行中的所有数据,来寻找每一个数据是否都存在,但注意要加上头文件#include<sstream>。

posted @ 2025-12-19 21:31  鱼籽不煮粥  阅读(0)  评论(0)    收藏  举报