华为仓颉语言的函数初步
函数是一段完成特定任务的独立代码片段,可以通过函数名字来标识,这个名字可以被用来调用函数。
要特别注意,与C/C++、Python等语言不同,仓颉禁止参数重新赋值------函数参数均为不可变(immutable)变量,在函数定义内不能对其赋值。也就是说,仓颉函数参数与 let 声明的变量一样,只能读取,不能再次赋值。例如:
func demo(x: Int64): Int64 {
x = x + 1 // ❌ 编译错误
return x
}
如果需要修改,只能引入新的局部变量:
func demo(x: Int64): Int64 {
let y = x + 1 // ✅ 正确
return y
}
这说明,仓颉语言语言设计决策强化对不可变性和函数式编程范式的偏好。
函数定义
基本语法:仓颉语言使用func关键字来定义函数,后跟函数名、参数列表、可选的返回值类型以及函数体。其基本语法为:
func 函数名(参数列表): 返回值类型 {
// 函数体
return 返回值
}
参数列表:函数可以有 0 个或多个参数,参数分为非命名参数 和命名参数。非命名参数定义为p: T,如a: Int64;命名参数定义为p!: T,如a!: Int64,且只能为命名参数设置默认值,非命名参数不能设置默认值,参数列表中非命名参数只能定义在命名参数之前。
返回值类型:必须显式写出返回类型,除非返回Unit(可以省略)。
函数体定义在一对花括号内。函数体中定义了函数被调用时执行的操作,通常包含一系列的变量定义和表达式,也可以包含新的函数定义(即嵌套函数)。
函数定义示例
func add(a: Int64, b: Int64): Int64 {
return a + b
}
如果函数体是单个表达式,仓颉也支持隐式返回(省略 return 关键字):
func add(a: Int64, b: Int64): Int64 {
a + b // 最后一行表达式的值即为返回值
}
函数参数示例
非命名参数 (Non-named Parameters): 即普通参数,调用时按顺序传递。非命名参数必须定义在命名参数之前。例如:
rust
// 定义一个包含两个非命名参数的函数
func multiply(a: Int64, b: Int64): Int64 {
return a * b
}
main() {
// 调用函数(必须按顺序传递参数,无需指定参数名)
let result = multiply(3, 4) // 正确调用,返回12
println(result)
}
命名参数 (Named Parameters): 在参数名后加 !,调用时需指定参数名,并可提供默认值,提高了代码可读性和灵活性。例如:
// 定义包含命名参数的函数(带默认值)
func calculateTotal(price: Int64, quantity!: Int64 = 1, discount!: Float64 = 0.0): Int64 {
let total = Float64(price * quantity) // 先转成 Float64
let discounted = total * (1.0 - discount) // 浮点运算
return Int64(discounted) // 再转回整数
}
注意:
只能为命名参数设置默认值,不能为非命名参数设置默认值。
参数列表中可以同时定义非命名参数和命名参数,但是需要注意的是,非命名参数只能定义在命名参数之前,也就意味着命名参数之后不能再出现非命名参数。例如,下例中 add 函数的参数列表定义是不合法的:
func add(a!: Int64, b: Int64): Int64 { // 错误!
return a + b
}
调用函数规则
a.非命名参数调用
调用规则:必须按定义顺序传递,不能指定参数名。
b.命名参数调用
必须用 参数名: 值 形式传递,顺序可任意调整,且支持使用默认值。
c.混合参数调用(非命名 + 命名)
非命名参数必须先定义、先传递,命名参数在后,且命名参数必须指定参数名。
示例:
rust
// 定义包含命名参数的函数(带默认值)
func calculateTotal(price: Int64, quantity!: Int64 = 1, discount!: Float64 = 0.0): Int64 {
let total = Float64(price * quantity) // 先转成 Float64
let discounted = total * (1.0 - discount) // 浮点运算
return Int64(discounted) // 再转回整数
}
main() {
// 调用方式1:只传非命名参数(price),命名参数用默认值
let total1 = calculateTotal(100) // quantity默认1,discount默认0 → 结果100
println(total1)
// 调用方式2:指定部分命名参数(可调整顺序)
let total2 = calculateTotal(100, discount: 0.2, quantity: 2)
// 计算:100 * 2 * (1-0.2) = 160 → 结果160
println(total2)
// 调用方式3:显式指定所有参数名
let total3 = calculateTotal(200, quantity: 3, discount: 0.1)
// 计算:200 * 3 * 0.9 = 540 → 结果540
println(total3)
}
返回值类型的说明
在 仓颉语言(Cangjie) 的正式规范中:
必须显式写出返回类型,当返回类型为 Unit 时可省略 -> Unit。
// ✅ 合法
func add(a: Int64, b: Int64): Int64 { a + b }
// ❌ 不合法- 函数体中有返回值的表达式时必须声明返回类型
func add(a: Int64, b: Int64) { a + b }
如果函数 没有有意义的返回值(即返回 Unit),可以写成 : Unit,也可以 直接省略 返回类型部分。
// 等价写法
func log(msg: String) { println(msg) } // 省略 : Unit
func log(msg: String): Unit { println(msg) } // 显式 : Unit
log("你好") //调用
单值返回和多值返回(Tuple)
单值返回示例:
rust
// 返回单个 Int64
func square(n: Int64): Int64 {
return n * n
}
main(): Unit {
let s = square(7)
println("square = ${s}") //square = 49
}
多值返回(Tuple)示例:
rust
// 返回一个二元组 (Int64, Int64)
func divmod(a: Int64, b: Int64): (Int64, Int64) {
return (a / b, a % b)
}
main(): Unit {
let (q, r) = divmod(10, 3) // q = 3, r = 1
println("quotient = ${q}, remainder = ${r}") //quotient = 3, remainder = 1
}
函数类型(Function Type)
仓颉编程语言中,函数是一等公民(first-class citizens),可以作为函数的参数或返回值,也可以赋值给变量。因此函数本身也有类型,称之为函数类型。
函数类型由函数的参数类型和返回类型组成,参数类型和返回类型之间使用 -> 连接。参数类型使用圆括号 () 括起来,可以有 0 个或多个参数,如果参数超过一个,参数类型之间使用逗号(,)分隔。
函数类型是编程中一个比较抽象但极其强大的概念,是函数式编程范式(FP)的核心基石之一。
函数类型的语法非常直观,遵循以下格式:
(参数1类型, 参数2类型, ...) -> 返回值类型
解释一下:
• 括号 ():里面放置函数的参数类型列表。如果没有参数,就空着 ()。
• 箭头 ->:连接参数和返回值,读作"返回"。
• 返回值类型:在箭头后面,指定函数返回的数据类型。
例如:
没参数也要空括号:() -> Unit
多个参数逗号隔:(Int, String) -> Bool
返回元组括号包:(Int, Int) -> (Int, Int)
函数类型既可以根据函数定义隐式存在,也可以由程序员在代码中显式地书写出来。
- 隐式的函数类型 (由函数定义产生)
例子:
// 【函数定义】
// 程序员写的是具体的实现
func add(a: Int64, b: Int64): Int64 {
return a + b
}
// 【隐式的函数类型】
// 编译器会自动识别出这个函数有一个类型:(Int64, Int64) -> Int64
// 这个类型是"依据函数定义存在的",程序员没有显式写出 `(Int64, Int64) -> Int64` 这几个字。
- 显式的函数类型 (由程序员主动书写)
你完全可以先写出类型,再去找或定义一个符合该类型的函数(甚至用变量、lambda、函数值等)。
例子
// 1. 【显式地用于变量声明】
// 程序员主动写下了类型注解 `: (Int64, Int64) -> Int64`
let myMathOperator: (Int64, Int64) -> Int64 = add // 将函数`add`赋值给变量
// 2. 【显式地用于函数参数】
// 程序员定义了一个高阶函数,它接受一个函数作为参数
// 参数 `operation` 的类型被显式地定义为 `(Int64, Int64) -> Int64`
func calculate(operation: (Int64, Int64) -> Int64, x: Int64, y: Int64) -> Int64 {
return operation(x, y)
}
// 3. 【显式地用于解决重载歧义】
func add(i: Int64, j: Int64) -> Int64 { i + j }
func add(i: Float64, j: Float64) -> Float64 { i + j }
// 这里直接写 `add` 编译器不知道选哪个,产生歧义
// let f = add // Error!
// 程序员通过【显式地书写类型】来告诉编译器需要哪个函数
let f: (Int64, Int64) -> Int64 = add // OK
函数类型的核心用途
- 声明函数类型的变量
在仓颉中,声明变量必须显式或通过初始化值来表明类型。
rust
// 定义一个函数
func add(a: Int64, b: Int64): Int64 {
return a + b
}
main() {
// 正确声明1: 显式指定变量类型,再赋值函数
let operation: (Int64, Int64) -> Int64 // 声明一个函数类型的变量
operation = add // 将函数赋值给变量
// 正确声明2: 声明的同时初始化(类型由编译器推断)
let anotherOperation = add // 编译器能推断出anotherOperation的类型是 (Int64, Int64) -> Int64
//现在,operation或anotherOperation就代表了 add 函数
let result = operation(5, 3)
println(result) // 输出 8
let result2 = anotherOperation(5, 3)
println(result2) // 输出 8
}
- 作为函数的参数(高阶函数)
rust
// 导入标准库中的集合包
import std.collection.ArrayList // 使用ArrayList
// 高阶函数的参数类型使用函数类型 (Int64) -> String
// 输入和输出使用 ArrayList 类型
func processNumbers(numbers: ArrayList<Int64>, transform: (Int64) -> String): ArrayList<String> {
// 创建一个新的 ArrayList 来存放结果
let results = ArrayList<String>()
// 遍历输入的 ArrayList - 使用 for-in 循环
for(num in numbers) {
// 调用传入的 transform 函数处理每个元素
let transformedValue = transform(num)
// 将结果添加到新的集合中
results.add(transformedValue)
}
return results
}
// 处理行为的函数定义不变
func intToString(num: Int64): String {
return "Number: ${num}"
}
main() {
// 使用 ArrayList 而不是原生数组
let myNumbers = ArrayList<Int64>([1, 2, 3, 4, 5])
// 调用方式完全一样,传递函数名
let resultList = processNumbers(myNumbers, intToString)
// 遍历结果 ArrayList - 使用 for-in 循环
for (str in resultList) {
println(str)
}
// 同样可以使用 Lambda 表达式
let squaredList = processNumbers(myNumbers, { n => "Squared: ${n * n}" })
// 遍历 squaredList
for (str in squaredList) {
println(str)
}
}
- 作为函数的返回值
rust
// 这个函数返回一个 () -> String 类型的函数
func getGreeter(prefix: String): () -> String {
// 在内部定义一个函数,它捕获了参数 `prefix`
func greeter(): String {
return "${prefix}, Hello!"
}
return greeter // 返回这个内部函数
}
main() {
// getGreeter 返回的是一个函数
let casualGreet = getGreeter("Hi")
let formalGreet = getGreeter("Good morning")
// 调用返回的函数
println(casualGreet()) // 输出: Hi, Hello!
println(formalGreet()) // 输出: Good morning, Hello!
}
顺便提示,先把函数定义好,再传递函数名。也可可以用Lambda 表达式(匿名函数),下面示例对比:
rust
import std.collection.ArrayList // 使用ArrayList
// 高阶函数的参数类型使用函数类型 (Int64) -> String
// 输入和输出使用 ArrayList 类型
func processNumbers(numbers: ArrayList<Int64>, transform: (Int64) -> String): ArrayList<String> {
// 创建一个新的 ArrayList 来存放结果
let results = ArrayList<String>()
// 遍历输入的 ArrayList - 使用 for-in 循环
for(num in numbers) {
// 调用传入的 transform 函数处理每个元素
let transformedValue = transform(num)
// 将结果添加到新的集合中
results.add(transformedValue)
}
return results
}
main() {
// 使用 ArrayList 而不是原生数组
let myNumbers = ArrayList<Int64>([1, 2, 3, 4, 5])
// ---------1.具名函数(普通写法)------------
// 先写一个具名函数
func multiply10(num: Int64): String {
return "Value is: ${num * 10}"
}
// 把函数名当参数传进去
let result = processNumbers(myNumbers, multiply10)
// 遍历结果
for(str in result) {
println(str)
}
println("-----------")
// ---------2.Lambda 表达式(匿名写法)------------
// Lambda 表达式写法
let result2 = processNumbers(myNumbers, { num => "Value is: ${num * 10}" })
// 遍历结果
for(str in result2) {
println(str)
}
}
特别提示,可给函数类型的参数标记显式名称(仅用于标识,不影响类型匹配),且需统一写或统一不写,不能混合。
示例:
rust
// 首先定义 showFruitPrice 函数
func showFruitPrice(name: String, price: Int64): Unit {
println("Fruit: ${name}, Price: ${price}")
}
main() {
// 1. 全部写名字 ------ 合法
let handler1: (name: String, price: Int64) -> Unit = showFruitPrice
// 2. 全部不写名字 ------ 合法
let handler2: (String, Int64) -> Unit = showFruitPrice
// 3. 混写 ------ 非法,编译时报错
// let handler3: (name: String, Int64) -> Unit // Error: 必须统一写或统一不写参数名
// 调用函数
handler1("apple", 5) // 正确
handler2("apple", 5) // 同样正确
}