当前位置: 首页 > news >正文

仓颉编程语言青少年基础教程:异常处理

仓颉编程语言青少年基础教程:异常处理

异常(Exceptions)是一类特殊的可以被程序员捕获并处理的错误,是程序执行时出现的一系列不正常行为的统称。例如,要读取使用的文件不存在、非法输入、除零错误、数组越界等。如果不处理这些异常,程序可能会崩溃,终止运行。仓颉语言通过 try、catch、throw 和 finally 等关键字,帮助我们优雅地处理这些问题,用来保证系统的正确性和健壮性。

先看一个简单示例:

import std.fs.*main(): Int64 {let p = Path("./no_such.txt")  //try {let f = File(p, Read)   // 若文件不存在会抛 FSExceptionprintln("文件可以打开")f.close()} catch (e: FSException) {println("捕获到异常:${e.message}")}return 0
}

在仓颉语言中,异常类包括 Error 和 Exception:

  • Error 类描述仓颉语言运行时,系统内部错误和资源耗尽错误。应用程序不应该抛出这种类型错误,如果出现内部错误,只能通知给用户,尽量安全终止程序。——在编译时发现错误,属于静态防御(Static Defense)。

  • Exception 类描述的是程序运行时(Runtime)的逻辑错误或者 IO 错误导致的异常,例如数组越界或者试图打开一个不存在的文件等,这类异常需要在程序中捕获处理。——在运行时发现异常,属于动态防御(Dynamic Defense)。

开发者不可以通过继承仓颉语言内置的 Error 或其子类来自定义异常,但是可以继承内置的 Exception 或其子类来自定义异常。

:此处官方文档所说的数组越界——当数组的索引是变量且编译期无法确定其值是否越界的情况。

在仓颉数组的下标访问中,对数组的下标越界访问也有安全检查。当上下文足以静态分析的时候,下标访问可提前在编译期检测出来,编译器会直接给出报错;当上下文不足以静态分析的时候,下标访问会在运行时做一个检查,如果溢出会抛出运行时异常。

——见https://cangjie-lang.cn/docs?url=%2F0.53.18%2Fwhite_paper%2Fsource_zh_cn%2Fcj-wp-dynamiccheck.html 相关部分。

现象描述如下:

main(): Int64 {let arrayTest: Array<Int64> = [0, 1, 2]try {println("arrayTest[2] = ${arrayTest[2]}")println("arrayTest[3] = ${arrayTest[3]}")  //?! 越界错误捕获不到} catch (_) {println("catch an exception!")}return 0
}

这段代码?! 处的错误为何捕获不到,而报错,为什么?

解释:对于编译期可直接判定的数组越界(比如你代码中arrayTest[3],数组长度为 3,索引 3 明显超出0~2范围),仓颉编译器会在编译阶段直接报错,终止编译流程 —— 此时程序还未开始运行,try-catch代码块自然无法发挥作用。

这是一种 “提前防御”或称为静态防御(Static Defense)的设计:对于一眼就能看出的逻辑错误(如硬编码的越界索引),编译器直接拦截,避免程序带着明显错误运行。

代码修改如下:

import std.convert.*main(): Int64 {let arr: Array<Int64> = [0, 1, 2]print("请输入要访问的索引(0-2):")let input = readln()try {let index = Int64.parse(input)// 手动检查索引范围,主动抛出包含详细信息的异常if (index < 0 || index >= arr.size) {throw Exception("数组越界:索引 ${index} 超出范围(数组长度为 ${arr.size},合法索引 0~${arr.size-1})")}println("arr[${index}] = ${arr[index]}")} catch (e: Exception) {println("捕获到异常:${e.message}")}return 0
}

就样就能捕获并展示数组越界异常。这段代码体验了仓颉语言 “防御式编程”。

添加的 if 检查和 throw 语句,正是 “动态防御” 的关键 —— 主动预判可能的错误(用户输入越界),并将其转化为可被 try-catch 处理的异常,这正是 “防御式编程” 的核心思想:“不要假设一切都会按预期运行,而是主动预判风险并处理”。对于在编译期无法确定、依赖于运行时数据(用户输入、网络返回、文件内容等)的潜在错误,通过抛出异常的方式提供给开发者一个捕获和处理的机会,保证程序不会因此崩溃。

仓颉的这种处理方式,本质是 “静态防御”+“动态防御” 的双重保险。

throw 和try 表达式

异常处理主要涉及:

  •  根据是否涉及资源的自动管理,将 try 表达式分为两类:不涉及资源自动管理的普通 try 表达式,以及会进行资源自动管理的 try-with-resources 表达式。

  •  throw 表达式(throw expression)由关键字 throw 以及尾随的表达式组成。尾随表达式的类型必须继承于 Exception 或 Error 类。

throw 负责“抛出”问题,try-catch 负责“接收并处理”问题。

throw的语法和用法

基本语法

throw 异常对象

说明,throw 用于 主动抛出异常对象,语法极其简单,但有两点必须牢记:

(1).只能抛 Exception 及其子类(Error 类不允许手动 throw)。

(2).抛出的是 对象,不是类名,也不是字符串字面量。

常见错误写法:

throw Exception              // ❌ 不是对象

throw "something wrong"      // ❌ 字符串不行

throw IO.Error               // ❌ Error 子类不允许手动抛

示例:整数四则运算计算器

import std.convert.*/* ===================== 自定义异常 ===================== */
class InvalidInputException <: Exception {public init(msg: String) {super(msg)}
}class DivideByZeroException <: Exception {public init() {super("除数不能为零")}
}class UnsupportedOperatorException <: Exception {public init(op: String) {super("不支持的运算符:${op}")}
}/* ===================== 核心业务 ===================== */
// 把字符串变成 Int64,失败就抛
func parseIntStrict(s: String): Int64 {try {return Int64.parse(s)} catch (e: Exception) {throw InvalidInputException("整数格式错误:${s}")}
}// 计算核心:一旦非法立即 throw
func calculate(a: Int64, op: String, b: Int64): Int64 {match (op) {case "+" => a + bcase "-" => a - bcase "*" => a * bcase "/" =>if (b == 0) {throw DivideByZeroException()}a / bcase _   => throw UnsupportedOperatorException(op)}
}/* ===================== 命令行交互 ===================== */
main() {println("=== 命令行整数计算器( 运算符op 支持 +  -  *  / ;exit 退出)===")println("请输入表达式,格式:a op b (例:123 + 456)")while (true) {print(">>> ")let line = readln()if (line.isEmpty()) {continue}if (line == "exit") {println("再见!")break}/* --------- 解析 --------- */let parts = line.split(" ")if (parts.size != 3) {println("❌ 格式错误!请用空格分隔:a op b")continue}/* --------- 业务 + 异常捕获 --------- */try {let a  = parseIntStrict(parts[0])let op = parts[1]let b  = parseIntStrict(parts[2])let result = calculate(a, op, b)println("结果:${result}")} catch (e: InvalidInputException) {println("❌ 输入非法:${e.message}")} catch (e: DivideByZeroException) {println("❌ 除零错误:${e.message}")} catch (e: UnsupportedOperatorException) {println("❌ 运算符错误:${e.message}")} catch (e: Exception) {println("❌ 未知错误:${e.message}")}}
}

编译运行截图:

try 表达式

仓颉通过try表达式处理异常,分为普通 try(无资源自动管理)和try-with-resources(自动释放资源)两种。

1.普通 try 表达式

可以捕获并处理异常,防止程序崩溃。包括三个部分:try 块,catch 块和 finally 块。基本语法:

try {
    // 可能出错的代码
} catch (e: 异常类型) {
    // 处理异常
} finally {
    // 无论是否出错都会执行(可选)
}

说明:

  • try 块:存放可能抛出异常的代码;

  • catch 块:捕获并处理异常(可多个,按 “子类在前、父类在后” 顺序,否则报 “不可达” 警告);

  • finally 块:无论是否抛出异常,必执行(用于释放资源、清理操作)。

注意:

  • 无 catch 块时,必须有 finally 块;

  • 有 catch 块时,finally 块可选;

  • try、catch 块的作用域相互独立,变量不共享。

示例

// 模拟除法运算,处理除零异常(假设仓颉内置ArithmeticException)
func divide(a: Int64, b: Int64): Int64 {if (b == 0) {throw ArithmeticException("除零错误:除数不能为0") // 抛出算术异常}return a / b
}main() {try {let result = divide(10, 0) // 触发除零异常println("计算结果:${result}")} catch (e: ArithmeticException) {println("捕获异常:${e.toString()}") // 调用toString()输出异常类型+信息}println("程序继续执行……")
}

编译运行截图:

示例2:从键盘读入数值,并保证用户输入1到l00之间的整数。

import std.convert.*  // 用于 Int64.parse() 函数// 函数:从键盘读取1-100之间的整数(循环重试直到输入合法)
func readIntBetween1And100(): Int64 {var inputValid = false  // 标记输入是否合法var result: Int64 = 0   // 存储最终合法的输入值// 循环重试:直到输入合法才退出while (!inputValid) {// 1. 提示用户输入print("请输入1到100之间的整数:")// 读取键盘输入(readln() 直接返回 String)let inputStr = readln()// 2. 处理“无输入”情况(空串用 isEmpty() 判断)if (inputStr.isEmpty()) {println("错误:未输入任何内容,请重新输入!")continue  // 跳过后续逻辑,直接进入下一次循环}// 3. 尝试将输入字符串转换为整数(处理格式错误)try {// 将字符串转为Int64(若不是纯数字会抛出异常)result = Int64.parse(inputStr)// 4. 校验数值是否在1-100范围内if (result >= 1 && result <= 100) {inputValid = true  // 输入合法,标记为true以退出循环println("输入正确!您输入的数值是:${result}")} else {println("错误:数值超出范围!请输入1到100之间的整数,重新输入!")}} catch (e: Exception) {// 捕获“字符串无法转为整数”的异常(如输入字母、符号、小数等)println("错误:输入格式不合法!请仅输入纯数字,重新输入!")}}return result
}// 主函数:调用输入函数并演示使用
main() {println("=== 整数输入校验演示 ===")let validNum = readIntBetween1And100()// 后续可使用合法输入值(如:输出输入值的2倍)println("您输入数值的2倍是:${validNum * 2}")
}

2.try-with-resources 表达式

仓颉提供了 try-with-resources 来自动管理资源。新手注意其中with 和 resources 都不是关键字,是一种描述性称呼。这是一种专门用于自动管理资源的异常处理机制,主要解决资源(如文件、网络连接、数据库连接等)的释放问题,确保资源在使用后能被正确关闭,无需手动在 finally 块中处理。

语法:

try (资源声明1, 资源声明2, ...) {
    // 使用资源的代码块
} [catch (异常模式) {
    // 异常处理(可选)
}] [finally {
    // 最终操作(可选)
}]

说明

(1)资源声明部分:

  • 在 try 关键字后的括号中声明资源,多个资源用逗号分隔

  • 资源类型必须实现 Resource 接口(包含 isClosed() 和 close() 方法)

  • 声明格式:变量名 = 资源实例(如 file = File("data.txt"))

(2)try 后的花括号内是使用资源的业务逻辑

(3)可选部分:

  • catch 块:捕获处理代码块或资源操作中可能抛出的异常

  • finally 块:无论是否发生异常都会执行的代码(通常无需使用,因资源已自动释放)

示例:

import std.fs.*// 生成函数:用于数组初始化
func zero(_: Int64): UInt8 {UInt8(0)
}// 1. 正确实现的文件包装类(实现Resource接口)
class AutoFile <: Resource {private let file: Fileprivate var closed: Bool = false  // 避免与isClosed()方法重名// 构造函数:使用正确的OpenMode枚举public init(path: String, mode: OpenMode) {file = File(path, mode)}// 写入字符串(兼容标准库的字符编码转换)public func write(content: String): Unit {if (closed) {throw Exception("文件已关闭,无法写入")}// 字符串转字节数组(手动实现)let n = content.sizevar bytes = Array<UInt8>(n, zero)for (i in 0..n) {bytes[i] = UInt8(Int64(content[i]))}file.write(bytes)}// 读取全部内容public func readAll(): String {if (closed) {throw Exception("文件已关闭,无法读取")}// 读取文件内容到缓冲区var buffer = Array<UInt8>(1024, zero)  // 正确初始化数组let bytesRead = file.read(buffer)return String.fromUtf8(buffer[0..bytesRead])}// Resource接口:检查是否已关闭public func isClosed(): Bool {closed}// Resource接口:关闭文件(自动调用)public func close(): Unit {if (!closed) {file.close()closed = trueprintln("文件已自动关闭(资源释放)")}}
}// 2. 主函数:使用try-with-resources
main() {let filePath = "test.txt"// 写入文件(自动管理资源)try (writer = AutoFile(filePath, OpenMode.Write)) {  // 使用OpenMode枚举writer.write("Hello, 仓颉语言!\n")writer.write("这是使用try-with-resources的示例")// 可以取消下面这行的注释来测试异常情况// throw Exception("测试异常")} catch (e: Exception) {println("写入失败:${e.message}")}// 读取文件(再次使用自动资源管理)try (reader = AutoFile(filePath, OpenMode.Read)) {  // 使用OpenMode枚举let content = reader.readAll()println("文件内容:\n${content}")} catch (e: Exception) {println("读取失败:${e.message}")}
}

编译运行输出:

文件已自动关闭(资源释放)
文件内容:
Hello, 仓颉语言!
这是使用try-with-resources的示例
文件已自动关闭(资源释放)

3.CatchPattern 的类型模式和通配符模式

CatchPattern 是仓颉语言中在 try-catch 异常处理结构中,用于精确捕获和匹配特定类型异常的一种模式语法。它决定了当 try 块中抛出异常时,哪个 catch 块应该被激活来处理该异常。

(1). 类型模式:匹配单个 / 多个异常

  • 单个异常:e: 异常类(单类,如e: OrderException);

  • 多个异常:e: 异常类1 | 异常类2(多类“或”关系,用|连接,匹配任意一个,变量类型为多个异常的 “最小公共父类”——捕获后,异常会被 转换 为这几个类的 最小公共父类,因此通过 e 只能访问父类的属性和方法,无法访问子类独有的内容)。

示例:批量捕获异常

main(): Int64 {try {throw IllegalArgumentException("这是一个异常!")} catch (e: OverflowException) {println(e.message)println("OverflowException 被捕获!")} catch (e: IllegalArgumentException | NegativeArraySizeException) {println(e.message)println("IllegalArgumentException 或 NegativeArraySizeException 被捕获!")} finally {println("终于执行了!")}return 0
}

编译运行输出:

这是一个异常!

IllegalArgumentException NegativeArraySizeException 被捕获!

终于执行了!

(2). 通配符模式:匹配所有异常

用_表示通配符,不绑定任何变量,捕获try块中抛出的任意Exception子类,适合 “统一处理所有未预料异常” 的场景。等价于 catch (e: Exception),但省掉了变量名。

示例:通配符捕获

main(): Int64 {try {throw OverflowException()} catch (_) {println("捕获一个异常!")}return 0
}

常见运行时异常

在仓颉语言中内置了最常见的异常类,开发人员可以直接使用。

异常

描述

ConcurrentModificationException

并发修改产生的异常

IllegalArgumentException

传递不合法或不正确参数时抛出的异常

NegativeArraySizeException

创建大小为负的数组时抛出的异常

NoneValueException

值不存在时产生的异常,如 Map 中不存在要查找的 key

OverflowException

算术运算溢出异常

示例:定义一个可能抛出异常的除法函数

// 定义一个可能抛出异常的除法函数
func divide(a: Int, b: Int): Int {if (b == 0) {// 当除数为0时,抛出异常(throw的类型是Nothing,后续代码不执行)throw NoneValueException("除数不能为0")  // 假设throw可以携带错误信息(字符串类型)// 以下代码永远不会执行(因为throw中断了流程)}a / b  // 正常情况返回除法结果
}main() {let num1 = 10;let num2 = 0;  // 除数为0,会触发异常// 使用try-catch捕获并处理异常try {let result = divide(num1, num2);println("${num1} / ${num2} = ${result}");  // 若正常执行,打印结果} catch (e:NoneValueException) {// 捕获到throw抛出的异常,处理错误println("发生错误:${e}");  // 输出错误信息}// 异常被处理后,程序继续执行后续代码println("程序继续运行...");
}

错误处理中使用Option 类型

Option类型是仓颉的 “无值 / 有值” 标记类型(本质是枚举),可用于替代简单异常场景(如 “值不存在” 无需抛异常,用None表示即可)。

Option有两种状态:

  • Some(v):表示 “有值”,v为具体值;

  • None:表示 “无值/空值”,对应 “无结果” 或 “轻微错误”。

常用解构使用方式

1. 模式匹配(最灵活)

通过match匹配Some/None,分别处理有值和无值场景。示例源码:

// 从Option中提取字符串(有值返回内容,无值返回默认值)
func getValue(opt: ?String): String {match (opt) {case Some(str) => "获取到值:${str}"case None => "无值(默认返回空字符串)"}
}main() {let a = Some("Hello Cangjie")let b: ?String = None // 显式声明为Noneprintln(getValue(a))  // 输出:获取到值:Hello Cangjieprintln(getValue(b))  // 输出:无值(默认返回空字符串)
}

2. Coalescing 操作符(??):无值时返回默认值

语法:e1 ?? e2,若e1是Some(v)则返回v,否则返回e2(默认值)。示例源码:

main() {let score1: ?Int64 = Some(90) // 有值let score2: ?Int64 = None     // 无值// 无值时默认返回0let final1 = score1 ?? 0let final2 = score2 ?? 0println("分数1:${final1}")  // 分数1:90println("分数2:${final2}")  // 分数2:0
}

3. 问号操作符(?):链式访问无值安全

用于链式调用(如., (), []),若中间环节为None,则整个表达式返回None。

可以把 ?. 想成 “安全电梯”:只要前面是 None,电梯停运,整层表达式直接变 None,不抛异常。问号操作符(?)后面的 点(.) 就是 普通的成员访问符,功能跟正常对象用 .一样。示例源码:

// 定义层级:A → B → Option<C> → d
class A { public var b: B = B() }
class B {public var c: Option<C> = Some(C())public var c1: Option<C> = None
}
class C { public var d: Int64 = 100 }main() {let a = Some(A())// 读取:层层安全拆包let v1 = a?.b.c?.d     // Some(100)let v2 = a?.b.c1?.d    // None// 写入:同样短路a?.b.c?.d = 200        // 成功,a.b.c.d 现在是 200a?.b.c1?.d = 300       // 短路,无效果println("v1 = ${v1}")   // Some(200)println("v2 = ${v2}")   // None
}

其中,class A { public var b: B = B() },表示定义一个类 A,它里面有一个公共的成员变量 b,类型是 B,并且初始化为 B() 的一个新实例。

路径图示:

a(Some)

├─b(B)───c(Some)───d(100) → Some(100)

└─b(B)───c1(None)         → 短路→ None

4. getOrThrow ():无值时抛出异常

当 “无值” 属于严重错误时,用getOrThrow()提取值,若为None则抛出NoneValueException。示例源码:

main() {let data: ?Int64 = None // 无值数据try {let value = data.getOrThrow() // 无值时抛出异常println("数据值:${value}")} catch (e: NoneValueException) {println("处理错误:${e.message}(数据不存在)")}
}

附录:相关官方文档

https://cangjie-lang.cn/docs?url=%2F1.0.0%2Fuser_manual%2Fsource_zh_cn%2Ferror_handle%2Fcommon_runtime_exceptions.html

https://cangjie-lang.cn/docs?url=%2F1.0.0%2Fuser_manual%2Fsource_zh_cn%2Ferror_handle%2Fexception_overview.html

http://www.dtcms.com/a/404164.html

相关文章:

  • 政务网站建设及管理搜索引擎优化的名词解释
  • AP2协议与AI Agent经济:重塑金融生态与主权国家挑战
  • 机器学习之损失函数
  • 语义三角论对人工智能自然语言处理的深层语义分析的影响与启示
  • 佛山市住房和城乡建设部网站姓氏变logo设计免费生成
  • DOS与DDOS攻击防御详解
  • 【Git】基本概念 + 基操
  • 建设优化网站wordpress侧导航菜单
  • 网站建设项目执行情况报告模板手机wap网站下载
  • 【笔试强训】Day01
  • 网站的想法夫唯seo
  • 阿里云建站套餐旅游网站排行榜前十名官网
  • 上饶网站开发 app开发做移动网站点击软件
  • 常州微信网站建设流程北京免费模板建站
  • AKS论文阅读
  • 快捷的赣州网站建设网站开发的逻辑
  • 【图论】【数据结构】图的深度优先与广度优先遍历、最短路径
  • 制作网站作业wordpress设置关键字
  • 网站建设 seo模块泉州企业网站维护定制
  • 如何做音乐分享类网站个人网页制作模板田田田田田田田田
  • 网站建设需求说明书怎么写做婚礼效果图的网站有哪些
  • 温州免费建站私密浏览器视频
  • 速通ACM省铜第十四天 赋源码(Coloring Game)
  • 淮安做网站seo海南省建设注册执业资格中心网站
  • 阿里云 PAI 携手 NVIDIA 提供端到端物理 AI 解决方案
  • 如何用织梦程序制作多个页面网站承接电商网站建设
  • location配置 rewrite配置
  • 建设网站查证书哈尔滨网站建设教学
  • 弧光之源网站建设永久免费国外vps无需信用卡
  • 学校网站建河北省建设厅网站重新安装