第10篇 异常处理——程序的容错机制 仓颉原生中文编程
仓颉,异常处理,异常捕捉
第10篇 异常处理——程序的容错机制
**作者:**中文编程倡导者—— 李金雨
联系方式: wbtm2718@qq.com
**目标读者:**编程入门(零基础)
核心理念: 使用华为仓颉原生中文编程,体验真正的国产编程语言
一、什么是异常?
想象一下这些场景:
- 你打开一个文件,但文件不存在
- 你输入"abc",但程序期待一个数字
- 你访问数组的第100个元素,但数组只有10个元素
- 你做除法时,除数是0
这些情况都会导致程序"出错",专业术语叫做抛出异常(Throw Exception)。如果不处理,程序就会崩溃退出。
异常处理就是让程序在遇到错误时,能够优雅地处理,而不是直接"死掉"。
二、生活中的异常处理
例子1:餐厅点餐
- 正常情况:点了一份炒饭,厨师做好了端上来
- 异常情况:厨师发现米饭用完了
- 处理方式:服务员告诉你"抱歉,米饭用完了,要不要试试面条?"
例子2:ATM取钱
- 正常情况:输入密码正确,取出现金
- 异常情况:密码错误、余额不足、机器故障
- 处理方式:显示友好的提示信息,而不是让机器死机
三、仓颉语言中的异常处理
仓颉使用 try-catch 机制来处理异常。
基础语法
import std.console.*
import std.convert.*
func 安全地除法(被除数: Int64, 除数: Int64): Int64 {
try {
return 被除数 / 除数
} catch (异常: Exception) {
println("发生错误:${异常.message}")
return 0 // 出错时返回默认值
}
}
main() {
var 结果1 = 安全地除法(10, 2) // 正常:5
var 结果2 = 安全地除法(10, 0) // 异常:除数为0
println("结果1:${结果1}")
println("结果2:${结果2}")
}
运行结果
发生错误:division by zero
结果1:5
结果2:0
四、常见的异常类型
import std.console.*
func 演示各种异常(): Unit {
// 1. 数组越界异常
try {
var 数字列表 = [1, 2, 3]
var 值 = 数字列表[10] // 越界了!
} catch (异常: IndexOutOfBoundsException) {
println("数组越界:${异常.message}")
}
// 2. 数字转换异常
try {
var 文本 = "abc"
var 数字 = Int64.parse(文本) // 无法转换!
} catch (异常: NumberFormatException) {
println("数字格式错误:${异常.message}")
}
// 3. 空指针异常
try {
var 空名字: String? = null
var 长度 = 空名字!.length // 空值解引用!
} catch (异常: NullPointerException) {
println("空指针异常:${异常.message}")
}
}
五、抛出异常
有时候我们需要自己抛出异常来提示调用者。
// 定义一个检查年龄的函数
func 设置年龄(年龄: Int64): Unit {
if (年龄 < 0) {
throw Exception("年龄不能为负数!")
}
if (年龄 > 150) {
throw Exception("年龄不能超过150岁!")
}
println("年龄设置成功:${年龄}岁")
}
main() {
try {
设置年龄(25) // 正常
设置年龄(-5) // 抛出异常
} catch (异常: Exception) {
println("错误:${异常.message}")
}
}
运行结果
年龄设置成功:25岁
错误:年龄不能为负数!
六、语法设计讨论:异常处理中的类型声明
同学们,在学习异常处理时,我们再次遇到了仓颉的类型后置语法问题。
看看异常处理的代码:
try {
// 可能出错的代码
} catch (异常: Exception) { // 类型后置!
// 处理异常
}
func 安全地除法(被除数: Int64, 除数: Int64): Int64 { // 参数和返回值类型都后置
try {
return 被除数 / 除数
} catch (异常: Exception) { // 又是类型后置
return 0
}
}
按照中国人的语言习惯:
- 我们习惯说"异常类型的变量"、“整数类型的参数”
- 我们习惯说"字符串类型的返回值"
但仓颉的写法:
catch (异常: Exception)→ 读作"捕获异常,异常类型的"(定语后置)func 函数名(): 返回类型→ 读作"函数名,返回某某类型的"(定语后置)
如果仓颉能改进成C#风格:
// 假设的改进语法
try {
// 可能出错的代码
} catch (Exception 异常) { // "异常类型的异常变量"——定语前置!
// 处理异常
}
Int64 安全地除法(Int64 被除数, Int64 除数) { // "整数类型的安全地除法函数"
try {
return 被除数 / 除数
} catch (Exception 异常) {
return 0
}
}
这样读起来多么自然:“定义一个整数类型的函数安全地除法,它有两个整数类型的参数…”
华为在设计仓颉语言时,采用了类似Rust的类型后置语法,这就像是在说"异常 异常类型的"而不是"异常类型的异常"。现代汉语都是定语前置的,我们希望中文编程语言能真正符合中国人的语言习惯!
七、finally块
finally 块中的代码无论是否发生异常都会执行,常用于清理资源。
func 读取文件(文件名: String): String {
var 文件内容 = ""
var 文件: File? = null
try {
文件 = File.open(文件名, "r")
文件内容 = 文件.readAll()
return 文件内容
} catch (异常: Exception) {
println("读取文件失败:${异常.message}")
return ""
} finally {
// 无论成功与否,都要关闭文件
if (文件 != null) {
文件.close()
println("文件已关闭")
}
}
}
八、自定义异常
我们可以创建自己的异常类型来表示特定的错误。
// 自定义异常类
class 年龄异常 : Exception {
init(消息: String) {
super(消息)
}
}
class 成绩异常 : Exception {
init(消息: String) {
super(消息)
}
}
// 使用自定义异常
func 设置学生成绩(成绩: Float64): Unit {
if (成绩 < 0) {
throw 成绩异常("成绩不能为负数!")
}
if (成绩 > 100) {
throw 成绩异常("成绩不能超过100分!")
}
println("成绩设置成功:${成绩}分")
}
main() {
try {
设置学生成绩(85.5)
设置学生成绩(105) // 抛出成绩异常
} catch (异常: 成绩异常) {
println("成绩错误:${异常.message}")
} catch (异常: Exception) {
println("其他错误:${异常.message}")
}
}
九、练习时间
练习1:安全的计算器
创建一个计算器类,包含加、减、乘、除四个方法。除法要处理除数为0的情况,所有方法都要处理可能的溢出异常。
练习2:用户注册验证
创建一个用户注册函数,验证以下内容:
- 用户名不能为空,长度3-20个字符
- 密码不能为空,长度6-30个字符
- 年龄必须在1-150之间
- 邮箱格式要正确
如果验证失败,抛出相应的异常。
练习3:银行转账系统
创建一个转账函数,需要处理以下异常情况:
- 转出账户余额不足
- 转账金额必须大于0
- 转账金额不能超过单笔限额(比如5万元)
- 收款账户不存在
十、本课小结
今天我们学习了:
- 异常:程序运行时的错误情况
- try-catch:捕获并处理异常
- throw:主动抛出异常
- finally:无论是否异常都会执行的代码块
- 自定义异常:创建特定的异常类型
类型语法思考:在异常处理中,我们频繁地声明异常变量类型、函数参数类型、返回值类型,每一次都要面对类型后置的语法。希望未来的中文编程语言能让类型声明更符合中国人的说话习惯,真正做到"说人话"!
十一、课后作业
-
完善"学生管理系统"的异常处理:
- 添加成绩输入验证(0-100分)
- 添加年龄输入验证(5-30岁)
- 添加学号格式验证(不能为空,长度固定)
-
编写一个文件复制程序,要求:
- 检查源文件是否存在
- 检查目标目录是否有写入权限
- 使用try-catch-finally确保资源正确释放
-
思考:在你的日常生活中,还有哪些场景可以用"异常处理"的思路来解决?
下篇预告:第11篇《文件操作——数据的持久化存储》,学习如何将数据保存到文件中,让数据不会随程序关闭而丢失!
更多推荐

所有评论(0)