仓颉编程(5)函数的定义、调用、执行
函数的定义、调用、执行
一、函数定义规则详解
1.1 函数定义的基本语法
在仓颉编程语言中,函数定义使用func关键字,基本语法格式为:
func 函数名(参数列表): 返回值类型 {
函数体
}
各部分说明:
- func:函数定义的关键字,必须放在最前面
- 函数名:合法的标识符,可以是任意有效的变量名
- 参数列表:定义在圆括号内,多个参数用逗号分隔
- 返回值类型:可选,定义函数返回的类型,冒号后声明
- 函数体:定义在花括号内,包含函数执行的具体逻辑
示例:
func add(a: Int64, b: Int64): Int64 {
return a + b
}
这个例子定义了一个名为add的函数,接收两个Int64类型的参数a和b,返回类型为Int64,函数体将两个参数相加并返回结果。
1.2 无参函数的定义
无参函数是指不接收任何参数的函数,其参数列表为空。在仓颉中定义无参函数的格式为:
func 函数名(): 返回值类型 {
函数体
}
示例 1:无参无返回值函数
func sayHello(): Unit {
print("Hello, World!")
print("Welcome to Cangjie Programming!")
}
示例 2:无参有返回值函数
func getPi(): Float64 {
return 3.1415926535
}
特殊说明:
- 当函数没有返回值时,返回类型应声明为Unit
- 函数体的最后一个表达式会被视为返回值,因此也可以省略return语句
- 例如:
func add(a: Int64, b: Int64): Int64 {
a + b // 直接返回表达式结果
}
1.3 有参函数的定义
有参函数可以接收一个或多个参数,参数分为非命名参数和命名参数两种类型。
1.3.1 非命名参数(位置参数)
非命名参数的定义方式为:参数名: 类型,调用时按位置传递参数。
定义示例:
func multiply(a: Int64, b: Int64): Int64 {
return a * b
}
调用示例:
let result = multiply(3, 5) // 3和5按位置传递给a和b
print(result) // 输出:15
1.3.2 命名参数
命名参数在定义时需要在参数名后加!,调用时必须使用参数名: 值的形式。
定义示例:
func divide(dividend!: Int64, divisor!: Int64): Float64 {
return dividend.toFloat() / divisor.toFloat()
}
调用示例:
let result = divide(dividend: 10, divisor: 3) // 必须指定参数名
print(result) // 输出:3.333333...
命名参数的优势:
- 提高代码可读性,明确参数含义
- 调用时可以改变参数顺序
- 例如:
let result = divide(divisor: 3, dividend: 10) // 参数顺序可以调换
1.3.3 混合参数列表
在一个函数中可以同时包含非命名参数和命名参数,但必须遵循规则:非命名参数必须放在命名参数之前。
正确示例:
func calculate(a: Int64, b: Int64, operation!: String): Int64 {
if operation == "add" {
return a + b
} else if operation == "subtract" {
return a - b
}
return 0
}
调用示例:
let result = calculate(5, 3, operation: "add") // 前两个是位置参数,第三个是命名参数
print(result) // 输出:8
1.4 默认参数的定义
仓颉语言支持为命名参数设置默认值,语法为:参数名!: 类型 = 默认值。
定义示例:
func greet(name!: String = "Guest", age!: Int64 = 18): Unit {
print(f"Hello, {name}! You are {age} years old.")
}
调用示例:
greet() // 使用默认参数:Hello, Guest! You are 18 years old.
greet(name: "Alice") // 只指定name:Hello, Alice! You are 18 years old.
greet(age: 25) // 只指定age:Hello, Guest! You are 25 years old.
greet("Bob", 30) // 按顺序传递:Hello, Bob! You are 30 years old.
greet(name: "Charlie", age: 35) // 明确指定参数名
注意事项:
- 只能为命名参数设置默认值,不能为非命名参数设置
- 函数重载时,默认参数不会产生新的重载
- 例如:
func add(a: Int64, b: Int64 = 10): Int64 { // 错误,b是非命名参数
return a + b
}
1.5 可变参数的定义
当函数需要接收不定数量的参数时,可以使用可变参数。在仓颉中,可变参数必须是函数的最后一个参数,且只能有一个。
定义语法:
func 函数名(参数名: 类型...): 返回类型 {
函数体
}
示例 1:计算任意个数的和
func sum(numbers: Int63...): Int64 {
var total = 0
for number in numbers {
total += number
}
return total
}
调用示例:
let result1 = sum(1, 2, 3, 4, 5) // 传递5个参数
print(result1) // 输出:15
let result2 = sum(10, 20, 30) // 传递3个参数
print(result2) // 输出:60
let numbers = [1, 2, 3, 4, 5]
let result3 = sum(numbers...) // 传递数组,使用...展开
print(result3) // 输出:15
示例 2:格式化日志信息
func log(message: String, args: Any...): Unit {
let formattedArgs = args.map { it.toString() }.join(", ")
print(f"[Log] {message}: {formattedArgs}")
}
调用示例:
log("User login", "Alice", 12345, true) // 输出:[Log] User login: Alice, 12345, true
1.6 函数重载
函数重载是指在同一作用域内定义多个同名函数,但参数列表必须不同(参数个数或类型不同)。
重载示例:
func abs(x: Int64): Int64 {
return x < 0 ? -x : x
}
func abs(x: Int32): Int32 {
return x < 0 ? -x : x
}
func abs(x: Float64): Float64 {
return x < 0 ? -x : x
}
调用示例:
let a = abs(-10) // 调用abs(Int64)
let b = abs(-3.14) // 调用abs(Float64)
let c = abs(-123) // 调用abs(Int64)
重载决议规则:
- 编译器根据参数的个数和类型自动选择匹配的函数
- 优先选择作用域级别高的函数
- 对于泛型函数,需满足非泛型部分参数不同才能构成重载
1.7 函数返回值
1.7.1 单一返回值
函数可以返回一个值,使用return语句,语法为:return 表达式。
示例:
func calculateArea(radius: Float64): Float64 {
return 3.14159 * radius * radius
}
1.7.2 多返回值(元组)
仓颉支持函数返回多个值,通过元组(Tuple)实现。
定义示例:
func minMax(array: Array<Int64>): (min: Int64, max: Int64) {
var min = array[0]
var max = array[0]
for value in array {
if value < min {
min = value
}
if value > max {
max = value
}
}
return (min, max) // 返回元组
}
调用示例:
let numbers = [1, 3, 5, 2, 4]
let (min, max) = minMax(numbers) // 解构元组
print(f"Min: {min}, Max: {max}") // 输出:Min: 1, Max: 5
// 也可以只获取部分值
let (_, maxOnly) = minMax(numbers) // 使用_忽略第一个值
print(f"Max only: {maxOnly}") // 输出:Max only: 5
1.7.3 无返回值(Unit 类型)
当函数不需要返回值时,应声明返回类型为Unit。在这种情况下,return语句可以省略或使用return。
示例:
func printInfo(name: String, age: Int64): Unit {
print(f"Name: {name}")
print(f"Age: {age}")
// 可以省略return语句
}
1.8 函数参数的不可变性
在仓颉语言中,所有函数参数都是不可变的(immutable)。这意味着:
- 不能在函数体内对参数进行赋值
- 如果参数是值类型,也不能修改其成员
错误示例:
func increment(x: Int64): Int64 {
x = x + 1 // 错误:无法对参数x赋值
return x
}
func modifyPoint(point: Point): Unit {
point.x = 10 // 错误:无法修改值类型的成员
}
正确做法:
func increment(x: Int64): Int64 {
return x + 1 // 直接返回计算结果
}
func modifyPoint(point: Point): Point {
return Point(x: 10, y: point.y) // 返回新的Point对象
}
1.9 特殊函数:main 函数
在仓颉程序中,main函数是程序的入口,具有特殊地位:
定义要求:
- 必须定义在顶层作用域
- 可以没有参数,也可以有Array<String>类型的参数
- 返回值类型可以是整数类型或Unit类型
示例 1:无参数 main 函数
func main(): Int64 {
print("Program started.")
let result = add(3, 5)
print(f"3 + 5 = {result}")
print("Program ended.")
return 0
}
示例 2:带参数 main 函数
func main(args: Array<String>): Unit {
print("Arguments:")
for arg in args {
print(f"- {arg}")
}
}
二、函数调用技巧
2.1 基础调用方式
函数调用的基本语法为:函数名(参数列表)。根据函数参数类型的不同,调用方式分为以下几种:
2.1.1 非命名参数调用
调用非命名参数函数时,直接按顺序传递参数值。
示例:
func add(a: Int64, b: Int64): Int64 {
return a + b
}
let result = add(3, 5) // 3传递给a,5传递给b
print(result) // 输出:8
2.1.2 命名参数调用
调用命名参数函数时,必须使用参数名: 值的形式。
示例:
func greet(name!: String, age!: Int64): Unit {
print(f"Hello, {name}! You are {age} years old.")
}
greet(name: "Alice", age: 25) // 必须指定参数名
命名参数的优势:
- 参数顺序可以改变:
greet(age: 25, name: "Alice") // 顺序不影响结果
- 提高代码可读性,明确参数含义
2.1.3 混合调用
当函数同时包含非命名参数和命名参数时,可以混合使用两种调用方式。但需要注意:非命名参数必须按照定义顺序传递,命名参数可以任意顺序。
示例:
func calculate(a: Int64, b: Int64, operation!: String): Int64 {
if operation == "add" {
return a + b
} else if operation == "subtract" {
return a - b
}
return 0
}
// 混合调用示例
let result1 = calculate(5, 3, operation: "add") // 前两个是位置参数,第三个是命名参数
let result2 = calculate(10, b: 4, operation: "subtract") // 第一个是位置参数,后两个是命名参数
2.2 函数作为参数(高阶函数)
在仓颉语言中,函数是 "一等公民",可以像普通值一样作为参数传递给其他函数。
2.2.1 函数类型
函数类型的表示方式为:(参数类型1, 参数类型2, ...) -> 返回类型。
示例:
// 定义一个函数类型别名
type MathOperation = (Int64, Int64) -> Int64
// 或者直接使用函数类型
func applyOperation(f: (Int64, Int64) -> Int64, a: Int64, b: Int64): Int64 {
return f(a, b)
}
2.2.2 函数作为参数的应用
示例 1:通用计算函数
func calculate(a: Int64, b: Int64, operation: (Int64, Int64) -> Int64): Int64 {
return operation(a, b)
}
// 定义具体的运算函数
func add(a: Int64, b: Int64): Int64 { return a + b }
func subtract(a: Int64, b: Int64): Int64 { return a - b }
func multiply(a: Int64, b: Int64): Int64 { return a * b }
// 调用示例
let result1 = calculate(5, 3, add) // 加法运算
let result2 = calculate(10, 4, subtract) // 减法运算
let result3 = calculate(6, 2, multiply) // 乘法运算
示例 2:数组映射(map)函数
func map(array: Array<Int64>, transform: (Int64) -> Int64): Array<Int64> {
var result = Array<Int64>()
for element in array {
result.append(transform(element))
}
return result
}
// 使用示例
let numbers = [1, 2, 3, 4, 5]
// 计算平方
let squares = map(numbers, { x in x * x })
print(squares) // 输出:[1, 4, 9, 16, 25]
// 计算立方
let cubes = map(numbers, { x in x * x * x })
print(cubes) // 输出:[1, 8, 27, 64, 125]
// 翻倍
let doubled = map(numbers, { x in x * 2 })
print(doubled) // 输出:[2, 4, 6, 8, 10]
2.2.3 匿名函数(Lambda 表达式)
在传递函数参数时,可以直接使用匿名函数(Lambda 表达式),使代码更加简洁。
语法格式:
{ 参数列表 in 表达式 }
示例:
// 计算1到10的和
let sum = reduce(1..10, 0, { acc, x in acc + x })
print(sum) // 输出:55
// 找出数组中的偶数
let numbers = [1, 2, 3, 4, 5, 6]
let evens = filter(numbers, { x in x % 2 == 0 })
print(evens) // 输出:[2, 4, 6]
2.3 函数作为返回值
函数不仅可以作为参数,还可以作为返回值。
示例 1:创建计数器函数
func createCounter(initialValue: Int64): () -> Int64 {
var count = initialValue
return {
count += 1
return count
}
}
// 使用示例
let counter = createCounter(0) // 创建一个从0开始的计数器
print(counter()) // 输出:1
print(counter()) // 输出:2
print(counter()) // 输出:3
示例 2:根据条件返回不同的运算函数
func getOperation(operator: String): (Int64, Int64) -> Int64 {
if operator == "+" {
return { a, b in a + b }
} else if operator == "-" {
return { a, b in a - b }
} else if operator == "*" {
return { a, b in a * b }
} else if operator == "/" {
return { a, b in a / b }
}
return { a, b in 0 } // 默认返回0
}
// 使用示例
let addFunc = getOperation("+")
print(addFunc(5, 3)) // 输出:8
let multiplyFunc = getOperation("*")
print(multiplyFunc(4, 6)) // 输出:24
2.4 闭包的应用
闭包是指函数能够捕获并记住其定义时的外部变量,即使在脱离原作用域后仍能访问这些变量。
2.3.1 闭包的基本概念
示例:
func outer(): () -> Int64 {
let x = 10 // 外部变量
return { // 内部函数(闭包)
return x + 5 // 访问外部变量x
}
}
let closure = outer() // closure捕获了x=10
print(closure()) // 输出:15
在这个例子中,outer函数返回的匿名函数形成了一个闭包,它捕获并记住了x=10的值。
2.3.2 闭包的变量捕获规则
仓颉语言对闭包的变量捕获有以下重要规则:
- 捕获的变量必须在闭包定义时已经声明
- 捕获var声明的可变变量时,闭包只能被调用,不能作为一等公民使用(不能赋值给变量、不能作为实参或返回值等)
错误示例:
func createCounter(): () -> Int64 {
var count = 0
return { // 错误:捕获了var声明的可变变量
count += 1
return count
}
}
正确做法:
func createCounter(): (() -> Int64, () -> Unit) {
var count = 0
let increment = { // 闭包1:递增计数器
count += 1
return count
}
let reset = { // 闭包2:重置计数器
count = 0
}
return (increment, reset) // 返回两个函数
}
// 使用示例
let (increment, reset) = createCounter()
print(increment()) // 输出:1
print(increment()) // 输出:2
reset()
print(increment()) // 输出:1
2.3.3 闭包的实际应用
示例 1:缓存计算结果
func memoize(f: (Int64) -> Int64): (Int64) -> Int64 {
var cache = Dictionary<Int64, Int64>()
return { x in
if let result = cache[x] {
print(f"Using cached result for {x}")
return result
}
let result = f(x)
cache[x] = result
print(f"Calculated and cached result for {x}")
return result
}
}
// 定义一个耗时的计算函数
func expensiveCalculation(x: Int64): Int64 {
// 模拟耗时操作
sleep(1 * Duration.Second)
return x * x * x
}
// 创建记忆化版本
let memoizedCalculation = memoize(expensiveCalculation)
// 第一次调用,计算并缓存
print(memoizedCalculation(5)) // 输出:125
// 第二次调用,直接使用缓存
print(memoizedCalculation(5)) // 输出:125
示例 2:事件处理器
func createButtonClickListener(onClick: () -> Unit): () -> Unit {
var isClicked = false
return {
if !isClicked {
isClicked = true
onClick()
}
}
}
// 使用示例
let clickHandler = createButtonClickListener {
print("Button clicked!")
}
clickHandler() // 输出:Button clicked!
clickHandler() // 无输出(防止重复点击)
2.5 尾随 Lambda 语法
当函数的最后一个参数是函数类型时,可以将 Lambda 表达式移到函数调用括号外面,形成尾随 Lambda 语法。
普通调用方式:
filter(numbers, { x in x % 2 == 0 })
尾随 Lambda 调用方式:
filter(numbers) { x in x % 2 == 0 }
更简洁的形式:当函数只有一个参数且为函数类型时,可以省略括号:
runLater {
print("This will run later.")
}
示例应用:
// 定义一个条件执行函数
func unless(condition: Bool, action: () -> Unit) {
if !condition {
action()
}
}
// 使用尾随Lambda
unless(5 > 10) {
print("5 is not greater than 10")
}
2.6 管道操作符
仓颉引入了管道操作符|>,用于简化嵌套函数调用,更直观地表达数据流向。
语法格式:
值 |> 函数1 |> 函数2 |> ...
示例 1:计算流程
func double(x: Int64): Int64 { return x * 2 }
func increment(x: Int64): Int64 { return x + 1 }
// 传统方式
let result1 = double(increment(double(double(5)))) // 42
// 管道方式
let result2 = 5 |> double |> double |> increment |> double
print(result2) // 输出:42
示例 2:数据处理流程
let numbers = [1, 2, 3, 4, 5]
// 传统方式
let processed1 = map(filter(numbers, { x in x % 2 == 0 }), { x in x * x })
// 管道方式
let processed2 = numbers
|> filter({ x in x % 2 == 0 })
|> map({ x in x * x })
print(processed2) // 输出:[4, 16]
三、函数执行原理
3.1 函数调用栈机制
当函数被调用时,系统会在内存中创建一个栈帧(Stack Frame)来管理函数的执行。栈帧中包含以下信息:
- 返回地址:函数执行完毕后应返回的代码位置
- 调用者的栈帧基址:用于恢复调用者的栈环境
- 函数参数:传递给函数的参数值
- 局部变量:函数内部定义的变量
- 临时数据:表达式计算的中间结果
示例:函数调用过程
func main(): Int64 {
let a = 10
let b = 20
let c = add(a, b) // 调用add函数
print(c)
return 0
}
func add(x: Int64, y: Int64): Int64 {
return x + y
}
当main函数调用add函数时,栈的变化过程如下:
- main函数正在执行,栈中保存了a=10、b=20等局部变量
- 执行add(a, b)时,将a和b的值压入栈
- 将返回地址(add函数调用后的下一条指令)压入栈
- 跳转到add函数执行
- add函数创建自己的栈帧,读取参数x=10、y=20
- 执行加法运算,返回结果
- 恢复main函数的栈帧,从返回地址继续执行
3.2 作用域与变量生命周期
在仓颉语言中,变量的作用域由花括号{}定义。函数体、条件语句、循环语句等都会创建新的作用域。
3.2.1 作用域规则
- 全局作用域:定义在所有函数外部的变量
- 函数作用域:函数体内定义的变量
- 块作用域:if、for、while等代码块内定义的变量
示例:
let globalVar = 100 // 全局变量
func outer(): Unit {
let outerVar = 200 // outer函数作用域
if true {
let blockVar = 300 // 块作用域
print(globalVar) // 可以访问
print(outerVar) // 可以访问
print(blockVar) // 可以访问
}
// print(blockVar) // 错误:blockVar超出作用域
}
3.2.2 变量遮蔽
当内部作用域定义了与外部作用域同名的变量时,内部变量会遮蔽外部变量。
示例:
let x = 10 // 全局变量
func f(): Unit {
let x = 20 // 遮蔽全局变量x
print(x) // 输出:20
}
func g(): Unit {
print(x) // 输出:10(访问全局变量)
}
3.3 递归函数的执行过程
递归函数是指在函数内部调用自身的函数。
3.3.1 递归的基本要素
- 递归终止条件:避免无限递归
- 递归调用:调用自身,但参数逐渐接近终止条件
- 递归处理:将问题分解为更小的子问题
示例:计算阶乘
func factorial(n: Int64): Int64 {
if n == 0 { // 终止条件
return 1
} else {
return n * factorial(n - 1) // 递归调用
}
}
3.3.2 递归执行过程分析
以factorial(3)为例,执行过程如下:
- 调用factorial(3)
- 由于3 != 0,执行3 * factorial(2)
- 调用factorial(2)
- 由于2 != 0,执行2 * factorial(1)
- 调用factorial(1)
- 由于1 != 0,执行1 * factorial(0)
- 调用factorial(0)
- 由于0 == 0,返回 1
- 返回1 * 1 = 1(回到factorial(1))
- 返回2 * 1 = 2(回到factorial(2))
- 返回3 * 2 = 6(回到factorial(3))
3.3.3 尾递归优化
尾递归是指递归调用是函数的最后一条语句。现代编译器可以对尾递归进行优化,避免栈溢出。
示例:尾递归实现阶乘
func factorialTailRecursive(n: Int64, accumulator: Int64 = 1): Int64 {
if n == 0 {
return accumulator
} else {
return factorialTailRecursive(n - 1, n * accumulator) // 尾递归调用
}
}
调用示例:
let result = factorialTailRecursive(5) // 计算5! = 120
3.4 函数调用的性能开销
函数调用会带来以下开销:
- 栈操作开销:创建和销毁栈帧
- 参数传递开销:复制参数值
- 指令跳转开销:CPU 缓存失效
- 返回开销:恢复调用者环境
在竞赛中,了解这些开销有助于优化代码:
优化建议:
- 对于频繁调用的简单函数,可以考虑内联
- 使用值类型传递小型数据
- 避免在循环中进行复杂的函数调用
- 使用尾递归优化替代普通递归
3.5 内存管理与垃圾回收
仓颉语言使用自动垃圾回收机制,开发者不需要手动管理内存。但理解内存管理机制对优化程序很重要。
3.5.1 栈内存与堆内存
- 栈内存:
-
- 自动分配和释放
- 存储函数参数、局部变量
- 速度快,但容量有限
- 堆内存:
-
- 需要手动分配(通过new等操作)
- 存储动态创建的对象
- 容量大,但分配速度慢
3.5.2 引用计数与垃圾回收
仓颉使用引用计数机制管理堆内存:
- 每个对象都有一个引用计数
- 当引用计数为 0 时,对象被自动回收
- 循环引用会导致内存泄漏
示例:
class Node {
var next: Node?
}
func createCycle(): Unit {
let a = Node()
let b = Node()
a.next = b
b.next = a // 循环引用,导致内存泄漏
}
3.6 并发执行模型
仓颉语言主打 "轻松并发",支持协程和通道等并发机制。
3.6.1 协程(Coroutine)
协程是一种轻量级的线程,可以在函数内部暂停和恢复执行。
示例:
func asyncPrint(): Unit {
go { // 在新协程中执行
sleep(1 * Duration.Second)
print("Printed after 1 second")
}
}
func main(): Unit {
asyncPrint()
print("Main function continues")
sleep(2 * Duration.Second) // 等待协程完成
}
3.6.2 通道(Channel)
通道用于协程间的通信和同步。
示例:
func producer(channel: Channel<Int64>): Unit {
for i in 0..<5 {
channel.send(i)
sleep(1 * Duration.Second)
}
channel.close()
}
func consumer(channel: Channel<Int64>): Unit {
for item in channel {
print(f"Received: {item}")
}
}
func main(): Unit {
let channel = Channel<Int64>()
go producer(channel)
go consumer(channel)
sleep(6 * Duration.Second)
}
3.7 错误处理机制
在函数执行过程中,可能会发生各种错误。仓颉提供了多种错误处理方式:
3.7.1 错误码返回
简单的错误可以通过返回错误码或Result类型处理。
示例:
enum ErrorCode {
Success,
DivideByZero,
InvalidInput
}
func safeDivide(a: Int64, b: Int64, result: inout Int64): ErrorCode {
if b == 0 {
return .DivideByZero
}
result = a / b
return .Success
}
// 使用示例
var result = 0
let code = safeDivide(10, 3, &result)
if code == .Success {
print(result)
} else {
print("Error occurred")
}
3.7.2 异常处理
对于更复杂的错误,可以使用异常处理机制。
示例:
func divide(a: Int64, b: Int64): Int64 {
if b == 0 {
throw "Division by zero"
}
return a / b
}
func main(): Unit {
try {
let result = divide(10, 0)
print(result)
} catch let error {
print(f"Error: {error}")
}
}更多推荐


所有评论(0)