第12篇 综合实战——制作一个学生管理系统

**作者:**中文编程倡导者—— 李金雨
联系方式: wbtm2718@qq.com
**目标读者:**编程入门(零基础)
核心理念: 使用华为仓颉原生中文编程,体验真正的国产编程语言


一、项目介绍

经过前面11篇的学习,我们已经掌握了仓颉编程语言的基础知识。现在,让我们综合运用这些知识,完成一个完整的项目——学生管理系统

项目功能

  1. 添加学生:输入学生信息并保存
  2. 删除学生:按学号删除学生
  3. 查找学生:按学号或姓名查找
  4. 修改信息:修改学生的成绩等信息
  5. 显示所有:列出所有学生信息
  6. 统计功能:计算平均分、最高分、最低分
  7. 数据持久化:保存到文件,下次启动不丢失

二、项目结构

学生管理系统/
├── 主程序.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. 用户登录

  • 管理员和普通用户权限区分
  • 密码验证
  • 操作日志记录

六、本课小结

通过这个项目,我们综合运用了前面学到的所有知识:

  1. 变量和数据类型:存储学生信息
  2. 运算符:计算总分和平均分
  3. 条件判断:验证输入、查找学生
  4. 循环:遍历学生列表
  5. 数组和列表:存储多个学生
  6. 方法:封装各种功能
  7. 类和对象:面向对象设计
  8. 继承和多态:可扩展的架构
  9. 异常处理:错误处理
  10. 文件操作:数据持久化

类型语法思考:在整个项目开发过程中,我们频繁地声明类、属性、方法参数、返回值类型,每一次都要面对类型后置的语法。希望未来的中文编程语言能让类型声明更符合中国人的说话习惯,真正做到"说人话"!

七、课程总结

经过12篇的学习,我们已经完成了仓颉编程语言的入门学习。

我们学到了:

  1. 编程的基本概念和仓颉语言的特点
  2. 变量、数据类型和运算符
  3. 条件判断和循环语句
  4. 数组和列表的使用
  5. 方法的定义和调用
  6. 面向对象编程(类、对象、继承、多态)
  7. 异常处理
  8. 文件操作
  9. 综合项目实战

关于语法设计的思考:

在学习过程中,我们反复提到了仓颉语言的类型后置语法问题。虽然仓颉是华为开发的中文编程语言,但在语法设计上采用了类似Rust的类型后置风格,这与中国人日常说话的"定语前置"习惯不太一致。

我们希望未来的中文编程语言能够:

  • 采用类型前置的语法(如C#风格)
  • 更符合中国人的语言习惯
  • 真正做到"用中文说人话"来编程

继续学习的建议:

  1. 多做练习,巩固基础知识
  2. 尝试修改和扩展学生管理系统
  3. 思考生活中可以用编程解决的问题
  4. 学习更多编程语言,对比它们的优缺点
  5. 关注国产编程语言的发展

祝你在编程的道路上越走越远!


Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐