【仓颉语言入门 · 第11课】函数定义、参数与返回值

前 10 课我们其实一直在"用"函数:mainprintlnInt64.parse、第 10 课的 parseAge……但函数到底有多少讲究,始终没有系统讲过。这节课就把仓颉函数最常用的家底一次盘清:非命名参数与命名参数(!)、默认值、可变参数、多返回值(元组)、Unit、函数重载、递归,最后再看一眼"函数是一等公民"意味着什么(Lambda 与闭包留给第 12、13 课)。

写作过程中仍然发现网上不少二手教程互相抄串了:把其他语言的写法安到仓颉头上,或者描述的是更新版本的行为。因此本文所有代码与报错文案均在仓颉 SDK 1.1.3 下逐行实测编译运行。途中会澄清四个高频坑:默认值不能给普通参数、命名参数靠 ! 声明而非调用时的小技巧、可变参数没有任何特殊符号、String.repeat 在 1.1.3 不存在。


目录(系列导航)

整套路线共 7 个模块、30 课,按每周 2~3 课的节奏,大约 2~3 个月可以走完一遍:

| 模块 | 课次 | 内容 |
| — | — | — — |
| 一、环境与入门 | 01~05 | 环境搭建与 Hello World、变量与基本类型、运算符与输入输出、分支、循环 |
| 二、常用类型与数据组织 | 06~10 | 字符串、数组与区间、ArrayList/HashMap/HashSet、可空类型、错误处理 |
| 三、函数与函数式 | 11~14 | 函数、Lambda 与高阶函数、闭包、迭代器与惰性序列 |
| 四、面向对象与类型系统 | 15~20 | struct/class、构造与属性、接口、枚举与 match 模式匹配、泛型、扩展 |
| 五、工程化与标准库 | 21~25 | cjpm 包管理与多文件、文件 IO、JSON 处理、网络编程、单元测试 |
| 六、并发编程 | 26~28 | 线程、Channel 通道与同步原语、并发实战 |
| 七、项目实战 | 29~30 | 命令行小工具、GeoJSON 数据处理实战 |

  1. 环境搭建与第一个仓颉程序
  2. 变量与常量:let / var 与基本数据类型
  3. 运算符与标准输入输出
  4. 分支结构:if 与 match 表达式
  5. 循环结构:while / for / Range
  6. 字符串详解与字符串插值
  7. 数组 Array 与区间 Range
  8. 集合框架:ArrayList、HashMap、HashSet
  9. 可空类型 ? 与 Option
  10. 错误处理:异常机制与 Result
  11. 函数定义、参数与返回值(本文)
  12. Lambda 与高阶函数
  13. 闭包、作用域与函数类型
  14. 迭代器 Iterator 与 Sequence
  15. 结构体 struct 与类 class
  16. 构造函数、属性与方法
  17. 接口 interface 与实现
  18. 枚举 enum、代数数据类型与 match 模式匹配
  19. 泛型编程
  20. 扩展、类型别名与可见性控制
  21. cjpm 包管理与多文件项目组织
  22. 文件与目录 IO
  23. JSON 处理(结合 stdx 扩展库)
  24. 网络编程入门
  25. 单元测试
  26. 并发基础:线程的创建与等待
  27. Channel 通道与同步原语
  28. 并发实战:多线程任务处理
  29. 实战一:带文件持久化的命令行小工具
  30. 实战二:GeoJSON 数据处理程序

一、函数的定义与调用

1.1 基本语法

仓颉用 func 定义函数,一个完整的函数长这样:

func add(a: Int64, b: Int64): Int64 {
    return a + b
}

从左到右依次是:关键字 func、函数名 add、括号里的参数列表参数名: 类型)、冒号后的返回类型、花括号里的函数体。调用就是熟悉的 add(2, 3),值为 5

参数列表和返回类型都可以"缺席":

func sayHello() {            // 无参数;无返回值(返回类型可省略)
    println("你好")
}

1.2 函数体的最后一个表达式就是返回值

和 if、match 一样,函数体也走"最后一个表达式即值"的规则:

func add1(a: Int64, b: Int64): Int64 {
    a + b            // 没有写 return,最后一个表达式就是返回值(SDK 1.1.3 实测可编译)
}

add1(2, 3) 的结果同样是 5。当然显式写 return a + b 也完全合法。需要提前返回(比如循环里找到就撤)或者函数中有多个返回点时,老老实实用 return

1.3 main 也是函数,而且有两种写法

前 10 课我们的 main 始终写作 main(): Int64 并以 return 0 收尾——这是在告诉操作系统"程序正常退出"。其实不关心退出码时,它也可以省略返回类型:

main() {
    println("两种 main 都合法")
}

两种写法 SDK 1.1.3 均实测通过。本系列统一使用 main(): Int64 + return 0 的写法,好处是退出码明确、需要时可以用非 0 值向脚本报告失败。

📌 同一个文件里函数的定义先后无所谓main 写在最前面、调用定义在它后面的函数也完全正常,仓颉不需要 C 语言那样的前向声明。


二、两套参数:非命名参数与命名参数

这是仓颉参数体系里最需要先建立的概念。参数分两种:

非命名参数(位置参数)命名参数
声明name: Stringname!: String(名字后加 !
调用必须按顺序给值:greet("小明")必须带名字给值:greet(name: "小明")
默认值不允许允许
顺序必须全部排在命名参数之前排在后面,调用时顺序可任意

2.1 命名参数要用 ! 声明

注意那个感叹号——它写在声明处,而不是调用处。调用处用的是"参数名 + 冒号":

func greet(name: String, greeting!: String = "你好", punct!: String = "!"): String {
    return "${greeting},${name}${punct}"
}

main(): Int64 {
    println(greet("小明"))                              // 你好,小明!
    println(greet("小红", greeting: "早上好"))           // 早上好,小红!
    println(greet("小刚", greeting: "晚上好", punct: "~")) // 晚上好,小刚~
    println(greet("小李", punct: "??"))                  // 你好,小李??(跳过中间的 greeting)
    println(greet("小王", punct: "。", greeting: "嗨"))    // 嗨,小王。(命名参数顺序可调换)
    return 0
}

实测输出:

你好,小明!
早上好,小红!
晚上好,小刚~
你好,小李??
嗨,小王。

命名参数带来两个实打实的好处:

  1. 跳过中间参数:只想改最后的标点,用 punct: "??" 即可,不必把前面的参数照抄一遍;
  2. 调用处自带说明report(..., title: "喜报", line: "*")report(..., "喜报", "*") 可读性强得多,布尔型参数尤其明显(retry: true 一眼懂,true 就只能去翻定义)。

2.2 三条编译器帮你守着的规矩

规矩一:命名参数必须用命名方式传递。 声明带 ! 的参数,调用时不写前缀直接塞值,编译器报错(实测文案):

error: missing argument prefix 'a:' for named parameter

规矩二:非命名参数不能用命名方式传。 反过来,普通参数 name: String 调用时写 name: "小王" 也不行:

error: invalid named arguments prefix 'name:', target is not a named parameter

规矩三:非命名参数必须排在命名参数之前。 把声明写成 func bad(a!: Int64, b: Int64),编译器直接拒绝:

error: unnamed parameters must come before named parameters

记忆方式很简单:位置参数在前、命名参数在后,和调用时的排列完全一致

🚫 辟谣:有些从 Kotlin/Swift 抄来的教程会让你以为"命名参数只是调用时的写法,任何参数都能 参数名 = 值 传"。仓颉不是这样:命名资格必须在声明时用 ! 显式申请,而且调用处的连接符是冒号 : 不是等号 =(写 greeting = "早上好" 是赋值表达式,不是命名实参)。


三、默认参数值

3.1 默认值只能给命名参数

给命名参数一个默认值,调用时就可以省略它(第一节的 greeting!: String = "你好" 即是)。但要特别注意:普通(非命名)参数不允许有默认值。下面这种在别的语言里很常见的写法,仓颉 1.1.3 编译不过:

func bad(a: Int64 = 5): Int64 {   // ❌
    return a
}
error: expected ',' or ')', found '='
 ==> ...src/main.cj:3:19:
3 | func bad(a: Int64 = 5): Int64 {
  |                   ^ expected ',' or ')' here

想要"可省略"的效果,把参数声明成命名参数即可:func ok(a!: Int64 = 5)。这是仓颉一个有意的设计取舍:位置参数靠位置辨认,若允许中间缺省,f(1, 2) 到底省略了谁就会歧义丛生。

3.2 默认值可以引用前面的参数

默认值表达式并不只能写字面量,它可以引用在它之前已经出现的参数:

func makeRect(width: Int64, height!: Int64 = width * 2): Int64 {
    return width * height
}

main(): Int64 {
    println("${makeRect(4, height: 5)}")   // 20:显式给值
    println("${makeRect(4)}")               // 32:height 取默认值 width*2 = 8,面积 4*8
    return 0
}

实测输出 2032。这类"第二个参数默认跟随第一个参数"的场景(比如正方形默认宽高相等)在实际开发中相当常用。


四、可变参数:Array 形参的"双重身份"

4.1 没有特殊符号

很多语言有变参专用符号(C 的 ...、Java 的 ...)。仓颉的设计出人意料地朴素:当最后一个非命名参数的类型是 Array<T> 时,这个函数就自动可以接收任意多个 T 实参——不需要任何额外语法。

func sumAll(nums: Array<Int64>): Int64 {
    var total = 0
    for (n in nums) {
        total += n
    }
    return total
}

main(): Int64 {
    println("${sumAll(1, 2, 3, 4, 5)}")   // 15:展开传,任意多个
    println("${sumAll()}")                 // 0:一个都不传,得到空数组
    println("${sumAll([10, 20])}")         // 30:直接传一整个数组也行
    return 0
}

实测输出 15030。同一个形参有两种传法:逐个展开(编译器帮你打包成数组),或把一个现成数组整体交出去。函数体内部则永远把它当普通 Array<Int64> 处理。

4.2 和普通参数混用

可变参数只是"恰好排在最后"的数组参数,它前面可以有任意普通参数:

func join(sep: String, parts: Array<String>): String {
    var result = ""
    for (i in 0..parts.size) {
        if (i > 0) { result += sep }
        result += parts[i]
    }
    return result
}

join(",", ["苹果", "香蕉", "橘子"])   // 整体传
join(",", "苹果", "香蕉", "橘子")    // 展开传,两者等价

⚠️ 两条限制:① 享受"展开传"待遇的必须是最后一个非命名参数(它后面只能再跟命名参数);② 展开的实参类型必须统一为 T,不能混类型。

🚫 顺便辟个谣:仓颉 1.1.3 里写 func f(...nums: Int64)func f(nums: Int64...) 都编译不过(分别报 expected ';' or '<NL>', found 'nums'expected ';' or '<NL>', found '...')。变参靠的是 Array 类型,不是省略号。


五、返回值与 Unit

5.1 return 的两种用法

  • return 表达式;:带着结果立刻离开函数;
  • return;(或函数体走完):无返回值函数的结束。

第 10 课已见过 finally 能拦在 return 之前执行,这里不再重复。

5.2 Unit:没有有意义结果的函数

打印日志、修改外部状态这类函数,调用方只关心它的副作用,不关心返回值。它们的返回类型是 Unit,可以显式写出,也可以直接省略:

func logPrint(msg: String) {          // 省略返回类型,等价于 : Unit
    println("[LOG] ${msg}")
}

func logPrint2(msg: String): Unit {   // 显式写出 Unit,同样合法
    println("[LOG] ${msg}")
}

第 1~5 课其实已经大量接触 Unit:println 的返回类型就是 Unit,赋值 =、自增 ++、while/for 循环表达式的类型也都是 Unit——它们"做事"但"不产出值"。

5.3 返回类型必须对得上

声明了 : Int64,所有出口就都必须给出 Int64:体表达式走最后一行,提前退出走 return。类型不一致时编译器会直接指出,不用等到运行时。


六、多返回值:元组与解构

想让一个函数同时返回"商和余数""最高分和最低分"怎么办?仓颉用元组(Tuple)——把多个类型可以不同的值打包成一对括号:

func divMod(a: Int64, b: Int64): (Int64, Int64) {
    return (a / b, a % b)        // 返回类型写作 (Int64, Int64)
}

main(): Int64 {
    let r = divMod(17, 5)
    println("商=${r[0]} 余=${r[1]}")     // 下标访问,从 0 开始

    let (q, m) = divMod(20, 6)           // 解构:一次拆成两个变量
    println("商=${q} 余=${m}")

    let (q2, _) = divMod(9, 2)           // 不需要的分量用 _ 占位
    println("只要商=${q2}")
    return 0
}

实测输出:

商=3 余=2
商=3 余=2
只要商=4

三个要点:

  1. 返回类型写成 (类型1, 类型2, ...),元组里各分量的类型不必相同((Int64, String, Bool) 也合法);
  2. 接收可以整体接收后用 r[0]r[1] 下标取,也可以用 let (a, b) = ... 一步解构;
  3. 忽略分量用下划线 _,和第 5 课 for 循环里的通配符、第 10 课 catch (_) 是同一个精神。

💡 元组适合"临时打包两三个相关值"。如果分量多到五六个、或者这套数据要在很多函数间传来传去,就该定义 struct 了(第 15 课)。


七、递归:函数自己调用自己

函数体内可以调用任何函数,包括它自己。每个递归只需要两样东西:一个不断缩小问题规模的递推式,和一个触底即停的基线条件

func factorial(n: Int64): Int64 {
    if (n <= 1) {
        return 1                 // 基线条件:n<=1 时不再递归
    }
    return n * factorial(n - 1)  // 递推:n! = n * (n-1)!
}

factorial(5) 的展开过程是 5 * 4 * 3 * 2 * 1,实测结果 120。如果漏掉基线条件,函数会一直调用自己直到栈耗尽崩溃——写递归后先问自己一句:“它什么时候停?”

第 5 课的循环、第 14 课的迭代器、这里的递归,常常能解决同一批问题;递归胜在贴近数学定义(树、分治、目录遍历尤其自然),循环胜在直观省栈。入门阶段先能读懂和写出阶乘、斐波那契这个级别的递归即可。


八、函数重载:同名不同参

8.1 按参数列表区分

同一个函数名可以定义多次,只要它们的参数个数或参数类型不同:

func format(x: Int64): String {
    return "整数 ${x}"
}
func format(x: String): String {
    return "字符串 ${x}"
}
func format(x: Int64, y: Int64): String {
    return "两个整数 ${x}, ${y}"
}

调用 format(42)format("hi")format(1, 2) 时,编译器根据实参的类型和个数自动挑选对应版本(实测三者分别输出 整数 42字符串 hi两个整数 1, 2)。这叫重载决议,整个过程发生在编译期,没有运行时开销。

8.2 仅返回类型不同不算重载

如果两个函数参数列表完全一样、只是返回类型不同,编译器无法凭调用处分辨该选谁——因为返回值可以被丢弃:

func foo(x: Int64): Int64 { return x }
func foo(x: Int64): String { return "${x}" }   // ❌
error: function 'foo' has overload conflicts
error: ambiguous match for function call 'foo'

记住区分重载的签名只看参数(类型 + 个数),返回类型不参与。


九、一等公民初印象

"函数是一等公民"的意思是:函数和数字、字符串一样,是一种有类型、能赋值、能传递的值。

9.1 函数类型与函数作参数

函数的类型写作 (参数类型列表) -> 返回类型。比如"吃一个 Int64、吐一个 Int64"的函数,类型是 (Int64) -> Int64。于是函数可以成为另一个函数的参数:

func applyTwice(x: Int64, f: (Int64) -> Int64): Int64 {
    return f(f(x))
}

func square(x: Int64): Int64 {
    return x * x
}

main(): Int64 {
    println("${applyTwice(3, square)}")        // 81:直接把函数名 square 当值传进去
    println("${applyTwice(3, {x => x + 1})}")  // 5:也可以传一个匿名 Lambda(第12课细讲)
    return 0
}

注意传命名函数时直接写函数名即可,不需要取地址符。applyTwice 不关心 f 具体是谁,只要求它符合 (Int64) -> Int64 这个形状——调用方传入平方,它就套两遍平方;传入加一,它就套两遍加一。这种"把动作参数化"的能力就是高阶函数的起点,第 12 课会展开讲透。

9.2 嵌套函数:函数里还能定义函数

函数可以定义在另一个函数内部,内层函数只在外层函数体内可见,还能直接访问外层的局部变量:

func outer(): Int64 {
    let base = 100
    func inner(delta: Int64): Int64 {
        return base + delta      // 直接读到了外层的 base
    }
    return inner(5) + inner(20)  // 105 + 120 = 225
}

实测 outer() 的值为 225。内层函数"记住"外层变量的机制叫闭包,是第 13 课的主题;本节只需知道嵌套定义合法、且内层能看到外层。


十、CIDE 实操:成绩报告单

10.1 编写程序

新建工程(或单文件),输入下面的程序。它把本节课的要素串了起来:变长参数风格的 Array 形参、元组多返回值与解构、函数重载、命名参数与默认值、Unit 函数、函数作参数。

// ① 用分隔符拼接字符串(最后一个 Array 形参也可整体传数组)
func join(sep: String, parts: Array<String>): String {
    var result = ""
    for (i in 0..parts.size) {
        if (i > 0) {
            result += sep
        }
        result += parts[i]
    }
    return result
}

// ② 多返回值:最高分、最低分、平均分
func stats(scores: Array<Float64>): (Float64, Float64, Float64) {
    var hi = scores[0]
    var lo = scores[0]
    var sum = 0.0
    for (s in scores) {
        if (s > hi) { hi = s }
        if (s < lo) { lo = s }
        sum += s
    }
    return (hi, lo, sum / Float64(scores.size))
}

// ③ 重载:判定单个成绩 / 统计及格人数
func pass(score: Float64): String {
    if (score >= 60.0) { return "及格" }
    return "不及格"
}
func pass(scores: Array<Float64>): Int64 {
    var count = 0
    for (s in scores) {
        if (s >= 60.0) { count++ }
    }
    return count
}

// ④ 小工具:把一个字符重复 n 次
// 注意:SDK 1.1.3 的 String 没有 repeat 方法,自己用循环拼
func mulLine(ch: String, n: Int64): String {
    var s = ""
    for (_ in 0..n) {
        s += ch
    }
    return s
}

// ⑤ 报告打印:非命名参数在前,命名参数(带默认值)在后
func report(name: String, scores: Array<Float64>, title!: String = "成绩报告单", line!: String = "="): Unit {
    println(mulLine(line, 20))
    println("${title}:${name}")
    let (hi, lo, avg) = stats(scores)
    println("最高 ${hi},最低 ${lo},平均 ${avg}")
    println("${join(",", scores.map({s => "${s}"}))}")
    println("及格 ${pass(scores)}/${scores.size} 人;最后一位 ${pass(scores[scores.size - 1])}")
    println(mulLine(line, 20))
}

main() {
    report("小明", [88.0, 55.5, 73.0, 92.0, 60.0])
    report("小红", [100.0, 99.0], title: "喜报", line: "*")
}

说明:scores.map({s => "${s}"}) 用到了 Lambda,照抄即可,第 12 课会专门讲;这里它的作用是把每个浮点成绩转成字符串,好交给 join 拼接。

10.2 运行与验证

在 CIDE 中点运行(或 cjpm run),应得到与下面逐字一致的输出(浮点数默认保留 6 位小数):

====================
成绩报告单:小明
最高 92.000000,最低 55.500000,平均 73.700000
88.000000,55.500000,73.000000,92.000000,60.000000
及格 4/5 人;最后一位 及格
====================
********************
喜报:小红
最高 100.000000,最低 99.000000,平均 99.500000
100.000000,99.000000
及格 2/2 人;最后一位 及格
********************

观察两处细节:

  • 第一次调用只给位置参数,titleline 全部取默认值;第二次调用用命名参数同时换了标题和分隔符,且顺序与声明相反——编译器照样正确匹配;
  • pass(scores)pass(scores[...]) 长得几乎一样,但一个传数组、一个传浮点数,重载决议分别选中了"统计人数"和"判定单个"两个版本,返回类型都不同。

可以自己动手改:

  • report 声明里 title!! 去掉但保留默认值,亲眼看 expected ',' or ')', found '=';再把调用处的 title: "喜报" 改成位置实参,看 missing argument prefix
  • stats 增加一个返回分量"极差"(hi - lo),同步修改解构处;
  • pass 的两个重载改成仅返回类型不同、参数列表一致,编译并记录 overload conflicts
  • 用递归写一个 fib(n)(斐波那契:fib(0)=0, fib(1)=1, fib(n)=fib(n-1)+fib(n-2)),打印前 10 项。

10.3 用调试器观察递归与多返回值

  1. factorial(练习里写的版本)的 return n * factorial(n - 1) 一行打断点,Shift + F9 启动调试;
  2. 单步进入(F7factorial(n-1),观察左侧 Call Stack:帧一层层堆叠成 factorial → factorial → factorial ……,直到 n <= 1 触底;
  3. 触底后用 F8 单步,看调用栈一层层"退货"——返回值逐层相乘,这是理解递归执行顺序最直观的方式;
  4. statsreturn (hi, lo, ...) 处打断点,断住后在 Variables 中确认三个局部变量的值;单步回到 report,观察解构 let (hi, lo, avg) = ... 如何一次把元组拆开。

调试结束点停止按钮。


十一、常见问题 FAQ

Q1:仓颉函数怎么写默认参数?为什么 func f(a: Int64 = 5) 编译报错?
仓颉 1.1.3 中只有命名参数(声明带 !)才能有默认值,非命名参数写 = 默认值 会报 expected ',' or ')', found '='。正确写法是 func f(a!: Int64 = 5),调用时写 f(a: 3) 或省略为 f()

Q2:命名参数的 ! 写在哪里?调用时为什么报 missing argument prefix?
! 写在声明处的参数名后func f(a!: Int64);调用处用冒号传值:f(a: 1)。带 ! 的参数不能再按位置传(否则报 missing argument prefix 'a:' for named parameter);反过来普通参数也不能用命名方式传。

Q3:可变参数怎么写?是 Int64... 还是 ...nums
都不是。仓颉没有变参专用符号:最后一个非命名参数声明为 Array<T>,调用时就可以传任意多个 T(如 func sum(nums: Array<Int64>),调用 sum(1,2,3)sum([1,2,3]) 均可)。... 两种写法在 1.1.3 均编译失败。

Q4:函数怎么返回多个值?
返回元组:声明 func f(): (Int64, String)return (1, "x")。接收方可写 let r = f() 后用 r[0]r[1] 访问,也可 let (a, b) = f() 直接解构;不需要的分量用 _ 忽略。

Q5:Unit 是什么?什么时候必须写返回类型?
Unit 表示"没有有意义的返回值",用于只产生副作用的函数(打印、修改状态)。返回类型可整体省略(省略即视为 Unit),也可显式写 : Unit;有实际值返回的函数建议显式声明返回类型并在所有出口给出该类型的值。另外 main 可写 main(): Int64(返回退出码)也可写 main()

Q6:函数重载怎么区分?为什么两个函数只差返回类型就报 overload conflicts?
重载只看参数的个数与类型,返回类型不参与签名。调用 f(1) 时编译器只能凭实参选版本,返回值还可能被丢弃,所以仅返回类型不同会造成歧义,报 function 'f' has overload conflicts

Q7:想重复一个字符串,为什么 "=".repeat(20) 报错?
SDK 1.1.3 的 String 没有 repeat 方法(实测编译器提示无此成员)。用循环自行拼接即可(见 10.1 的 mulLine);较新版本标准库可能已补充,以你本地版本的 API 提示为准。

Q8:把函数当参数传递时,需要取地址符吗?Lambda 又是什么?
不需要,直接写函数名:applyTwice(3, square)。函数的类型是 (参数类型) -> 返回类型{x => x + 1} 这种没有名字的匿名函数叫 Lambda,适合只在一处用的"一次性动作",系统讲解在下一课。


十二、课后练习

  1. func makeGreeting(name: String, lang!: String = "zh", excited!: Bool = false): Stringlang"zh" 时返回 "你好,X""en" 时返回 "Hello, X"excitedtrue 时末尾加 "!"/"!"。要求:① 用默认参数调用一次;② 只传 excited: true 跳过 lang;③ 命名实参按与声明相反的顺序调用一次。
  2. 用可变参数(Array 形参)实现 func maxOf(nums: Array<Int64>): Int64,找出任意多个整数中的最大值(至少处理一个元素)。分别用 maxOf(3, 9, 2, 7)maxOf([3, 9, 2, 7]) 两种方式调用,确认结果都是 9。
  3. func minMax(nums: Array<Int64>): (Int64, Int64) 同时返回最小值与最大值。在 main 中解构接收并打印;再用 let (mn, _) = ... 只取最小值打印一次。
  4. 定义两个 func describe(x: Int64): Stringfunc describe(x: String): String 的重载(分别返回 "整数: X" / "文本: X"),各调用一次;然后故意把两者的参数列表改成一致、仅保留返回类型不同,编译并记录 overload conflicts 报错,再改回正确版本。
  5. 用递归实现斐波那契 func fib(n: Int64): Int64fib(0)=0fib(1)=1),在 main 中循环打印 fib(0)fib(10)Shift+F9 调试,在递归行断点观察 Call Stack 的入栈与出栈过程。

下节预告

这节课最后一节,我们把函数当成值传来传去,并用 {x => x + 1} 这种没有名字的小函数尝了个鲜。第 12 课就正式学习 Lambda 与高阶函数:Lambda 的完整语法、类型推断、尾随 Lambda 写法,以及 mapfilterforEachfold 等把循环"说"出来的函数式工具。我们下节课见!


系列说明:本系列基于 Windows 平台 + CIDE + 仓颉 SDK(1.1.3)编写,所有代码均已实际编译运行通过。如遇 SDK 版本差异导致的细节出入(例如新版本为普通参数开放默认值、String 增加 repeat 等),以你本地版本为准,欢迎评论区交流。


💬 遇到问题?扫码联系作者

跟着课程练习时,如果在 SDK 安装、环境变量配置、编译报错或调试上卡住,欢迎扫码加作者企业微信直接咨询(请备注"仓颉课程"):
在这里插入图片描述

离线环境下图片可能加载不出来,也可以在 CIDE 菜单 Help ▸ 联系作者 / Contact 中查看同一张二维码(应用内置兜底图,无需联网)。


📥 工具下载

本系列全程使用的仓颉 IDE —— CIDE(免费开源、社区版):

  • GitCode 仓库 / 安装包下载https://gitcode.com/wp_upala/cide
  • 打开页面后进入 发行版(Releases),两种包任选其一:
    • 安装版:下载 CIDE-<版本>-x64-Setup.exe,双击安装,适合日常长期使用;
    • 免安装版(Portable):下载 CIDE-<版本>-x64-Portable.zip解压到任意目录即用,不写注册表、不留安装痕迹,拷到 U 盘也能在别的电脑直接运行(包内附《使用说明.txt》)。适合先试用、或在受限电脑上学习本系列课程。
  • 仓颉 SDK 请前往仓颉编程语言官网下载:https://cangjie-lang.cn
Logo

一站式 AI 云服务平台

更多推荐