学生成绩管理系统是教育信息化中最基础也最实用的工具之一。作为一名有多年教学管理经验的开发者,我深知传统Excel表格管理成绩的痛点:数据容易出错、统计功能有限、无法直观展示分析结果。这次我决定用C++结合EasyX图形库,打造一个兼具数据处理能力和可视化界面的解决方案。
选择C++主要基于三点考量:首先,它具备处理大规模数据的高效性,特别适合学校级别的成绩管理;其次,C++的面向对象特性让系统架构更清晰;最后,作为教学常用语言,方便其他教师二次开发。而EasyX的加入则解决了控制台程序交互体验差的问题,让非技术人员也能轻松操作。
系统需要实现的核心功能包括:
经过对比测试,最终选用Visual Studio 2019作为开发环境。相较于Code::Blocks等开源IDE,VS对Windows平台的支持更完善,调试工具也更强大。特别在图形程序开发时,其集成的调试器能准确定位绘图相关的内存问题。
安装时需注意勾选"使用C++的桌面开发"工作负载,并确保Windows 10 SDK版本不低于10.0.18362.0。EasyX库对SDK版本有特定要求,版本不匹配会导致绘图函数异常。
EasyX是针对C++的轻量级图形库,其API设计类似TC的graphics.h,但支持现代Windows系统。从官网下载最新版(2022版)后,只需将对应的.h和.lib文件放入VS的包含目录即可。
配置时容易踩的坑:
重要提示:EasyX暂不支持高DPI缩放,在4K屏上开发时需将显示缩放设置为100%,否则会出现坐标错位问题。
采用MVC模式分层设计,主要类结构如下:
cpp复制class Student { // 数据模型
private:
string id;
string name;
map<string, float> scores; // 科目-成绩键值对
public:
// 访问器与方法...
};
class DataManager { // 控制器
private:
vector<Student> students;
sqlite3* db;
public:
bool loadFromDB();
bool saveToDB();
// 增删改查方法...
};
class GUI { // 视图
private:
HWND hwnd;
void drawMainMenu();
void drawScoreChart();
public:
void initWindow();
void eventLoop();
};
排名算法采用改进的快速排序,处理相同分数时保持原始顺序:
cpp复制void quickSort(vector<Student>& arr, int left, int right, string subject) {
if (left >= right) return;
float pivot = arr[(left+right)/2].getScore(subject);
int i = left, j = right;
while (i <= j) {
while (arr[i].getScore(subject) > pivot) i++;
while (arr[j].getScore(subject) < pivot) j--;
if (i <= j) {
swap(arr[i], arr[j]);
i++; j--;
}
}
quickSort(arr, left, j, subject);
quickSort(arr, i, right, subject);
}
基于EasyX封装柱状图组件:
cpp复制void drawBarChart(int x, int y, int width, int height,
const map<string, float>& data) {
// 计算比例尺
float maxVal = max_element(data.begin(), data.end(),
[](auto& a, auto& b) { return a.second < b.second; })->second;
float scale = height / (maxVal * 1.2f);
// 绘制坐标轴
setlinecolor(WHITE);
line(x, y, x, y + height);
line(x, y + height, x + width, y + height);
// 绘制柱状图
int barWidth = width / (data.size() * 1.5f);
int posX = x + 20;
for (const auto& [name, value] : data) {
int barHeight = value * scale;
setfillcolor(HSVtoRGB(rand()%360, 0.7, 0.8));
fillrectangle(posX, y + height - barHeight,
posX + barWidth, y + height);
// 显示数值标签
char text[32];
sprintf(text, "%.1f", value);
outtextxy(posX, y + height - barHeight - 20, text);
posX += barWidth * 1.5;
}
}
SQLite表结构设计:
sql复制CREATE TABLE students (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
class TEXT
);
CREATE TABLE scores (
student_id TEXT,
subject TEXT,
score REAL,
PRIMARY KEY (student_id, subject),
FOREIGN KEY (student_id) REFERENCES students(id)
);
使用ORM简化数据库操作:
cpp复制class DBHelper {
public:
static int callback(void* data, int argc, char** argv, char** colName) {
auto result = static_cast<vector<map<string, string>>*>(data);
map<string, string> row;
for (int i = 0; i < argc; i++) {
row[colName[i]] = argv[i] ? argv[i] : "NULL";
}
result->push_back(row);
return 0;
}
static bool execute(sqlite3* db, const string& sql,
vector<map<string, string>>& result) {
char* errMsg = nullptr;
result.clear();
if (sqlite3_exec(db, sql.c_str(), callback, &result, &errMsg) != SQLITE_OK) {
cerr << "SQL error: " << errMsg << endl;
sqlite3_free(errMsg);
return false;
}
return true;
}
};
采用经典的顶部菜单+左侧导航+中央内容区布局:
cpp复制void GUI::drawMainMenu() {
// 背景色
setbkcolor(RGB(50, 50, 80));
cleardevice();
// 顶部菜单
setfillcolor(RGB(70, 70, 100));
fillrectangle(0, 0, getwidth(), 40);
// 左侧导航
setfillcolor(RGB(60, 60, 90));
fillrectangle(0, 40, 200, getheight());
// 绘制菜单项
settextcolor(WHITE);
settextstyle(20, 0, "微软雅黑");
outtextxy(20, 10, "学生成绩管理系统 v1.0");
const char* menus[] = {"学生管理", "成绩录入", "统计分析", "数据导出"};
for (int i = 0; i < 4; i++) {
if (currentMenu == i) {
setfillcolor(RGB(80, 80, 120));
fillrectangle(0, 80 + i*50, 200, 130 + i*50);
}
outtextxy(20, 90 + i*50, menus[i]);
}
}
实现通用的文本输入框:
cpp复制class InputBox {
private:
int x, y, width, height;
string text;
bool isFocus = false;
public:
InputBox(int x, int y, int w, int h) : x(x), y(y), width(w), height(h) {}
void draw() {
setfillcolor(isFocus ? RGB(100,100,100) : RGB(70,70,70));
fillrectangle(x, y, x+width, y+height);
settextcolor(WHITE);
settextstyle(16, 0, "宋体");
RECT r = {x+5, y, x+width-5, y+height};
drawtext(text.c_str(), &r, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
if (isFocus) {
// 绘制光标
static time_t last = clock();
if ((clock() - last) % 1000 < 500) {
int tx = x+5+textwidth(text.substr(0, cursorPos).c_str());
line(tx, y+5, tx, y+height-5);
}
}
}
bool checkClick(int mx, int my) {
isFocus = (mx >= x && mx <= x+width && my >= y && my <= y+height);
return isFocus;
}
void inputChar(char ch) {
if (isFocus) {
if (ch == '\b' && !text.empty()) {
text.pop_back();
} else if (isprint(ch)) {
text += ch;
}
}
}
};
EasyX默认使用英文字符集,显示中文需要特殊处理:
cpp复制void setChineseFont(int height = 16) {
static LOGFONT font;
GetObject(GetStockObject(SYSTEM_FONT), sizeof(LOGFONT), &font);
wcscpy_s(font.lfFaceName, L"微软雅黑");
font.lfHeight = height;
font.lfQuality = ANTIALIASED_QUALITY;
HFONT hFont = CreateFontIndirect(&font);
SelectObject(GetImageHDC(), hFont);
}
当处理超过1000条记录时,采用以下优化策略:
虽然主要面向Windows,但通过抽象接口保持核心逻辑可移植:
cpp复制class IDatabase {
public:
virtual bool connect(const string& path) = 0;
virtual bool query(const string& sql, vector<Student>& result) = 0;
// ...
};
class SQLiteImpl : public IDatabase { /*...*/ };
// 未来可添加MySQLImpl等
当前系统已实现基础功能,还可以进一步扩展:
一个实用的技巧是使用Git管理版本迭代。我为项目创建了三个主要分支:
每次新增功能都从dev拉取特性分支,开发完成后通过Pull Request合并,确保代码质量。