{"id":16132877,"url":"https://github.com/vvbbnn00/usst-datastruct_coursesystem","last_synced_at":"2025-10-23T19:30:56.392Z","repository":{"id":195331751,"uuid":"684243174","full_name":"vvbbnn00/USST-Datastruct_CourseSystem","owner":"vvbbnn00","description":"上海理工大学数据结构课程设计项目：课程信息管理系统","archived":false,"fork":false,"pushed_at":"2023-09-17T11:10:06.000Z","size":692,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2023-09-17T14:10:35.483Z","etag":null,"topics":["c","datastructure","homework"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vvbbnn00.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-08-28T18:36:44.000Z","updated_at":"2023-09-17T14:10:53.975Z","dependencies_parsed_at":"2023-09-17T14:26:26.021Z","dependency_job_id":null,"html_url":"https://github.com/vvbbnn00/USST-Datastruct_CourseSystem","commit_stats":null,"previous_names":["vvbbnn00/usst-datastruct_coursesystem"],"tags_count":null,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vvbbnn00%2FUSST-Datastruct_CourseSystem","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vvbbnn00%2FUSST-Datastruct_CourseSystem/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vvbbnn00%2FUSST-Datastruct_CourseSystem/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vvbbnn00%2FUSST-Datastruct_CourseSystem/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vvbbnn00","download_url":"https://codeload.github.com/vvbbnn00/USST-Datastruct_CourseSystem/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":219868019,"owners_count":16555810,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c","datastructure","homework"],"created_at":"2024-10-09T22:35:01.461Z","updated_at":"2025-10-23T19:30:50.939Z","avatar_url":"https://github.com/vvbbnn00.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# USST-Datastruct_CourseSystem\n\n`上海理工大学数据结构课程设计：课程信息管理系统`的设计实现。\n\n## 项目要求\n\n为某学校计算机专业开发一个课程信息管理软件，该软件有三类 用户：管理员、教师和学生，其分别具有如下功能\n\n管理员：\n\n1. 创建计算机专业课程信息\n2. 添加计算机专业课程信息\n3. 删除计算机专业课程信息\n4. 统计计算机专业开设课程的总数\n5. 查询每门课程修课人数\n6. 浏览计算机专业的课程信息\n\n教师：\n\n1. 查询自己带的课程信息\n2. 查询自己带的班级学生信息\n3. 输入课程成绩\n4. 修改课程成绩\n5. 统计课程 90 分以上的人数、80\\~90 分的人数、60\\~80 分的人数、60 分以下的人数\n\n学生：\n\n1. 查询课程信息\n2. 报名课程学习\n3. 取消课程学习\n4. 查询课程成绩\n5. 浏览所有课程信息\n\n其中，课程信息至少包含：课程号、课程名、课程的学分、任课教师、上课时间、上课地点、考试成绩；\n\n学生信息至少包含：学号、姓名、联系方式。\n\n## 项目设计\n\n### 数据结构\n\n通过分析系统功能，我们确定涉及到的操作对象主要有三类：用户（User）、课程（Course）和学生选课（CourseSelection）。\n\n对于用户（User）实体，我们选择使用顺序结构来组织各个属性字段，并进一步采用有序集合来存储多个用户实体的数据。具体的逻辑结构形式如图1-2-1所示：\n\n![img.png](images/img.png)\n\n在课程（Course）实体方面，我们也采用顺序数据结构以组织各个属性字段。值得注意的是，其中的“开课教师”属性字段专门用于存储对应的教师用户实体。与用户实体类似，我们也采用有序集合数据结构来汇聚和存储多个课程实体。该逻辑结构的具体形式如图1-2-2所展示。\n\n![img_1.png](images/img_1.png)\n\n在学生选课（CourseSelection）实体的设计方面，我们沿用与课程（Course）实体类似的数据存储结构。在“选课学生”和“选课课程”属性字段中，我们分别存储对应的学生和课程实体。该逻辑结构的具体形式如图1-2-3所示。\n\n![img_2.png](images/img_2.png)\n\n这种设计方案便于实施各种查询和修改操作，同时也为将来可能的功能扩展提供了灵活性。\n\n为了实现多样的查询功能，包括基于教师身份查询所教授课程，以及基于学生身份查询已选课程等，同时优化代码的可重用性与可维护性，我们采取了一种策略，即将用于查询的数据结构（称为通用索引，Index）与各个实体类型（User、Course、CourseSelection）进行解耦。在这里，我们以实体的id作为主键，其他必要信息作为次要键，并通过通用索引创建多级索引。索引同样采用顺序结构组织各个属性字段，并使用有序集合存储多个索引的实体数据。具体的逻辑结构形式如图1-2-4所示。\n\n![img_3.png](images/img_3.png)\n\n在前述通用索引结构的基础上，我们设计了一系列专门的索引，以满足系统不同方面的需求。具体的索引结构、功能以及所存储的键（Key）和值（Value）的类型如表1-2-1所示。\n\n| 索引名称                            | 键（Key）                         | 链表内值（Value）       | 功能描述                            |\n|---------------------------------|--------------------------------|-------------------|---------------------------------|\n| user_id_Index                   | 用户ID                           | 对应的用户结构体          | 便于通过用户ID快速检索用户信息，同时缓存从文件加载的数据   |\n| user_name_Index                 | 用户姓名的哈希值                       | 用户ID              | 通过用户姓名的哈希值检索其ID                 |\n| user_empId_Index                | 用户学工号的哈希值                      | 用户ID              | 通过学工号的哈希值检索用户ID                 |\n| user_file_Index                 | 用户ID                           | 存于系统文件中的用户数据文件名   | 映射用户ID与其在文件系统中的数据文件             |\n| course_id_Index                 | 课程ID                           | 对应的课程结构体          | 便于通过课程ID快速检索课程信息，同时缓存从文件加载的数据   |\n| course_name_Index               | 课程名称的哈希值                       | 课程ID              | 通过课程名称的哈希值检索其ID                 |\n| course_teacherId_Index          | 教师ID                           | 教师所教授的课程ID        | 便于查找教师负责的所有课程                   |\n| course_file_Index               | 课程ID                           | 存于文件系统中的课程数据文件名   | 映射课程ID与其在文件系统中的数据文件             |\n| selection_id_Index              | 选课信息ID                         | 对应选课信息结构体         | 便于通过选课信息ID快速检索选课信息，同时缓存从文件加载的数据 |\n| selection_userId_Index          | 用户ID                           | 用户所选课程ID          | 检索某个用户选择的所有课程                   |\n| selection_courseId_Index        | 课程ID                           | 课程对应选课用户ID        | 查找选修某一课程的所有用户                   |\n| selection_userId_courseId_Index | 用户ID与课程ID组合后计算的哈希值             | 选课信息ID            | 通过用户ID和课程ID组合进行复合检索             |\n| selection_file_Index            | 选课信息ID                         | 存于文件系统中的选课信息数据文件名 | 映射选课信息ID与其在文件系统中的数据文件           |\n| global_counter_Index            | 对应不同数据，例如user、course、selection | 统计每个类型的数据包含多少条数据  | 用于全局计数，如统计不同实体类型的数据条数等          |\n\n在存储操作对象时，目标是对各操作对象信息的各字段进行快速而准确的查询与修改。为实现该目标，我们选择使用结构体（Struct）的方式来对各实体进行存储。\n用户（User）、课程（Course）与选课信息（CourseSelection）的结构体如下方代码所示：\n\n```c\n// 用户\ntypedef struct {\n    int64 id;              // 用户ID\n    char name[21],         // 用户姓名\n    passwd[65],            // 密码\n    empId[21];             // 学工号\n    int role;              // 用户角色 0-学生 1-教师 2-管理员\n    char contact[201];     // 联系方式\n    int64 lastLoginTime;   // 最后一次登录时间戳\n} User;\n\n// 课程\ntypedef struct {\n    int64 id;            // 课程ID/课程号\n    char courseName[101],// 课程名称\n    description[501];    // 课程描述\n    int64 teacherId;     // 任课老师ID\n    User *teacher;       // 任课老师\n    int type,            // 课程类型：0-必修 1-选修 2-公选 3-辅修\n    weekStart,           // 开课周\n    weekEnd,             // 结课周\n    currentMembers,      // 当前报名人数\n    maxMembers;          // 最大可报名人数\n    char location[101];  // 上课地点\n    double points;       // 学分\n    int schedule[7][13]; // 每星期课程安排\n} Course;\n\n// 选课信息\ntypedef struct {\n    int64 id;                // 选课ID\n    int64 studentId;         // 学生ID\n    User *student;           // 学生信息\n    int64 courseId;          // 课程ID\n    Course *course;          // 课程信息\n    int64 selectionTime;     // 选择该课程的时间\n    int64 score;             // 成绩\n} CourseSelection;\n\n```\n\n考虑到对内存空间的高效利用，我们选择采用链式存储结构来存储多个操作对象。链式存储结构在存储密度和存储灵活性方面展示出优越性，允许我们在不需连续内存空间的条件下，实现动态数据存储。\n\n具体而言，用户（User）、课程（Course）和学生选课信息（CourseSelection）实体将被集成至2.1.1.2节所述的基于平衡二叉树（AVL Tree）实现的通用索引中，即user_id_Index、course_id_Index和selection_id_Index。\n\n为了确保数据的持久性和一致性，我们采用了一种结构化的文件存储策略。具体地，不同类型的数据结构体将被分别存储在独立的文件夹内，以便于数据的加载和管理。以下是各种数据存储方式的详细描述：\n\n**课程数据文件（Course）** 这类文件将被存储在data/course文件夹中，并使用.dat作为文件扩展名。目前，文件的命名约定是使用课程ID，这一规范可以根据后续需求进行调整。\n\n**用户数据文件（User）与选课数据文件（CourseSelection）** 用户和选课的数据存储策略与课程数据高度相似，包括文件夹的布局和文件的命名规则，因此在这里不做重复说明。\n\n通用索引采用平衡二叉树（AVL树）与链表的组合方式进行构建。由于平衡二叉树具有高效的结构特性，其可将单次查询操作的时间复杂度降至O(logN)。在二叉树中，节点的Key由特定哈希函数计算生成，其数据类型为int64；相应的Value则是链表节点（IndexListNode），链表中的数据区指向一个特定的Index数据结构。这个Index数据结构包含哈希值和相应的实体数据结构的指针。\n上述通用索引的存储结构见图1-2-5。\n\n![img_4.png](images/img_4.png)\n\n通用索引涉及的相关代码定义如下：\n\n```c\n// 索引类型\ntypedef enum {\n    INDEX_TYPE_INT64,       // 整数\n    INDEX_TYPE_STRING,      // 字符串\n    INDEX_TYPE_OBJECT       // 对象\n} IndexType;\n\n// 索引\ntypedef struct {\n    int64 hash;             // 哈希值\n    IndexType type;         // 索引类型\n    void *data;             // 数据\n} Index;\n\n// 索引链表\ntypedef struct IndexListNode {\n    Index index; // 索引\n    struct IndexListNode *next; // 下一个节点\n} IndexListNode;\n\n// AVL树节点\ntypedef struct AVLNode {\n    IndexListNode *list;    // 索引链表\n    int height;             // 节点高度\n    struct AVLNode *left;   // 左子树\n    struct AVLNode *right;  // 右子树\n} AVLNode;\n```\n\n在实现数据持久性与一致性的过程中，通用索引（Index）类型的实体将通过文件的形式被存储在data/index文件夹中，并采用.avl的文件扩展名。文件的命名规范是与相应的索引名称一致。\n\n为了优化数据的加载和访问性能，我们采用了一种混合的数据加载策略，结合预加载和懒加载的特点。具体来说，系统在启动阶段会预先加载所有的索引数据，而实体数据（如课程、用户及选课数据）则采用懒加载的机制。以下是该策略的详细说明：\n\n**预加载索引** 在系统启动时，所有的索引文件将被预先加载到内存中。这种预加载策略有助于减少在后续操作中的索引查询时间，从而提高系统的响应速度。\n\n**实体数据的懒加载** 对于课程、用户和选课的实体数据，系统采用懒加载策略。具体而言，当某一函数调用需要访问这些实体数据时，系统会首先通过相应的file_Index索引查找数据所对应的文件名。随后，该文件会被加载到内存并存储在对应的id_Index中。\n\n**数据缓存** 一旦实体数据被加载到内存中，它将被缓存以供后续的请求使用。这意味着在下一次相同的数据请求发生时，系统会直接从内存中返回这些缓存数据，从而避免了不必要的文件I/O操作。\n\n通过这一混合数据加载策略，我们不仅降低了系统启动时的I/O负担，还提高了数据访问的效率和响应速度。这种机制进一步加强了系统的可扩展性和可维护性，同时也优化了资源的使用和管理。\n\n## 功能设计\n\n以下是主程序的功能设计伪码，包括从系统初始化到执行相应功能的整体流程：\n\n```text\nFunction 主函数(){\n\t配置显示设置(); // 切换控制台编码模式、设置标题等\n\t加载中文转哈希函数的必要配置();\n\t加载索引(); // 从文件中加载必需的索引\n\tLabel Login: // 标记登录标签，用于后续跳转\n\t登录系统(); // 执行登录相关操作\n\t// 登陆成功后\n\t打印主菜单(); // 打印主菜单\n}\n\nFunction 登录系统() {\n\tString username = 从控制台获取用户名();\n\tString password = 从控制台获取密码();\n\t// 判断用户名或密码的格式是否正确\n\tIf (!Regex.match(USERNAME_PATTERN, username) or !Regex.match(PASSWORD_PATTERN, password)) {\n\t\t显示错误信息(“用户名或密码不正确”);\n\t\tcontinue;\n\t}\n    String hashedPassword = hmac_sha256(password, SECRET); // 计算密码的hmac_sha256值\n\tUser *user = DB_getUserByEmpId(username); // 获取用户\n\tif (!user || user.passwd != hashedPassword) {\n        显示错误信息(“用户名或密码错误”);\n        goto Login;\n    }\n    currentUser = user; // 设置登录状态\n\t显示登陆成功信息();\n}\n\nFunction 打印主菜单(){\n\tLabel Refresh;\n\t初始化屏幕设置(); // 清屏、设置窗口大小等\n\tSwitch (currentUser.role){ // 根据不同角色显示不同菜单\n\t\tCase 学生:\n\t\t\t显示学生菜单();\n\t\t\tBreak;\n\t\tCase 教师:\n\t\t\t显示教师菜单();\n\t\t\tBreak;\n\t\tCase 管理员:\n\t\t\t显示管理员菜单();\n\t\t\tBreak;\n\t\tDefault:\n\t\t\t显示报错信息(“未知用户角色”);\n\t\t\tGoto Login;\n    }\n    Int choice = 从控制台读取选项();\n    // 由于选项最多不超过10个\n    If (choice not in [1..10])｛\n        显示报错信息(“无效选项”);\n        Goto Refresh;\n    ｝\n    // 由于角色只有0、1、2，所以可以用这一特性区分不同角色的不同功能\n    Int parsedChoice = currentUser.role * 10 + choice;\n    执行相应操作(parsedChoice);\n    Goto Refresh; // 执行完毕后，还需继续读取下一个操作\n}\n\nFunction 执行相应操作(parsedChoice){\n\tSwitch (parsedChoice){\n\t\tCase a:\n\t\tCase b:\n\t\t\t某公共功能();\n\t\t\tBreak;\n\t\tCase aa:\n\t\t\t某角色独有功能();\n\t\t\tBreak;\n\t\t// …\n\t\tDefault:\n\t\t\t显示错误信息(“无效选项”);\n\t\t\tBreak;\n    }\n}\n\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvvbbnn00%2Fusst-datastruct_coursesystem","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvvbbnn00%2Fusst-datastruct_coursesystem","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvvbbnn00%2Fusst-datastruct_coursesystem/lists"}