(MFC)广州大学大一下课程设计实验报告-学生成绩管理系统

程序设计
课程设计实验报告

学院: 计算机科学与网络工程学院
专业班级: XXX
姓名: XXX
学号: 190XXXXXXX
指导老师:张艳玲

2020.6.29

一、课程设计题目及内容

题目:学生成绩管理系统
设计要求及提示如下:
(1)、设计一个学生类Student,包括数据成员:姓名、学号、二门课程(面向对象程序设计、高等数学)的成绩。
(2)、创建一个管理学生的类Management,包括实现学生的数据的增加、删除、修改、按课程成绩排序、保存学生数据到文件及加载文件中的数据等功能。
(3)、创建一个基于对话框的MFC应用程序,程序窗口的标题上有你姓名、学号和应用程序名称。使用(1)和(2)中的类,实现对学生信息和成绩的输入和管理。
在这里插入图片描述

(4)、创建一个单文档的MFC应用程序,读取(3)中保存的文件中的学生成绩,分别用直方图和折线方式显示所有学生某课程的成绩分布图。
在这里插入图片描述

二、每个功能模块的设计分析及算法描述

各个功能模块的核心功能在Management类完成:

  1. 在Management类里声明了一个动态数组all,默认初始化容量为10个,在添加数据时,首先进行数据合法性检查,如果检查不通过直接添加失败,通过则进行添加。如果数组容量满了,将进行自动扩容,扩容的方法是:再开辟一个更大容量的动态数组temp,然后逐一将all中的数据拷贝到temp,拷贝完成之后释放掉all的内存空间,再让all指向temp。如果数组容量再次满了,重复上述操作。
  2. 删除数据时,需要传入学号,是先逐一遍历all数组中的元素,当发现某个Student与传入的学号相同时,就将这这后面的所有Student向前挪一位,覆盖掉要被删除的Student。
  3. 修改数据,分为修改姓名、学号、程序课成绩、高数课成绩四个函数,这四个函数都是需要传入学号和修改后的数据,方法和删除数据一样,先根据id找到要修改的Student,然后修改。
  4. 排序功能:采用冒泡排序。
  5. 加载文件中的数据:在构造函数里初始化数组all,使用ifstream读取和程序相同目录下的“data.txt”文件,逐个读取后封装成Student对象,添加到数组all。
  6. 保存数据到文件:在析构函数里进行,先用ofstream打开“data.txt”文件,然后遍历all数组,分别文件输出姓名、学号、程序课成绩、高数课成绩,每个信息之间用空格分隔,每保存完一个Student后回车。
    data.txt文件如图:
    在这里插入图片描述

画统计图在View类完成:

  1. 排序之前,从Management类里获取要统计的信息(高数课成绩或者程序课成绩),vector类型,然后再使用这些数据来统计。
  2. 绘制条形图,先创建一个矩形,把vector中的数据分为5个区间,根据矩形的宽度、高度和区间的最高人数计算每个人应该占多少高度、每个区间占多少宽度,然后根据这些信息来绘制。
  3. 绘制折线图,也是先创建一个矩形,然后在这个矩形里画线(10行,n列,n为数据的数量),用矩形的宽度/数据的数量可以得到每个点相差的距离,用矩形的高度*数据的值/100得到高度,然后每个点的信息都记录在数组里,最后交给Polyline函数画线。

查找功能:首先要检查输入内容的合法性(分数区间不能为空,最大值不能大于100,最小值不能小于0,且最小值不能比最大值大),借助vector容器,遍历Management的all数组,如果满足条件,则将改Student添加到vector容器中,最后遍历完之后输入vector容器保存的结果。模糊查询的方法是用CString的Find()函数,如果返回结果不为-1,则代表这个CString里包含这个字符串。

三、程序中使用的数据及主要符号说明

Student 类:
	CString m_name;//姓名
	CString m_id;//学号(唯一)
	int score1;//程序课成绩
	int score2;//高数课成绩

Management 类:
	Student* all; //存放学生数据的数组
	int capacity; //数组当前容量
	int size;     //数组当前大小
	(extern) Management m; //Management对象,在View.cpp和ADD.cpp都有出现,为了在两个cpp共用一个对象,所以在其中一个地方加了extern

数据管理的对话框:
	ADD; //添加数据的对话框,类名为ADD
	CString m_name;	//输入姓名的编辑框
	CString m_id;//输入学号的编辑框
	int score1;//输入程序课成绩的编辑框
	int score2;//输入高数课成绩的编辑框
	int Search_Score1_Min; //数据查找时,需要输入的程序课成绩最小值
	int Search_Score1_Max; //数据查找时,需要输入的程序课成绩最大值
	int Search_Score2_Min; //数据查找时,需要输入的高数课成绩最小值
	int Search_Score2_Max; //数据查找时,需要输入的高数课成绩最大值
	CString Search_Name; //数据查找时,需要输入的姓名
	CString Search_Id; //数据查找时,需要输入的程序课成绩学号

四、带有详细注释的自己编写的源程序

Student.h

#pragma once

class Student {
public:	
	CString m_name;//姓名
	CString m_id;//学号(唯一)
	int score1;//程序课
	int score2;//高数课

	//全参构造
	Student(const CString& m_name, const CString& m_id, int score1, int score2);
	//默认构造
	Student();
};

Student.cpp

#include "pch.h"
#include "Student.h"

Student::Student(const CString& m_name, const CString& m_id, int score1, int score2)
	: m_name(m_name),
	  m_id(m_id),
	  score1(score1),
	  score2(score2) {
}

Student::Student() {
}

Management.h

#pragma once
#include<fstream>
#include "Student.h"
#include <string>
#include "ADD.h"
#include <vector>
using namespace std;
constexpr auto INIT_SIZE = 10;	//默认大小是10个;
constexpr auto INCREASE_SIZE = 10;	//数组满后扩充的容量;

class Management {
public:
	Student* all = NULL; //存放学生数据的数组,在构造函数中对其初始化
	int capacity = 0;   //数组当前容量
	int size = 0;       //当前大小
	//--------------------------------------------------------
	//基本功能
	void add(Student stu);    //添加数据
	bool del(CString id);     //根据ID删除数据
	bool hasId(CString id);   //判断id是否存在
	void sortByScore1(bool = false);      //按程序课成绩排序(false:降序,true:升序,采用了默认参数,默认降序)
	void sortByScore2(bool = false);      //按高数课成绩排序(false:降序,true:升序,默认降序)
	void sortById(bool = false);		//按学号排序(false:降序,true:升序,默认降序)
	//---------------------------------------------------------
	//根据id获取名字、分数
	CString getName(CString id);
	int getS1(CString id);	//获取程序课成绩
	int getS2(CString id);    //获取高数课成绩
	//---------------------------------------------------------
	//根据id来修改信息
	bool changeData(CString oldId, CString newName, CString newId, int S1, int S2) const;
	//---------------------------------------------------------
	//构造和析构函数
	Management();
	~Management();
	//--------------------------------------------------------
	//获取高数和程序课的vector集合(用于画统计图)
	vector<int> getMathScores() const;
	vector<int> getProgrammingScores() const;
};

Management.cpp

#include "pch.h"
#include "Management.h"


//添加数据
void Management::add(Student stu) {

	if (size >= capacity) {
		//如果当前数组存满了
		capacity += INCREASE_SIZE;
		Student* temp = new Student[capacity]; //重新创建一个容量更大的数组
		for (int i = 0; i < size; i++) {
			temp[i] = all[i]; //把旧数组的数据迁移到新数组
		}
		delete[] all; //把旧数组的空间释放掉
		all = temp; //让all指针指向新数组
	}
	all[size++] = stu; //插入数据
}

//删除数据
bool Management::del(CString id) {
	for (int i = 0; i < size; i++) {
		//寻找指定id
		if (id == all[i].m_id) {
			//让后面的元素往前移,填补中间被删除后空缺的位置
			for (int j = i; j < size - 1; ++j) {
				all[j] = all[j + 1];
			}
			--size;
			return true;
		}
	}
	//如果找不到指定的id,删除失败
	return false;
}

//判断是否存在指定的id
bool Management::hasId(CString id) {
	for (int i = 0; i < size; i++) {
		if (id == all[i].m_id)
			return true;
	}
	return false;
}


//按照程序课成绩排序(false:降序,true:升序,采用默认参数,默认降序)
void Management::sortByScore1(bool tag) {
	//采用冒泡排序算法
	if (tag) {
		//升序
		for (int i = 0; i < size; i++) {
			for (int j = 0; j < size - i - 1; ++j) {
				if (all[j].score1 > all[j + 1].score1) {
					Student temp = all[j];
					all[j] = all[j + 1];
					all[j + 1] = temp;
				}
			}
		}
	}else {
		//降序
		for (int i = 0; i < size; i++) {
			for (int j = 0; j < size - i - 1; ++j) {
				if (all[j].score1 < all[j + 1].score1) {//和上面的不同是这里< ,上面 >
					Student temp = all[j];
					all[j] = all[j + 1];
					all[j + 1] = temp;
				}
			}
		}
	}

}

//按照高数课成绩排序(false:降序,true:升序,默认降序)
void Management::sortByScore2(bool tag) {
	//采用冒泡排序算法(代码和上面的函数一样,只是把score1改成score2而已)
	if (tag) {
		//升序
		for (int i = 0; i < size; i++) {
			for (int j = 0; j < size - i - 1; ++j) {
				if (all[j].score2 > all[j + 1].score2) {
					Student temp = all[j];
					all[j] = all[j + 1];
					all[j + 1] = temp;
				}
			}
		}
	}
	else {
		//降序
		for (int i = 0; i < size; i++) {
			for (int j = 0; j < size - i - 1; ++j) {
				if (all[j].score2 < all[j + 1].score2) {//和上面的不同是这里< ,上面 >
					Student temp = all[j];
					all[j] = all[j + 1];
					all[j + 1] = temp;
				}
			}
		}
	}
}

//按学号排序(false:降序,true:升序,默认降序)
void Management::sortById(bool tag)
{
	//采用冒泡排序算法(代码和按程序课排序的函数一样,只是把score1改成m_id而已)
	if (tag) {
		//升序
		for (int i = 0; i < size; i++) {
			for (int j = 0; j < size - i - 1; ++j) {
				if (all[j].m_id > all[j + 1].m_id) {
					Student temp = all[j];
					all[j] = all[j + 1];
					all[j + 1] = temp;
				}
			}
		}
	}
	else {
		//降序
		for (int i = 0; i < size; i++) {
			for (int j = 0; j < size - i - 1; ++j) {
				if (all[j].m_id < all[j + 1].m_id) {//和上面的不同是这里< ,上面 >
					Student temp = all[j];
					all[j] = all[j + 1];
					all[j + 1] = temp;
				}
			}
		}
	}
}


CString Management::getName(CString id) {
	for (int i = 0; i < size; i++) {
		if (id == all[i].m_id)

			return all[i].m_name;
	}
	return TEXT("");
}

//获取程序课成绩
int Management::getS1(CString id) {
	for (int i = 0; i < size; i++) {
		if (id == all[i].m_id)

			return all[i].score1;
	}
	return 0;
}

//获取高数课成绩
int Management::getS2(CString id) {
	for (int i = 0; i < size; i++) {
		if (id == all[i].m_id)

			return all[i].score2;
	}
	return 0;
}

//修改信息(学号不存在返回false)
bool Management::changeData(CString oldId, CString newName, CString newId, int S1, int S2) const {
	for (int i = 0; i < size; i++) {
		if (oldId == all[i].m_id) {
			all[i].m_name = newName;
			all[i].m_id = newId;
			all[i].score1 = S1;
			all[i].score2 = S2;
			return true;
		}
	}
	return false;
}



//构造函数
Management::Management() {
	all = new Student[INIT_SIZE]; //初始化数组
	capacity = INIT_SIZE; //更新容量
	//读取文件
	ifstream ifs("data.txt", ios::in);
	if (ifs.is_open() && ifs.peek() != EOF) {
		//确保文件已经打开,并且不是空

		while (ifs.peek() != EOF) {
			//一直读到结尾为止
			string name, id;
			int score1, score2;
			ifs >> name >> id >> score1 >> score2; //逐个读取数据
			CString m_name(name.c_str());
			CString m_id(id.c_str());
			Student stu(m_name, m_id, score1, score2); //新建一个Student
			add(stu); //将Student添加到数组
		}
	}
	ifs.close(); //释放资源
}

//析构
Management::~Management() {
	//保存文件
	ofstream ofs("data.txt", ios::out);
	if (ofs.is_open()) {
		for (int i = 0; i < size; i++) {
			Student stu = all[i];
			string name(CT2A(all[i].m_name.GetBuffer())); //将CString转为string
			string id(CT2A(all[i].m_id.GetBuffer()));

			ofs << name << " " << id << " " << stu.score1 << " " << stu.score2;
			if (i != size - 1) ofs << endl;
			//最后一个数据之后不要换行,否则下次读取的时候会读取多一条空数据
		}

	}
	ofs.close();
	delete[] all;
}


vector<int> Management::getMathScores() const {
	vector<int> datas;
	for (int i = 0; i < size; i++) {
		datas.push_back(all[i].score2);
	}
	return datas;
}


vector<int> Management::getProgrammingScores() const {
	vector<int> datas;
	for (int i = 0; i < size; i++) {
		datas.push_back(all[i].score1);
	}
	return datas;
}

ADD.h(对话框类)

#pragma once
// ADD 对话框

class ADD : public CDialogEx
{
	DECLARE_DYNAMIC(ADD)

public:
	ADD(CWnd* pParent = nullptr);   // 标准构造函数
	virtual ~ADD();
	

// 对话框数据
#ifdef AFX_DESIGN_TIME
	enum { IDD = ADD_STUDENT };
#endif

protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持

	DECLARE_MESSAGE_MAP()
public:
	
	CString m_name;
	CString m_id;
	int score1;
	int score2;
	
	afx_msg void OnBnClickedButton1();
	CListBox DATA;
	afx_msg void OnBnClickedButton6();
	afx_msg void OnBnClickedButton2();
	afx_msg void OnLbnSelchangeList1();
	afx_msg void OnBnClickedButton3();
	afx_msg void OnBnClickedButton4();
	afx_msg void OnBnClickedButton5();
	afx_msg void OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized);
	afx_msg void OnBnClickedButton7();
	afx_msg void OnBnClickedButton8();
	afx_msg void OnBnClickedButton9();
	afx_msg void OnBnClickedButton10();
	afx_msg void OnBnClickedButton11();
	afx_msg void OnBnClickedButton12();
	afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
	int Search_Score1_Min;
	int Search_Score1_Max;
	int Search_Score2_Min;
	int Search_Score2_Max;
	CString Search_Name;
	CString Search_Id;
	afx_msg void OnBnClickedButton13();
};

ADD.cpp

// ADD.cpp: 实现文件
//

#include "pch.h"
#include "StudentManagerSystem.h"
#include "afxdialogex.h"
#include "ADD.h"
#include "ChildView.h"
#include<ctime>
#include <vector>


Management m; //创建Management对象

// ADD 对话框

IMPLEMENT_DYNAMIC(ADD, CDialogEx)

ADD::ADD(CWnd* pParent /*=nullptr*/)
	: CDialogEx(ADD_STUDENT, pParent)
	  , m_name(_T(""))
	  , m_id(_T(""))
	  , score1(60)
	  , score2(60), Search_Score1_Min(0)
	  , Search_Score1_Max(100)
	  , Search_Score2_Min(0)
	  , Search_Score2_Max(100)
	  , Search_Name(_T(""))
	  , Search_Id(_T("")) {
}

ADD::~ADD() = default;


void ADD::DoDataExchange(CDataExchange* pDX) {
	CDialogEx::DoDataExchange(pDX);

	DDX_Text(pDX, IDC_EDIT1, m_name);
	DDX_Text(pDX, IDC_EDIT2, m_id);
	DDX_Text(pDX, IDC_EDIT3, score1);
	DDX_Text(pDX, IDC_EDIT4, score2);

	DDX_Control(pDX, IDC_LIST1, DATA);
	DDX_Text(pDX, IDC_EDIT7, Search_Score1_Min);
	DDX_Text(pDX, IDC_EDIT8, Search_Score1_Max);
	DDX_Text(pDX, IDC_EDIT9, Search_Score2_Min);
	DDX_Text(pDX, IDC_EDIT10, Search_Score2_Max);
	DDX_Text(pDX, IDC_EDIT5, Search_Name);
	DDX_Text(pDX, IDC_EDIT6, Search_Id);
}


BEGIN_MESSAGE_MAP(ADD, CDialogEx)
	ON_BN_CLICKED(IDC_BUTTON1, &ADD::OnBnClickedButton1)
	ON_BN_CLICKED(IDC_BUTTON6, &ADD::OnBnClickedButton6)
	ON_BN_CLICKED(IDC_BUTTON2, &ADD::OnBnClickedButton2)
	ON_LBN_SELCHANGE(IDC_LIST1, &ADD::OnLbnSelchangeList1)
	ON_BN_CLICKED(IDC_BUTTON3, &ADD::OnBnClickedButton3)
	ON_BN_CLICKED(IDC_BUTTON4, &ADD::OnBnClickedButton4)
	ON_BN_CLICKED(IDC_BUTTON5, &ADD::OnBnClickedButton5)
	ON_WM_ACTIVATE()
	ON_BN_CLICKED(IDC_BUTTON7, &ADD::OnBnClickedButton7)
	ON_BN_CLICKED(IDC_BUTTON8, &ADD::OnBnClickedButton8)
	ON_BN_CLICKED(IDC_BUTTON9, &ADD::OnBnClickedButton9)
	ON_BN_CLICKED(IDC_BUTTON10, &ADD::OnBnClickedButton10)
	ON_BN_CLICKED(IDC_BUTTON11, &ADD::OnBnClickedButton11)
	ON_BN_CLICKED(IDC_BUTTON12, &ADD::OnBnClickedButton12)
	ON_WM_CREATE()
	ON_BN_CLICKED(IDC_BUTTON13, &ADD::OnBnClickedButton13)
END_MESSAGE_MAP()


// ADD 消息处理程序

// 添加按钮
void ADD::OnBnClickedButton1() {

	//判断输入的内容是否为空
	//我这样做的原因是:如果先UpdateData,那数字输入框如果为空,会弹窗
	CString cstr1;
	CString cstr2;
	CString cstr3;
	CString cstr4;
	GetDlgItem(IDC_EDIT1)->GetWindowText(cstr1);
	GetDlgItem(IDC_EDIT2)->GetWindowText(cstr2);
	GetDlgItem(IDC_EDIT3)->GetWindowText(cstr3);
	GetDlgItem(IDC_EDIT4)->GetWindowText(cstr4);
	if (cstr1 == _T("") || cstr2 == _T("") || cstr3 == _T("") || cstr4 == _T("")) {
		MessageBox(TEXT("输入的内容不能为空!"));
		return;
	}
	//==================================================================
	UpdateData(TRUE); //将控件中的数据更新到变量
	//==================================================================
	//判断输入的内容是否包含空格
	int a = m_name.Find(TEXT(" ")); //m_name空格所在的索引
	int b = m_id.Find(TEXT(" ")); //查找空格所在的索引
	if (a != -1 || b != -1) {
		//如果索引不是-1,代表输入的东西一定包含空格
		MessageBox(TEXT("输入的内容请不要包含空格!"));
		return;
	}

	//==================================================================
	//判断学号是否全为数字,并且长度为10
	if (m_id.GetLength() == 10) {
		//如果长度为10,再判断是否每一位都是数字
		for (int i = 0; i < 10; ++i) {
			if (!(m_id.GetAt(i) >= '0' && m_id.GetAt(i) <= '9')) {
				//只要碰到一个字符不是数字,添加失败
				MessageBox(TEXT("学号必须由数字组成"));
				return;
			}
		}
	}
	else {
		//如果长度不为10,添加失败
		MessageBox(TEXT("学号的长度必须为10"));
		return;
	}
	//==================================================================
	//判断成绩是否符合范围
	if (score1 < 0 || score1 > 100 || score2 < 0 || score2 > 100) {
		MessageBox(TEXT("分数必须在0到100分之间"));
		return;
	}
	//==================================================================
	//判断要添加的学号是否存在
	if (m.hasId(m_id)) {
		MessageBox(TEXT("该学号已重复"));
		return;
	}
	//==================================================================
	//以上条件都满足后,添加成功
	Student stu(m_name, m_id, score1, score2);
	m.add(stu);
	DATA.AddString(m_id + "==" + m_name); //让ListBox添加一条数据
	MessageBox(TEXT("添加成功"));
	DATA.SetCurSel(DATA.GetCount() - 1); //添加之后让ListBox选中刚刚添加的数据
	OnLbnSelchangeList1(); //手动触发ListBox的Change事件

}


// 删除按钮
void ADD::OnBnClickedButton2() {

	CString id;
	int i = DATA.GetCurSel(); //获取ListBox正在选中的索引
	if (i != -1) {

		DATA.GetText(i, id);
		const int index = id.Find(_T("==")); //‘==’在字符串里的位置
		id = id.Mid(0, index); //列表里显示的格式为 学号==姓名,读取==前面的,就是学号
		if (m.del(id) /*删除数据,如果学号不存在将返回false*/) {
			DATA.DeleteString(i); //列表删除对应的项
			MessageBox(TEXT("删除成功"));
			DATA.SetCurSel(i == 0 ? 0 : i - 1); //手动让ListBox选中被删除的前面一项
			OnLbnSelchangeList1(); //手动触发ListBox的Change事件
		}
		else {
			MessageBox(TEXT("该学号不存在!可能是由于数据未更新所致,请刷新列表!"));
		}
	}
	else {
		//-1代表没选中
		MessageBox(TEXT("当前没有选中任何学生,请先在左边列表中选择!"));
	}

}

/**
 * ListBox的Change事件
 * 功能:当列表框选中某些项的时候,右边显示这个学生的信息
 */
void ADD::OnLbnSelchangeList1() {
	// TODO: 在此添加控件通知处理程序代码
	int i = DATA.GetCurSel(); //获取当前ListBox选中的索引
	if (i != -1) {
		CString id;
		DATA.GetText(i, id);
		i = id.Find(_T("=="));
		id = id.Mid(0, i); //列表里显示的格式为 学号==姓名,读取==前面的,就是学号
		if (m.hasId(id)) {
			m_name = m.getName(id);
			m_id = id;
			score1 = m.getS1(id);
			score2 = m.getS2(id);
			UpdateData(FALSE); //将变量的值同步到控件
		}
		else {
			MessageBox(TEXT("该学号不存在!可能是由于数据未更新所致,请刷新列表!"));
		}
	}
}


//编辑按钮
void ADD::OnBnClickedButton3() {
	//获取ListBox当前选中的内容,以获取学号信息
	CString id; //学号
	int i = DATA.GetCurSel(); //当前列表框选中的索引
	if (i == -1) {
		MessageBox(TEXT("当前没有选中任何学生,请先在左边列表中选择!"));
		return;
	}
	DATA.GetText(i, id);
	const int index = id.Find(_T("==")); //‘==’在字符串里的位置
	id = id.Mid(0, index); //列表里显示的格式为 学号==姓名,读取==前面的,就是学号
	//==================================================================
	//检查空格和学号格式是否正确,这部分和添加数据里面的代码是一样的。
	//==================================================================
	//判断输入的内容是否为空
	//我这样做的原因是:如果先UpdateData,那数字输入框如果为空,会弹窗
	CString cstr1;
	CString cstr2;
	CString cstr3;
	CString cstr4;
	GetDlgItem(IDC_EDIT1)->GetWindowText(cstr1);
	GetDlgItem(IDC_EDIT2)->GetWindowText(cstr2);
	GetDlgItem(IDC_EDIT3)->GetWindowText(cstr3);
	GetDlgItem(IDC_EDIT4)->GetWindowText(cstr4);
	if (cstr1 == _T("") || cstr2 == _T("") || cstr3 == _T("") || cstr4 == _T("")) {
		MessageBox(TEXT("输入的内容不能为空!"));
		return;
	}
	//==================================================================
	UpdateData(TRUE); //将控件中的数据更新到变量
	//==================================================================
	//判断输入的内容是否包含空格
	int a = m_name.Find(TEXT(" ")); //m_name空格所在的索引
	int b = m_id.Find(TEXT(" ")); //查找空格所在的索引
	if (a != -1 || b != -1) {
		//如果索引不是-1,代表输入的东西一定包含空格
		MessageBox(TEXT("输入的内容请不要包含空格!"));
		return;
	}

	//==================================================================
	//判断学号是否全为数字,并且长度为10
	if (m_id.GetLength() == 10) {
		//如果长度为10,再判断是否每一位都是数字
		for (int i = 0; i < 10; ++i) {
			if (!(m_id.GetAt(i) >= '0' && m_id.GetAt(i) <= '9')) {
				//只要碰到一个字符不是数字,添加失败
				MessageBox(TEXT("学号必须由数字组成"));
				return;
			}
		}
	}
	else {
		//如果长度不为10,添加失败
		MessageBox(TEXT("学号的长度必须为10"));
		return;
	}
	//==================================================================
	//判断成绩是否符合范围
	if (score1 < 0 || score1 > 100 || score2 < 0 || score2 > 100) {
		MessageBox(TEXT("分数必须在0到100分之间"));
		return;
	}
	//==================================================================
	//判断是否需要修改学号
	//不需要:m_id和当前的学号一致,不作修改
	//需要:m_id和当前的学号不一致,下一步还需判断修改后的学号是否重复
	if (m_id != id && m.hasId(m_id)) {
		//如果m_id != id,代表希望修改学号,但是修改学号需要判断学号有没有重复
		MessageBox(TEXT("该学号已重复"));
		return;
	}
	//===================================================================
	//以上条件都满足之后,
	//1.原来的学号存在:修改成功
	//2.原来的学号不存在:修改失败
	if (m.changeData(id, m_name, m_id, score1, score2)) {
		//修改数据,如果学号不存在将返回false

		OnBnClickedButton6(); //手动触发“刷新列表”的单击事件
		MessageBox(TEXT("修改成功"));

		//刷新列表之后,ListBox又会变为不选中状态
		//自动选回刚刚选中的那个
		for (int j = 0; j < DATA.GetCount(); j++) {
			CString cs;
			DATA.GetText(j, cs);
			if (cs.Mid(0, cs.Find(_T("=="))) == m_id) {
				DATA.SetCurSel(j);
				break;
			}
		}
	}
	else {
		MessageBox(TEXT("该学号不存在!可能是由于数据未更新所致,请刷新列表!"));
	}

}


//按程序课降序按钮
void ADD::OnBnClickedButton4() {

	m.sortByScore1(); //排序
	CString id;
	int i = DATA.GetCurSel(); //记住排序之前选中的索引
	OnBnClickedButton6(); //刷新列表
	//刷新列表之后,ListBox又会变为不选中状态
	//需要自动选回刚刚选中的那个(如果i==-1,代表原来并没有选择)
	if (i != -1) {
		DATA.GetText(i, id);
		for (int j = 0; j < DATA.GetCount(); j++) {
			CString cs;
			DATA.GetText(j, cs);
			if (cs.Mid(0, cs.Find(_T("=="))) == m_id) {
				DATA.SetCurSel(j);
				break;
			}
		}
	}
	OnLbnSelchangeList1(); //触发ListBox的事件,让右边能显示选中的学生的个人信息
}

//按高数课降序按钮(这部分代码和按程序课降序的一样,唯一不同的地方是第一行的排序函数不同)
void ADD::OnBnClickedButton5() {
	m.sortByScore2();
	CString id;
	int i = DATA.GetCurSel();
	OnBnClickedButton6();
	if (i != -1) {
		DATA.GetText(i, id);
		for (int j = 0; j < DATA.GetCount(); j++) {
			CString cs;
			DATA.GetText(j, cs);
			if (cs.Mid(0, cs.Find(_T("=="))) == m_id) {
				DATA.SetCurSel(j);
				break;
			}
		}
	}
	OnLbnSelchangeList1();
}

//刷新列表按钮
void ADD::OnBnClickedButton6() {
	DATA.ResetContent(); //清空列表
	for (int i = 0; i < m.size; i++) {
		CString str;
		str = m.all[i].m_id;
		str += "==";
		str += m.all[i].m_name;
		DATA.AddString(str); //读取数组,逐个插入数据
	}
}

//窗口激活消息(显示窗口后刷新列表)
void ADD::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized) {
	CDialogEx::OnActivate(nState, pWndOther, bMinimized);

	// TODO: 在此处添加消息处理程序代码
	srand((unsigned long)time(NULL)); //随机数种子
	OnBnClickedButton6();
}


//按程序课升序(这部分代码和降序的一样,唯一不同的地方是第一行的排序函数给了true参数)
void ADD::OnBnClickedButton7() {
	m.sortByScore1(true); //排序
	CString id;
	int i = DATA.GetCurSel(); //记住排序之前选中的索引
	OnBnClickedButton6(); //刷新列表
	//刷新列表之后,ListBox又会变为不选中状态
	//自动选回刚刚选中的那个(如果i==-1,代表原来并没有选择)
	if (i != -1) {
		DATA.GetText(i, id);
		for (int j = 0; j < DATA.GetCount(); j++) {
			CString cs;
			DATA.GetText(j, cs);
			if (cs.Mid(0, cs.Find(_T("=="))) == m_id) {
				DATA.SetCurSel(j);
				break;
			}
		}
	}
	OnLbnSelchangeList1(); //触发ListBox的事件,让右边能显示选中的学生的个人信息
}


//按高数课降序(这部分代码和降序的一样,唯一不同的地方是第一行的排序函数给了true参数)
void ADD::OnBnClickedButton8() {
	m.sortByScore2(true);
	CString id;
	int i = DATA.GetCurSel();
	OnBnClickedButton6();
	if (i != -1) {
		DATA.GetText(i, id);
		for (int j = 0; j < DATA.GetCount(); j++) {
			CString cs;
			DATA.GetText(j, cs);
			if (cs.Mid(0, cs.Find(_T("=="))) == m_id) {
				DATA.SetCurSel(j);
				break;
			}
		}
	}
	OnLbnSelchangeList1();
}


//按学号降序(和上面的排序是一样的)
void ADD::OnBnClickedButton9() {
	m.sortById(); //排序
	CString id;
	int i = DATA.GetCurSel(); //记住排序之前选中的索引
	OnBnClickedButton6(); //刷新列表
	//刷新列表之后,ListBox又会变为不选中状态
	//自动选回刚刚选中的那个(如果i==-1,代表原来并没有选择)
	if (i != -1) {
		DATA.GetText(i, id);
		for (int j = 0; j < DATA.GetCount(); j++) {
			CString cs;
			DATA.GetText(j, cs);
			if (cs.Mid(0, cs.Find(_T("=="))) == m_id) {
				DATA.SetCurSel(j);
				break;
			}
		}
	}
	OnLbnSelchangeList1(); //触发ListBox的事件,让右边能显示选中的学生的个人信息
}

//按学号升序
void ADD::OnBnClickedButton10() {
	m.sortById(true); //排序
	CString id;
	int i = DATA.GetCurSel(); //记住排序之前选中的索引
	OnBnClickedButton6(); //刷新列表
	//刷新列表之后,ListBox又会变为不选中状态
	//自动选回刚刚选中的那个(如果i==-1,代表原来并没有选择)
	if (i != -1) {
		DATA.GetText(i, id);
		for (int j = 0; j < DATA.GetCount(); j++) {
			CString cs;
			DATA.GetText(j, cs);
			if (cs.Mid(0, cs.Find(_T("=="))) == m_id) {
				DATA.SetCurSel(j);
				break;
			}
		}
	}
	OnLbnSelchangeList1(); //触发ListBox的事件,让右边能显示选中的学生的个人信息
}

//面向对象分数随机
void ADD::OnBnClickedButton11() {
	UpdateData(TRUE);
	score1 = rand() % 101; //0-100之间的随机数
	UpdateData(FALSE); //数据更新到控件
}


//高数分数随机
void ADD::OnBnClickedButton12() {
	UpdateData(TRUE);
	score2 = rand() % 101; //0-100之间的随机数
	UpdateData(FALSE); //数据更新到控件
}


int ADD::OnCreate(LPCREATESTRUCT lpCreateStruct) {
	if (CDialogEx::OnCreate(lpCreateStruct) == -1)
		return -1;

	// TODO:  在此添加您专用的创建代码

	return 0;
}

//查询按钮
void ADD::OnBnClickedButton13() {
	//判断输入的成绩范围是不是为空的
	CString cstr1;
	CString cstr2;
	CString cstr3;
	CString cstr4;
	GetDlgItem(IDC_EDIT7)->GetWindowText(cstr1);
	GetDlgItem(IDC_EDIT8)->GetWindowText(cstr2);
	GetDlgItem(IDC_EDIT9)->GetWindowText(cstr3);
	GetDlgItem(IDC_EDIT10)->GetWindowText(cstr4);
	if (cstr1 == _T("") || cstr2 == _T("") || cstr3 == _T("") || cstr4 == _T("")) {
		MessageBox(TEXT("输入的成绩的范围不能为空!"));
		return;
	}
	//==============================================================
	UpdateData(TRUE);
	//进行数据的合法性判断
	if (Search_Score1_Max > 100 || Search_Score1_Max < 0 || Search_Score1_Min > 100 || Search_Score1_Min < 0 ||
		Search_Score2_Max > 100 || Search_Score2_Max < 0 || Search_Score2_Min > 100 || Search_Score2_Min < 0) {
		MessageBox(TEXT("输入的成绩的范围必须在区间[0,100]内!"));
		return;
	}
	if (Search_Score1_Min > Search_Score1_Max || Search_Score2_Min > Search_Score2_Max) {
		MessageBox(TEXT("输入的成绩最小值不能比最大值大!"));
		return;
	}
	//===============================================================
	//开始查询
	std::vector<Student> result;
	for (int i = 0; i < m.size; ++i) {
		Student stu = m.all[i];
		if (stu.m_name.Find(Search_Name) != -1 &&
			stu.m_id.Find(Search_Id) != -1 &&
			stu.score1 >= Search_Score1_Min &&
			stu.score1 <= Search_Score1_Max &&
			stu.score2 >= Search_Score2_Min &&
			stu.score2 <= Search_Score2_Max) {
			//满足条件
			result.push_back(stu);
		}
	}
	CString str;
	for (auto stu : result) {
		CString cScore1;
		CString cScore2;
		cScore1.Format(_T("%d"), stu.score1);
		cScore2.Format(_T("%d"), stu.score2);
		str += _T("学生姓名:") + stu.m_name + _T("\t学号:") + stu.m_id + _T("\t程序课成绩:") + cScore1 + _T("\t高数课成绩:") + cScore2 + _T("\n");
	}
	CString cLength;
	cLength.Format(_T("%d"), result.size());
	MessageBox(str,_T("共查询到") + cLength + _T("条结果"));
}

ChildView.h

// ChildView.h: CChildView 类的接口
//

#pragma once
#include "ADD.h"
#include "Management.h"
#include<vector>
using namespace std;


// CChildView 窗口

class CChildView : public CWnd
{
// 构造
public:
	CChildView();
	ADD add;

// 特性
public:

// 操作
public:

// 重写
	protected:
	virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

// 实现
public:
	virtual ~CChildView();

	// 生成的消息映射函数
protected:
	afx_msg void OnPaint();
	DECLARE_MESSAGE_MAP()
public:
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);

	void DrawBar(CDC *dc, std::vector<int> db,bool left = true) const;
	void DrawLine(CDC *dc, std::vector<int> db,bool left = true) const;
	void OnDraw(CDC* pDC);
};

ChildView.cpp

// ChildView.cpp: CChildView 类的实现
//

#include "pch.h"
#include "framework.h"
#include "ChildView.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif

extern Management m; //需要和ADD.cpp共用一个Management对象,所以这里要声明extern
// CChildView

CChildView::CChildView() {
}

CChildView::~CChildView() {
}


BEGIN_MESSAGE_MAP(CChildView, CWnd)
	ON_WM_PAINT()
	ON_WM_LBUTTONDOWN()
	ON_WM_DRAWITEM()
	ON_WM_DRAWITEM()
END_MESSAGE_MAP()


// CChildView 消息处理程序

BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs) {
	if (!CWnd::PreCreateWindow(cs))
		return FALSE;

	cs.dwExStyle |= WS_EX_CLIENTEDGE;
	cs.style &= ~WS_BORDER;
	cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS,
	                                   ::LoadCursor(nullptr, IDC_ARROW), reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1),
	                                   nullptr);

	return TRUE;
}


void CChildView::OnPaint() {
	CPaintDC dc(this); // 用于绘制的设备上下文
	// TODO: 在此处添加消息处理程序代码
	// 不要为绘制消息而调用 CWnd::OnPaint()
	OnDraw(&dc);
}

//画条状图,db:vector类的集合,里面存放所有要统计的数据
void CChildView::DrawBar(CDC* dc, std::vector<int> db,bool left) const {//left:是否在左边
	CRect rc; //外面的矩形
	GetClientRect(rc); //获取CWnd客户区的尺寸。(让rc占满窗口)
	rc.DeflateRect(30, 15, 10, rc.Height() / 2+15); //x1,y1,x2,y2
	if(left) {
		//如果是左边,相应修改一下x2的值
		rc.right = rc.Width() / 2 - 15;
		dc->TextOutW(rc.left, 0, TEXT("高数课成绩统计"));
	}else {
		rc.left = rc.Width() / 2 + 15;
		dc->TextOutW(rc.left, 0, TEXT("程序课成绩统计"));
	}
	CBrush brush1(HS_BDIAGONAL, RGB(96, 192, 255)); //向下阴影(从左到右)在45度(右斜),蓝色
	CBrush brush2(HS_FDIAGONAL, RGB(96, 192, 255)); //向上阴影(从左到右)在45度(左斜),蓝色
	CPen pen(PS_INSIDEFRAME, 2, RGB(96, 192, 255)); //
	int n = 5; //定义有五个区间
	int width = rc.Width() / n; //一个区间的宽度

	int s[] = {0, 0, 0, 0, 0}; //各区间人数
	//统计各区间人数
	for (int i = 0; i < db.size(); i++) {
		if (db[i] < 60) s[0] ++;
		else if (db[i] >= 60 && db[i] < 70) s[1]++;
		else if (db[i] >= 70 && db[i] < 80) s[2]++;
		else if (db[i] >= 80 && db[i] < 90) s[3]++;
		else s[4]++;
	}

	//找出人数最大的区间
	int max_s = s[0];
	for (int i = 0; i < n; i++)
		if (max_s < s[i]) max_s = s[i];

	int per_Height = rc.Height() / max_s; //算出一个人代表的矩形高度

	CRect ps_rect(rc); //直方图的矩形
	ps_rect.right = ps_rect.left + width;

	dc->SelectObject(&pen); //选择画笔
	CString str[5] = {_T("<60"),_T("60-70"),_T("70-80"),_T("80-90"),_T(">=90")}; //将每个区间的注释先存储
	for (int i = 0; i < n; i++) {
		ps_rect.top = ps_rect.bottom - per_Height * s[i]; //该区间的高度
		if (i % 2) dc->SelectObject(&brush2);
		else dc->SelectObject(&brush1); //每隔一个区间用不同的斜线,左斜或右斜
		dc->Rectangle(ps_rect); //绘制矩形

		if (s[i] > 0) {
			CString str1;
			str1.Format(_T("%d人"), s[i]); //设置str1的内容
			dc->DrawText(str1, ps_rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); //在矩形中间显示人数
		}
		//输出注释在矩形下面
		dc->TextOutW((ps_rect.left + ps_rect.right) / 2 - 15, ps_rect.bottom+5 , str[i]);
		ps_rect.OffsetRect(width, 0); //移动矩形框的位置,水平方向移动width
	}

}

//折线图
void CChildView::DrawLine(CDC* dc, std::vector<int> db,bool left) const {
	CRect rc; //最外面的矩形
	GetClientRect(rc); //让矩形占满窗口
	rc.DeflateRect(30, rc.Height() / 2 + 15, 10, 15); x1,y1,x2,y2
	if (left) {
		//如果是左边,相应修改一下x2的值
		rc.right = rc.Width() / 2 - 15;
	}
	else {
		rc.left = rc.Width() / 2 + 15;
	}
	int gridXnums = db.size(); //列数
	int gridYnums = 10; //行数
	int dx = rc.Width() / gridXnums; //每一个格子的宽度
	int dy = rc.Height() / gridYnums; //每一个格子的高度


	CRect gridRect(rc.left, rc.top, rc.left + dx * gridXnums, rc.top + dy * gridYnums);

	CPen gridPen(PS_DASHDOTDOT, 1, RGB(96, 192, 255)); //获取蓝色的点状画笔
	dc->SelectObject(&gridPen); //选择画笔
	//-------------------------------------------------------------------
	//画格子
	for (int i = 0; i <= gridXnums; i++) {
		dc->MoveTo(gridRect.left + i * dx, gridRect.bottom);
		dc->LineTo(gridRect.left + i * dx, gridRect.top);
	}
	for (int j = 0; j <= gridYnums; j++) {
		dc->MoveTo(gridRect.left, gridRect.top + j * dy);
		dc->LineTo(gridRect.right, gridRect.top + j * dy);
		CString str;
		str.Format(_T("%d"), 100 - 10 * j);
		dc->TextOutW(rc.left - 25, gridRect.top + j * dy - 10, str); //标注纵坐标的值
	}
	//-------------------------------------------------------------------

	gridPen.Detach(); //官方解释:从CGdiObject对象分离Windows GDI对象,并将句柄返回给Windows GDI对象。
	//但是不加这一行会报异常

	CPen linePen(PS_SOLID, 2, RGB(255, 0, 0)); //创建一个红色实线画笔,用于画线
	dc->SelectObject(&linePen); //选择画笔

	int nCount = db.size(); //一共多少个数据
	int deta = gridRect.Width() / nCount; //每一个数据所占的宽度
	POINT* pt = new POINT[nCount];

	for (int i = 0; i < nCount; i++) {
		pt[i].x = gridRect.left + i * deta;
		pt[i].y = gridRect.bottom - (int)(db[i] / 100.0 * gridRect.Height());

		CString score;
		score.Format(_T("%d"), db[i]); //设置score的内容
		// dc->TextOutW(pt[i].x - 5, gridRect.bottom + 5, score); //显示分数(在下面)
	}
	dc->Polyline(pt, nCount); //画线
	delete[] pt;
	pt = nullptr;
}


void CChildView::OnDraw(CDC* pDC) {
	vector<int> programmingScores = m.getProgrammingScores();
	vector<int> mathScores = m.getMathScores();
	

	if (!mathScores.empty() && !programmingScores.empty()) {
		//只有数据不为空了才去画图
		DrawBar(pDC, mathScores,true); //左边条形图画高数
		DrawBar(pDC, programmingScores,false); //右边条形图画程序
		DrawLine(pDC, mathScores,true); //左边折线图画高数
		DrawLine(pDC, programmingScores,false); //右边折线图画程序
	}
}

//左
void CChildView::OnLButtonDown(UINT nFlags, CPoint point) {

	CWnd::OnLButtonDown(nFlags, point);

	//弹出对话框
	add.DoModal();

	//重绘统计图
	Invalidate(); // 重画窗口。
	OnPaint();
}

五、程序运行时的效果图

  1. 统计图

在这里插入图片描述

  1. 数据管理
    在这里插入图片描述

  2. 添加学生时,姓名、学号或成绩为空
    在这里插入图片描述

  3. 添加学生时,学号不是数字或者长度不为10

在这里插入图片描述
在这里插入图片描述

  1. 如果姓名或学号输入了空格
    在这里插入图片描述
    在这里插入图片描述

  2. 输入的成绩越界
    在这里插入图片描述

  3. 成功添加数据
    在这里插入图片描述

  4. 成功删除数据
    在这里插入图片描述

  5. 成功修改数据(容错条件和添加数据一样,不再演示)
    在这里插入图片描述

  6. 按成绩排序(从折线图看效果)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  7. 数据查询,查询全部
    在这里插入图片描述

  8. 数据查询,查询高数课和程序课成绩都大于80分的
    在这里插入图片描述

  9. 数据查询,查询姓名里带有“刘”字的
    在这里插入图片描述

  10. 查询学号里带有2的
    在这里插入图片描述

六、实验结果分析

我在实验中遇到的问题和解决方法

  1. 对话框初始化时程序崩溃,如下图:
    在这里插入图片描述

本来我是打算在对话框一打开就自动加载数据,而不需要手动加载数据,所以我在对话框的OnCreate()函数里进行列表的初始化操作,才会发生这样子的程序奔溃,经过我后面的尝试,把初始化代码放到OnActivate()函数里才解决问题。问题产生的原因我不太清楚,我个人猜测是OnCreate函数在窗口还没完全显示的时候执行,而OnActivate会在窗口显示完成之后才执行,应该是因为窗口还没显示的时候把数据添加到列表导致的问题。

  1. Management对象的问题
    因为我所有的学生数据都存放在Management里的all数组里,所以我需要在对话框ADD类和单文档绘图的View类里共用一个Management对象,一开始我是把对象创建在.h文件中,结果造成了一些错误(无法解析的外部符号),于是我按照网上的办法,把对象放在.cpp文件中。但是新的问题出现了,我本以为多个cpp文件如果共用一个变量,只需在一个.cpp中包含另一个.cpp就行了,结果我在一个debug中,发现Management对象竟然创建了5次,这时我才意识到多个cpp中的Management并不是共用的,后来我的解决办法是在View.cpp中的Management加extern修饰符。

  2. 数据文件保存和读取问题
    我在Management类的构造和析构函数中对数据文件进行读取和保存,我每向文件输出完一条学生信息,我都会换行,但是在读取的时候会莫名地多出一条空白数据,经过我的研究,是因为输出最后一行数据再回车导致的问题。我的解决办法是在向文件输出时加个if判断,如果这条数据不是最后一条数据,则再输出回车。

  3. 关于成绩输入框的弹窗问题
    我成绩输入框创建变量的时候,类型设置为int,当执行UpdataData函数的时候,如果里面不是数字或者为空,会弹出这样的一个框
    在这里插入图片描述

本来这样的提示是没什么问题的,但是弹出框了之后,如果是添加数据,则照样会添加成功,只是成绩都是默认值0,但这样子就不符合逻辑了!我的解决办法是:先在把编辑框的Number属性改成True,保证了输入框无法输入数字,然后就还剩输入为空的情况,我就通过以下代码,在UpdateData之前检查输入是否为空:
在这里插入图片描述

收获

  1. 更加熟悉MFC编程,学会了在View类的绘图方法,学会了基于对话框的很多内容。
  2. 重新复习了上个学期所学的冒泡排序、动态数组等知识。
  3. 领悟了CString与string的区别,并学会了CString与string的互相转换、其他基本数据类型转换成CString。
  4. 掌握了面向对象的编程思想。
  • 10
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值