实验6

实验任务1

源代码:

contestant.hpp

点击查看代码
#pragma once
#include <iomanip>
#include <iostream>
#include <string>

struct Contestant {
	long id;
	std::string name;
	std::string major;
	int solved;
	int penalty;
};

inline std::ostream& operator<<(std::ostream& out, const Contestant& c) {
	out << std::left;
	out << std::setw(15) << c.id
		<< std::setw(15) << c.name
		<< std::setw(15) << c.major
		<< std::setw(15) << c.solved
		<< std::setw(15) << c.penalty;

	return out;
}

inline std::istream& operator>>(std::istream& in, Contestant& c) {
	in >> c.id >> c.name >> c.major >> c.solved >> c.penalty;

	return in;
}

utils.hpp

点击查看代码
#pragma once
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
#include <sstream>
#include <cctype>
#include "contestant.hpp"
// ACM 排序规则:先按解题数降序,再按罚时升序
inline bool cmp_by_solve(const Contestant& a, const Contestant& b) {
	if (a.solved != b.solved)return a.solved > b.solved;
	return a.penalty < b.penalty;
}
// 将结果写至任意输出流
inline void write(std::ostream& os, const std::vector<Contestant>& v) {
	for (const auto& x : v)
		os << x << '\n';
}
// 将结果打印到屏幕
inline void print(const std::vector<Contestant>& v) {
	write(std::cout, v);
}
// 将结果保存到文件
inline void save(const std::string& filename, const std::vector<Contestant>& v) {
	std::ofstream os(filename);
	if (!os)
		throw std::runtime_error("fail to open " + filename);
	write(os, v);
}
// 从文件读取信息(跳过标题行)
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 = 0;
	std::size_t line_no = 1;
	while (std::getline(is, line)) {
		++line_no;

		bool all_ws = true;
		for (char ch : line) {
			if (!std::isspace(static_cast<unsigned char>(ch))) {
				all_ws = false;
				break;
			}
		}
		if (all_ws) {
			continue;
		}

		std::istringstream iss(line);
		if (!(iss >> seq >> t.id >> t.name >> t.major >> t.solved >> t.penalty)) {
			std::cerr << "warning: malformed input at line " << line_no << ": " << line << '\n';
			continue;
		}

		v.push_back(t);
	}
	return v;
}

task1.cpp

点击查看代码
#include <algorithm>
#include <iostream>
#include <stdexcept>
#include <vector>
#include "contestant.hpp"
#include "utlis.hpp"

const std::string in_file = R"(D:\实验6部分代码及数据文件_gbk\实验6部分代码及数据文件_gbk\1\data_bad.txt)";
const std::string out_file = R"(D:\实验6部分代码及数据文件_gbk\实验6部分代码及数据文件_gbk\1\ans.txt)";

void app() {
	std::vector<Contestant> contestants;

	try {
		contestants = load(in_file);
		std::sort(contestants.begin(), contestants.end(), cmp_by_solve);
		print(contestants);
		save(out_file, contestants);
	}
	catch (const std::exception& e) {
		std::cerr << e.what() << '\n';
		return;
	}
}

int main() {
	app();
}

运行截图

image
缺少数据或者某一行没有数据就会跳过该行
image

问题

问题1:

(1)std::cout是std::ostream的一个对象的引用,std::ofstream是从是从std::basic_ofstream派生。函数参数是对基类std::ostream的引用,C++ 的引用/继承允许把派生类对象绑定到基类引用,从而复用同一函数来输出到不同类型的流
(2)不需要修改 write()。只要目标设备能以 std::ostream(或其派生类)暴露写接口,就可以直接传入。

问题2:

两处throw分别在save()和load()函数里面
(1)在save()里面打开输出文件失败以后,会抛出异常,在load()里面打开输入文件失败以后会抛出异常。
(2)异常被app()中的
try {
contestants = load(in_file);
...
save(out_file, contestants);
} catch (const std::exception& e) {
std::cerr<< e.what() << '\n';
return;
}
所有抛出的异常会被捕捉时,what返回fail to open,然后std::cerr将错误信息输出。

问题3:
cmp_by_solve 换成下面的lambda表达式之后,功能是一样的,如果a.solved 不等于 b.solved,返回a.solved>b.solved,否则返回a.solved>b.solved
性能主要是sort()决定,比较函数微小几乎不影响性能。结果是一样的。

问题4:

image

我增加以下代码

点击查看代码
while (std::getline(is, line)) {
	++line_no;

	bool all_ws = true;
	for (char ch : line) {
		if (!std::isspace(static_cast<unsigned char>(ch))) {
			all_ws = false;
			break;
		}
	}
	if (all_ws) {
		continue;
	}

	std::istringstream iss(line);
	if (!(iss >> seq >> t.id >> t.name >> t.major >> t.solved >> t.penalty)) {
		std::cerr << "warning: malformed input at line " << line_no << ": " << line << '\n';
		continue;
	}

	v.push_back(t);
}
通过std::getline(is,line)按行读取,对每行用 std::istringstream 解析,这样一行解析失败不会破坏整个流。
std::isspace 检查每个字符是否为空白,如果整行都是空白,all_ws最后就是1,那么就跳过该行。
最后用流提取操作iss >> seq >> t.id >> t.name >> t.major >> t.solved >> t.penalty按空白分隔依次读取字段,如果不匹配抛出错误,跳过该行。

实验任务2

源代码:

student.hpp

点击查看代码
#pragma once

#include <iostream>
#include <string>

class Student {
public:
    Student() = default;
    Student(int id, const std::string& name, const std::string& major, int grade);
    ~Student() = default;
    
    const std::string get_major() const;
    int get_grade() const;

    friend std::ostream& operator<<(std::ostream& os, const Student& s);
    friend std::istream& operator>>(std::istream& is, Student& s);

private:
    int id;   
    std::string  name;
    std::string  major;
    int          grade;  // 0-100
};

stumgr.hpp

点击查看代码
#pragma once
#include <string>
#include <vector>
#include "student.hpp"

class StuMgr {
public:
    void load(const std::string& file);  // 加载数据文件(空格分隔)
    void sort();                         // 排序: 按专业字典序升序、同专业分数降序
    void print() const;                  // 打印到屏幕
    void save(const std::string& file) const; // 保存到文件

private:
    void write(std::ostream &os) const;  // 把数据写到任意输出流

private:
    std::vector<Student> students;
};

stumgr.cpp

点击查看代码
#include "stumgr.hpp"
#include <fstream>
#include <stdexcept>
#include <algorithm>
#include <iostream>
#include <sstream>

inline bool compare_(const Student& a, const Student& b) {
    if (a.get_major() != b.get_major())
        return a.get_major() < b.get_major();
    return a.get_grade() > b.get_grade();
}
void StuMgr::load(const std::string& file) {
    std::ifstream is(file);
    if (!is) {
        throw std::runtime_error("fail to open " + file);
    }
    students.clear();

    std::string line;
    std::getline(is, line);
    size_t line_no = 1;

    while (std::getline(is, line)) {
        ++line_no;
        if (line.empty()) continue;

        std::istringstream ls(line);
        Student s;
        if (!(ls >> s)) {
            throw std::runtime_error("parse error in " + file + " at line " + std::to_string(line_no) + ": '" + line + "'");
        }
        // 检查是否存在多余字段
        std::string extra;
        if (ls >> extra) {
            throw std::runtime_error("extra fields in " + file + " at line " + std::to_string(line_no) + ": '" + extra + "'");
        }

        //检查分数是否合理
        if (s.get_grade() < 0 || s.get_grade() > 100) {
            throw std::runtime_error("invalid grade (not 0-100) in " + file + " at line " + std::to_string(line_no) + ": " + std::to_string(s.get_grade()));
        }
        students.push_back(s);
    }
    
}

void StuMgr::sort() {
    std::sort(students.begin(), students.end(), compare_);
}

void StuMgr::write(std::ostream& os)const {
    for (const auto& s : students)
        os << s << '\n';
}
void StuMgr::print() const{
    write(std::cout);
}

void StuMgr::save(const std::string& file)const {
    std::ofstream os(file);
    if (!os) {
        throw std::runtime_error("fail to open " + file);
    }
    write(os);
}

student.cpp

点击查看代码
#include "student.hpp"
#include <iomanip>
const std::string Student::get_major() const {
	return major;
}

int Student::get_grade() const {
	return grade;
}

std::ostream& operator<<(std::ostream& os, const Student& s) {
	os << std::left;
	os << std::setw(15) << s.id
		<<std::setw(15)<< s.name
		<< std::setw(15) << s.major
		<< std::setw(15) << s.grade;
	return os;
}

std::istream& operator>>(std::istream& is, Student& s) {
	is >> s.id >> s.name >> s.major >> s.grade;
	return is;
}

task2.cpp

点击查看代码
#include <iostream>
#include <limits>
#include <string>
#include "stumgr.hpp"

const std::string in_file = "D:\\C语言代码\\实验六.2\\data_bad.txt";
const std::string out_file = "D:\\C语言代码\\实验六.2\\ans.txt";

void menu() {
    std::cout << "\n**********简易应用**********\n"
                 "1. 加载文件\n"
                 "2. 排序\n"
                 "3. 打印到屏幕\n"
                 "4. 保存到文件\n"
                 "5. 退出\n"
                 "请选择:";
}

void app() {
    StuMgr mgr;

    while(true) {
        menu();
        int choice;
        std::cin >> choice;

        try {
            switch (choice) {
            case 1: mgr.load(in_file); 
                    std::cout << "加载成功\n"; break;
            case 2: mgr.sort(); 
                    std::cout << "排序已完成\n"; break;
            case 3: mgr.print(); 
                    std::cout << "打印已完成\n"; break;
            case 4: mgr.save(out_file); 
                    std::cout << "导出成功\n"; break;
            case 5: return;
            default: std::cout << "不合法输入\n";
            }
        }
        catch (const std::exception& e) {
            std::cout << "Error: " << e.what() << '\n';
        }
    }
}

int main() {
    app();
}

运行截图

打印
image

排序后打印
image

拓展

我在studmgr.cpp文件的load()函数里面
(a)添加了数据缺失检测,
image
如果数据不是规定的格式,就会返回那一行的行数并且显示文件名,将问题抛出,这样方便找到报错原因。
(b)我还增加了检验分数是否合理的代码,
image
规定分数在0-100之间,否则抛出问题。

测试截图:
在data_bad.txt文件中数据缺失检测
image

不合法成绩检测
image
image

总结

(1)通过实验,我了解了流的抽象与复用,用 std::ostream& 编写 write(),同一函数可接受 std::cout、std::ofstream、std::ostringstream 等输出目标。
(2)学会了sort()函数的使用,第三个参数是用来定义排序的规则,可以实现升序降序或者其他形式的排序,功能强大。
(3)对于异常处理与错误报告。使用std::runtime_error可以在运行时触发,抛出错误信息,可用于检测是否打开文件。

posted @ 2025-12-21 11:20  feifeile  阅读(4)  评论(0)    收藏  举报