华为仓颉语言的函数初步

华为仓颉语言的函数初步

函数是一段完成特定任务的独立代码片段,可以通过函数名字来标识,这个名字可以被用来调用函数。

要特别注意,与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)

函数类型既可以根据函数定义隐式存在,也可以由程序员在代码中显式地书写出来。

  1. 隐式的函数类型 (由函数定义产生)

例子:

// 【函数定义】

// 程序员写的是具体的实现

func add(a: Int64, b: Int64): Int64 {

return a + b

}

// 【隐式的函数类型】

// 编译器会自动识别出这个函数有一个类型:(Int64, Int64) -> Int64

// 这个类型是"依据函数定义存在的",程序员没有显式写出 `(Int64, Int64) -> Int64` 这几个字。

  1. 显式的函数类型 (由程序员主动书写)

你完全可以先写出类型,再去找或定义一个符合该类型的函数(甚至用变量、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

函数类型的核心用途

  1. 声明函数类型的变量

在仓颉中,声明变量必须显式或通过初始化值来表明类型。

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
}
  1. 作为函数的参数(高阶函数)
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)
    }
}
  1. 作为函数的返回值
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)   // 同样正确
}
相关推荐
猫林老师2 天前
HarmonyOS数据持久化:Preferences轻量级存储实战
华为·harmonyos
Devil枫2 天前
鸿蒙深链落地实战:从安全解析到异常兜底的全链路设计
安全·华为·harmonyos
广州腾科助你拿下华为认证2 天前
华为考试:HCIE数通考试难度分析
大数据·华为
与天仙漫步星海2 天前
华为基本命令
华为
低调小一2 天前
Android传统开发 vs Android Compose vs HarmonyOS ArkUI 对照表
android·华为·harmonyos
猛码Memmat3 天前
华为HarmonyOS开发文档
华为·harmonyos
流影ng3 天前
【HarmonyOS】MVVM与三层架构
华为·架构·harmonyos
爱笑的眼睛113 天前
HarmonyOS Stage 模型与 ArkUI 声明式开发深度实践:构建高效稳定的应用
华为·harmonyos
安卓开发者3 天前
鸿蒙Next ArkWeb网页文件上传与下载完全指南
华为·harmonyos