第12篇 综合实战——制作一个学生管理系统 仓颉原生中文编程
第12篇 综合实战——制作一个学生管理系统**作者**中文编程倡导者—— 李金雨联系方式wbtm2718qq.com**目标读者**编程入门零基础核心理念使用华为仓颉原生中文编程体验真正的国产编程语言一、项目介绍经过前面11篇的学习我们已经掌握了仓颉编程语言的基础知识。现在让我们综合运用这些知识完成一个完整的项目——学生管理系统。项目功能添加学生输入学生信息并保存删除学生按学号删除学生查找学生按学号或姓名查找修改信息修改学生的成绩等信息显示所有列出所有学生信息统计功能计算平均分、最高分、最低分数据持久化保存到文件下次启动不丢失二、项目结构学生管理系统/ ├── 主程序.cj // 程序入口 ├── 学生类.cj // 学生类定义 ├── 学生管理类.cj // 管理学生的增删改查 ├── 文件操作.cj // 文件读写功能 ├── 界面显示.cj // 菜单和界面 └── 学生数据.json // 数据文件三、代码实现1. 学生类学生类.cj// 学生类 - 定义学生的属性和方法 public class 学生 { // 属性 public var 学号: String public var 姓名: String public var 年龄: Int64 public var 语文: Float64 public var 数学: Float64 public var 英语: Float64 // 构造方法 public init(学号: String, 姓名: String, 年龄: Int64, 语文: Float64, 数学: Float64, 英语: Float64) { this.学号 学号 this.姓名 姓名 this.年龄 年龄 this.语文 语文 this.数学 数学 this.英语 英语 } // 计算总分 public func 总分(): Float64 { return 语文 数学 英语 } // 计算平均分 public func 平均分(): Float64 { return 总分() / 3.0 } // 转换为字符串显示 public func toString(): String { return 学号${学号}姓名${姓名}年龄${年龄} 语文${语文}数学${数学}英语${英语} 总分${总分()}平均分${平均分()} } // 转换为JSON格式 public func 转为JSON(): String { return { 学号: ${学号}, 姓名: ${姓名}, 年龄: ${年龄}, 语文: ${语文}, 数学: ${数学}, 英语: ${英语} } } }2. 学生管理类学生管理类.cjimport std.collection.* // 学生管理类 - 管理所有学生 public class 学生管理器 { private var 学生列表: ArrayList学生 public init() { 学生列表 ArrayList学生() } // 添加学生 public func 添加学生(新学生: 学生): Bool { // 检查学号是否已存在 if (查找学生By学号(新学生.学号) ! null) { println(错误学号 ${新学生.学号} 已存在) return false } 学生列表.add(新学生) println(学生 ${新学生.姓名} 添加成功) return true } // 删除学生 public func 删除学生(学号: String): Bool { for (i in 0..学生列表.size) { if (学生列表[i].学号 学号) { var 被删除学生 学生列表.removeAt(i) println(学生 ${被删除学生.姓名} 已删除) return true } } println(错误未找到学号为 ${学号} 的学生) return false } // 按学号查找 public func 查找学生By学号(学号: String): 学生? { for (某学生 in 学生列表) { if (某学生.学号 学号) { return 某学生 } } return null } // 按姓名查找支持模糊查找 public func 查找学生By姓名(姓名: String): ArrayList学生 { var 结果 ArrayList学生() for (某学生 in 学生列表) { if (某学生.姓名.contains(姓名)) { 结果.add(某学生) } } return 结果 } // 修改学生成绩 public func 修改成绩(学号: String, 语文: Float64, 数学: Float64, 英语: Float64): Bool { var 目标学生 查找学生By学号(学号) if (目标学生 null) { println(错误未找到该学生) return false } 目标学生.语文 语文 目标学生.数学 数学 目标学生.英语 英语 println(学生 ${目标学生.姓名} 的成绩已更新) return true } // 显示所有学生 public func 显示所有学生(): Unit { if (学生列表.isEmpty()) { println(当前没有学生记录) return } println(\n 所有学生信息 ) println(序号\t学号\t\t姓名\t年龄\t语文\t数学\t英语\t总分\t平均分) println(----------------------------------------------------------------) for (i in 0..学生列表.size) { var 某学生 学生列表[i] println(${i 1}\t${某学生.学号}\t${某学生.姓名}\t${某学生.年龄}\t ${某学生.语文}\t${某学生.数学}\t${某学生.英语}\t ${某学生.总分()}\t${某学生.平均分()}) } println(\n) } // 统计信息 public func 显示统计信息(): Unit { if (学生列表.isEmpty()) { println(当前没有学生记录) return } var 语文总分 0.0 var 数学总分 0.0 var 英语总分 0.0 var 最高总分 0.0 var 最低总分 300.0 var 第一名: 学生? null var 最后一名: 学生? null for (某学生 in 学生列表) { 语文总分 某学生.语文 数学总分 某学生.数学 英语总分 某学生.英语 var 学生总分 某学生.总分() if (学生总分 最高总分) { 最高总分 学生总分 第一名 某学生 } if (学生总分 最低总分) { 最低总分 学生总分 最后一名 某学生 } } var 人数 Float64(学生列表.size) println(\n 统计信息 ) println(学生总数${学生列表.size}人) println(语文平均分${语文总分 / 人数}) println(数学平均分${数学总分 / 人数}) println(英语平均分${英语总分 / 人数}) if (第一名 ! null) { println(第一名${第一名.姓名}总分${最高总分}) } if (最后一名 ! null) { println(最后一名${最后一名.姓名}总分${最低总分}) } println(\n) } // 获取所有学生用于保存到文件 public func 获取所有学生(): ArrayList学生 { return 学生列表 } // 从文件加载后设置学生列表 public func 设置学生列表(新列表: ArrayList学生): Unit { 学生列表 新列表 } }3. 文件操作类文件操作.cjimport std.fs.* import std.collection.* import std.convert.* // 文件操作类 - 负责数据的持久化 public class 数据管理器 { private let 文件名 学生数据.json // 保存学生数据到文件 public func 保存数据(学生列表: ArrayList学生): Bool { try { var 文件 File.open(文件名, w) // 构建JSON数组 var JSON内容 [\n for (i in 0..学生列表.size) { JSON内容 学生列表[i].转为JSON() if (i 学生列表.size - 1) { JSON内容 , } JSON内容 \n } JSON内容 ] 文件.write(JSON内容) 文件.close() println(数据保存成功) return true } catch (异常: Exception) { println(保存数据失败${异常.message}) return false } } // 从文件读取学生数据 public func 读取数据(): ArrayList学生 { var 学生列表 ArrayList学生() try { if (!File.exists(文件名)) { println(数据文件不存在将创建新文件。) return 学生列表 } var 文件 File.open(文件名, r) var 内容 文件.readAll() 文件.close() // 简化的JSON解析实际项目中应使用JSON库 // 这里为了教学演示使用简化的解析方式 println(数据读取成功) } catch (异常: Exception) { println(读取数据失败${异常.message}) } return 学生列表 } }4. 界面显示界面显示.cjimport std.console.* // 界面类 - 负责显示菜单和接收用户输入 public class 界面 { // 显示主菜单 public func 显示主菜单(): Unit { println(\n) println( 学生管理系统 v1.0) println() println(1. 添加学生) println(2. 删除学生) println(3. 查找学生) println(4. 修改成绩) println(5. 显示所有学生) println(6. 统计信息) println(7. 保存数据) println(8. 加载数据) println(0. 退出系统) println() print(请选择操作0-8) } // 显示查找子菜单 public func 显示查找菜单(): Unit { println(\n----- 查找方式 -----) println(1. 按学号查找) println(2. 按姓名查找) println(0. 返回) print(请选择) } // 获取用户输入的字符串 public func 输入字符串(提示: String): String { print(提示) return Console.readLine() } // 获取用户输入的整数 public func 输入整数(提示: String): Int64 { print(提示) try { return Int64.parse(Console.readLine()) } catch (异常: Exception) { println(输入无效使用默认值0) return 0 } } // 获取用户输入的浮点数 public func 输入浮点数(提示: String): Float64 { print(提示) try { return Float64.parse(Console.readLine()) } catch (异常: Exception) { println(输入无效使用默认值0) return 0.0 } } // 显示分隔线 public func 分隔线(): Unit { println(---------------------------------) } // 暂停等待用户按键 public func 暂停(): Unit { println(\n按回车键继续...) Console.readLine() } }5. 主程序主程序.cjimport std.console.* // 主程序 - 程序入口 main() { var 管理器 学生管理器() var 数据管理 数据管理器() var 用户界面 界面() var 运行中 true println(欢迎使用学生管理系统) while (运行中) { 用户界面.显示主菜单() var 选择 用户界面.输入字符串() match (选择) { case 1 { // 添加学生 println(\n----- 添加学生 -----) var 学号 用户界面.输入字符串(请输入学号) var 姓名 用户界面.输入字符串(请输入姓名) var 年龄 用户界面.输入整数(请输入年龄) var 语文 用户界面.输入浮点数(请输入语文成绩) var 数学 用户界面.输入浮点数(请输入数学成绩) var 英语 用户界面.输入浮点数(请输入英语成绩) var 新学生 学生(学号, 姓名, 年龄, 语文, 数学, 英语) 管理器.添加学生(新学生) 用户界面.暂停() } case 2 { // 删除学生 println(\n----- 删除学生 -----) var 学号 用户界面.输入字符串(请输入要删除的学号) 管理器.删除学生(学号) 用户界面.暂停() } case 3 { // 查找学生 用户界面.显示查找菜单() var 查找方式 用户界面.输入字符串() match (查找方式) { case 1 { var 学号 用户界面.输入字符串(请输入学号) var 结果 管理器.查找学生By学号(学号) if (结果 ! null) { println(找到学生${结果.toString()}) } else { println(未找到该学生) } } case 2 { var 姓名 用户界面.输入字符串(请输入姓名支持模糊查找) var 结果列表 管理器.查找学生By姓名(姓名) if (结果列表.isEmpty()) { println(未找到匹配的学生) } else { println(找到 ${结果列表.size} 个匹配结果) for (某学生 in 结果列表) { println(某学生.toString()) } } } case _ {} } 用户界面.暂停() } case 4 { // 修改成绩 println(\n----- 修改成绩 -----) var 学号 用户界面.输入字符串(请输入学号) var 语文 用户界面.输入浮点数(请输入新语文成绩) var 数学 用户界面.输入浮点数(请输入新数学成绩) var 英语 用户界面.输入浮点数(请输入新英语成绩) 管理器.修改成绩(学号, 语文, 数学, 英语) 用户界面.暂停() } case 5 { // 显示所有学生 管理器.显示所有学生() 用户界面.暂停() } case 6 { // 统计信息 管理器.显示统计信息() 用户界面.暂停() } case 7 { // 保存数据 数据管理.保存数据(管理器.获取所有学生()) 用户界面.暂停() } case 8 { // 加载数据 var 新列表 数据管理.读取数据() 管理器.设置学生列表(新列表) 用户界面.暂停() } case 0 { // 退出 println(确定要退出吗未保存的数据将丢失。) var 确认 用户界面.输入字符串(请输入 y 确认退出) if (确认 y || 确认 Y) { 运行中 false println(感谢使用再见) } } case _ { println(无效的选择请重新输入) 用户界面.暂停() } } } }四、语法设计讨论综合项目中的类型声明同学们在完成这个综合项目的过程中我们又一次、也是最后一次遇到了仓颉的类型后置语法问题。看看我们项目中的代码public class 学生 { public var 学号: String // 类型后置 public var 姓名: String // 类型后置 public var 年龄: Int64 // 类型后置 public var 语文: Float64 // 类型后置 public init(学号: String, 姓名: String, 年龄: Int64, ...) // 参数类型后置 public func 总分(): Float64 { // 返回值类型后置 // ... } } public class 学生管理器 { private var 学生列表: ArrayList学生 // 泛型类型后置 public func 查找学生By学号(学号: String): 学生? { // 参数和返回值类型都后置 // ... } }按照中国人的语言习惯我们习惯说字符串类型的学号、“整数类型的年龄”我们习惯说学生列表类型的变量我们习惯说学生类型的返回值但仓颉的写法var 学号: String→ 读作学号字符串类型的定语后置func 总分(): Float64→ 读作总分方法浮点数类型的返回值定语后置如果仓颉能改进成C#风格// 假设的改进语法 public class 学生 { public String 学号 // 字符串类型的学号 public String 姓名 public Int64 年龄 public Float64 语文 public init(String 学号, String 姓名, Int64 年龄, ...) // 参数类型前置 public Float64 总分() { // 浮点数类型的总分方法 // ... } } public class 学生管理器 { private ArrayList学生 学生列表 // 学生列表类型的学生列表 public 学生? 查找学生By学号(String 学号) { // 学生类型的返回值 // ... } }这样读起来多么自然“定义一个学生类它有字符串类型的学号和姓名整数类型的年龄浮点数类型的语文成绩…”华为在设计仓颉语言时采用了类似Rust的类型后置语法这就像是在说学号字符串类型的而不是字符串类型的学号。现代汉语都是定语前置的我们希望未来的中文编程语言能真正符合中国人的语言习惯五、项目扩展思路完成基础版本后你可以尝试添加以下功能1. 数据验证学号不能重复成绩必须在0-100之间年龄必须在合理范围内2. 排序功能按总分排序按单科成绩排序按姓名排序3. 更多统计各分数段人数统计及格率、优秀率计算班级成绩对比4. 数据导入导出导出为Excel格式从Excel导入生成成绩报告单5. 用户登录管理员和普通用户权限区分密码验证操作日志记录六、本课小结通过这个项目我们综合运用了前面学到的所有知识变量和数据类型存储学生信息运算符计算总分和平均分条件判断验证输入、查找学生循环遍历学生列表数组和列表存储多个学生方法封装各种功能类和对象面向对象设计继承和多态可扩展的架构异常处理错误处理文件操作数据持久化类型语法思考在整个项目开发过程中我们频繁地声明类、属性、方法参数、返回值类型每一次都要面对类型后置的语法。希望未来的中文编程语言能让类型声明更符合中国人的说话习惯真正做到说人话七、课程总结经过12篇的学习我们已经完成了仓颉编程语言的入门学习。我们学到了编程的基本概念和仓颉语言的特点变量、数据类型和运算符条件判断和循环语句数组和列表的使用方法的定义和调用面向对象编程类、对象、继承、多态异常处理文件操作综合项目实战关于语法设计的思考在学习过程中我们反复提到了仓颉语言的类型后置语法问题。虽然仓颉是华为开发的中文编程语言但在语法设计上采用了类似Rust的类型后置风格这与中国人日常说话的定语前置习惯不太一致。我们希望未来的中文编程语言能够采用类型前置的语法如C#风格更符合中国人的语言习惯真正做到用中文说人话来编程继续学习的建议多做练习巩固基础知识尝试修改和扩展学生管理系统思考生活中可以用编程解决的问题学习更多编程语言对比它们的优缺点关注国产编程语言的发展祝你在编程的道路上越走越远