第12篇 综合实战——制作一个学生管理系统 仓颉原生中文编程
本文介绍了使用华为仓颉中文编程语言开发学生管理系统的完整项目实战。系统包含添加、删除、查找、修改学生信息以及统计功能,支持数据持久化存储。文章详细展示了项目结构,包括主程序、学生类、管理类等模块,并提供了核心代码实现。学生类定义了学号、姓名、成绩等属性和相关计算方法;学生管理类实现了增删改查、统计排名等功能。该项目综合运用了中文编程基础语法,适合零基础学习者通过实践巩固编程知识。
第12篇 综合实战——制作一个学生管理系统
**作者:**中文编程倡导者—— 李金雨
联系方式: wbtm2718@qq.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. 学生管理类(学生管理类.cj)
import 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. 文件操作类(文件操作.cj)
import 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. 界面显示(界面显示.cj)
import 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. 主程序(主程序.cj)
import 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#风格)
- 更符合中国人的语言习惯
- 真正做到"用中文说人话"来编程
继续学习的建议:
- 多做练习,巩固基础知识
- 尝试修改和扩展学生管理系统
- 思考生活中可以用编程解决的问题
- 学习更多编程语言,对比它们的优缺点
- 关注国产编程语言的发展
祝你在编程的道路上越走越远!
更多推荐

所有评论(0)