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 }
终端打印结果

ans.txt

回答问题
问题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)观察运行结果有什么问题?给出测试截图并分析原因。

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

测试一下

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 }
终端运行结果


ans.txt

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 }
终端测试,错误信息正确打印

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

ans.txt导出也成功

感悟
<sstream>用得有点顺手了😊
一学期真快...
一年真快...去年这个时候也差不多写完c语言的IO实验

浙公网安备 33010602011771号