定义函数
- 仓颉使用关键字 func 来表示函数定义的开始,func 之后依次是函数名、参数列表、可选的函数返回值类型、函数体。
func add(a: Int64, b: Int64): Int64 {
return a + b
}
- 只能为命名参数设置默认值,不能为非命名参数设置默认值。
func add(a!: Int64 = 1, b!: Int64 = 1): Int64 {
return a + b
}
- 函数参数均为不可变变量,在函数定义内不能对其赋值。
func add(a: Int64, b: Int64): Int64 {
a = a + b // Error
return a
}
调用函数
- 函数调用的形式为 f(arg1, arg2, ..., argn)。其中,f 是要调用的函数的名字,arg1 到 argn 是 n 个调用时的参数(称为实参),要求每个实参的类型必须是对应参数类型的子类型。实参可以有 0 个或多个,当实参个数为 0 时,调用方式为 f()。
函数类型
仓颉编程语言中,函数是一等公民(first-class citizens),可以作为函数的参数或返回值,也可以赋值给变量。因此函数本身也有类型,称之为函数类型。
函数类型由函数的参数类型和返回类型组成,参数类型和返回类型之间使用 -> 连接。参数类型使用圆括号 () 括起来,可以有 0 个或多个参数,如果参数超过一个,参数类型之间使用逗号(,)分隔。
func hello(): Unit {
println("Hello!")
}
// 函数名为 hello,其类型是 () -> Unit,表示该函数没有参数,返回类型为 Unit。
- 可以为函数类型标记显式的类型参数名,下面例子中的 name 和 price 就是 类型参数名。
main() {
let fruitPriceHandler: (name: String, price: Int64) -> Unit
fruitPriceHandler = {n, p => println("fruit: ${n} price: ${p} yuan")}
fruitPriceHandler("banana", 10)
}
- 对于一个函数类型,只允许统一写类型参数名,或者统一不写类型参数名,不能交替存在。
let handler: (name: String, Int64) -> Int64 // Error
- 函数类型作为参数类型的例子
func printAdd(add: (Int64, Int64) -> Int64, a: Int64, b: Int64): Unit {
println(add(a, b))
}
// 函数名为 printAdd,其类型是 ((Int64, Int64) -> Int64, Int64, Int64) -> Unit,表示该函数有三个参数,参数类型分别为函数类型 (Int64, Int64) -> Int64 和两个 Int64,返回类型为 Unit。
- 函数类型作为返回类型的例子
func add(a: Int64, b: Int64): Int64 {
a + b
}
func returnAdd(): (Int64, Int64) -> Int64 {
add
}
main() {
var a = returnAdd()
println(a(1,2))
}
// 函数名为 returnAdd,其类型是 () -> (Int64, Int64) -> Int64,表示该函数无参数,返回类型为函数类型 (Int64, Int64) -> Int64。注意,-> 是右结合的。
- 函数类型作为变量类型的例子
func add(p1: Int64, p2: Int64): Int64 {
p1 + p2
}
let f: (Int64, Int64) -> Int64 = add
// 函数名是 add,其类型为 (Int64, Int64) -> Int64。变量 f 的类型与 add 类型相同,add 被用来初始化 f
嵌套函数
定义在源文件顶层的函数被称为全局函数。定义在函数体内的函数被称为嵌套函数。
例子:函数 foo 内定义了一个嵌套函数 nestAdd,可以在 foo 内调用该嵌套函数 nestAdd,也可以将嵌套函数 nestAdd 作为返回值返回,在 foo 外对其进行调用
func foo() {
func nestAdd(a: Int64, b: Int64) {
a + b + 3
}
println(nestAdd(1, 2)) // 6
return nestAdd
}
main() {
let f = foo()
let x = f(1, 2)
println("result: ${x}")
}
/* 程序会输出:
6
result: 6
*/
Lambda表达式
Lambda 表达式的语法为如下形式: { p1: T1, ..., pn: Tn => expressions | declarations }。
Lambda 表达式不管有没有参数,都不可以省略 =>,除非其作为尾随 lambda。
var display = { => println("Hello") }
func f2(lam: () -> Unit) { }
let f2Res = f2{ println("World") } // OK to omit the =>
Lambda 表达式中不支持声明返回类型,其返回类型总是从上下文中推断出来,若无法推断则报错。
Lambda 表达式支持立即调用
let r1 = { a: Int64, b: Int64 => a + b }(1, 2) // r1 = 3
let r2 = { => 123 }() // r2 = 123
- Lambda 表达式也可以赋值给一个变量,使用变量名进行调用
func f() {
var g = { x: Int64 => println("x = ${x}") }
g(2)
}
闭包
一个函数或 lambda 从定义它的静态作用域中捕获了变量,函数或 lambda 和捕获的变量一起被称为一个闭包,这样即使脱离了闭包定义所在的作用域,闭包也能正常运行。
函数或 lambda 的定义中对于以下几种变量的访问,称为变量捕获:
- 函数的参数缺省值中访问了本函数之外定义的局部变量;
- 函数或 lambda 内访问了本函数或本 lambda 之外定义的局部变量;
- class/struct 内定义的不是成员函数的函数或 lambda 访问了实例成员变量或 this。
- 示例 1:闭包 add,捕获了 let 声明的局部变量 num,之后通过返回值返回到 num 定义的作用域之外,调用 add 时仍可正常访问 num。
func returnAddNum(): (Int64) -> Int64 {
let num: Int64 = 10
func add(a: Int64) {
return a + num
}
add
}
main() {
let f = returnAddNum()
println(f(10))
}
/* 程序会输出:
20
*/
函数调用语法糖
尾随Lambda
- 当函数最后一个形参是函数类型,并且函数调用对应的实参是 lambda 时,我们可以使用尾随 lambda 语法,将 lambda 放在函数调用的尾部,圆括号外面。
func myIf(a: Bool, fn: () -> Int64) {
if(a) {
fn()
} else {
0
}
}
func test() {
myIf(true, { => 100 }) // General function call
myIf(true) { // Trailing closure call
100
}
}
- 当函数调用有且只有一个 lambda 实参时,我们还可以省略 (),只写 lambda。
func f(fn: (Int64) -> Int64) { fn(1) }
func test() {
f { i => i * i }
}
Flow表达式
流操作符包括两种:表示数据流向的中缀操作符 |> (称为 pipeline)和表示函数组合的中缀操作符 ~> (称为 composition)。
当需要对输入数据做一系列的处理时,可以使用 pipeline 表达式来简化描述。pipeline 表达式的语法形式如下:e1 |> e2。等价于如下形式的语法糖:let v = e1; e2(v) 。
func inc(x: Array<Int64>): Array<Int64> { // Increasing the value of each element in the array by '1'
let s = x.size
var i = 0
for (e in x where i < s) {
x[i] = e + 1
i++
}
x
}
func sum(y: Array<Int64>): Int64 { // Get the sum of elements in the array.
var s = 0
for (j in y) {
s += j
}
s
}
let arr: Array<Int64> = Array<Int64>([1, 3, 5])
let res = arr |> inc |> sum // res = 12
- composition 表达式表示两个单参函数的组合。composition 表达式语法如下: f ~> g。等价于如下形式: { x => g(f(x)) }。
func f(x: Int64): Float64 {
Float64(x)
}
func g(x: Float64): Float64 {
x
}
var fg = f ~> g // The same as { x: Int64 => g(f(x)) }
变长参数
- 变长参数是一种特殊的函数调用语法糖。当形参最后一个非命名参数是 Array 类型时,实参中对应位置可以直接传入参数序列代替 Array 字面量(参数个数可以是 0 个或多个)。
func sum(arr: Array<Int64>) {
var total = 0
for (x in arr) {
total += x
}
return total
}
main() {
println(sum())
println(sum(1, 2, 3))
}
/* 程序会输出:
0
6
*/
函数重载
在仓颉编程语言中,如果一个作用域中,一个函数名对应多个函数定义,这种现象称为函数重载。
- 函数名相同,函数参数不同(是指参数个数不同,或者参数个数相同但参数类型不同)的两个函数构成重载。
// Scenario 1
func f(a: Int64): Unit {
}
func f(a: Float64): Unit {
}
func f(a: Int64, b: Float64): Unit {
}
- 同一个类内的两个构造函数参数不同,构成重载。
// Scenario 2
class C {
var a: Int64
var b: Float64
public init(a: Int64, b: Float64) {
this.a = a
this.b = b
}
public init(a: Int64) {
b = 0.0
this.a = a
}
}
- 同一个类内的主构造函数和 init 构造函数参数不同,构成重载(认为主构造函数和 init 函数具有相同的名字)。
// Scenario 3
class C {
C(var a!: Int64, var b!: Float64) {
this.a = a
this.b = b
}
public init(a: Int64) {
b = 0.0
this.a = a
}
}
- 两个函数定义在不同的作用域,在两个函数可见的作用域中构成重载。
// Scenario 4
func f(a: Int64): Unit {
}
func g() {
func f(a: Float64): Unit {
}
}
- 两个函数分别定义在父类和子类中,在两个函数可见的作用域中构成重载。
// Scenario 5
open class Base {
public func f(a: Int64): Unit {
}
}
class Sub <: Base {
public func f(a: Float64): Unit {
}
}
class、interface、struct 类型的静态成员函数和实例成员函数之间不能重载
enum 类型的 constructor、静态成员函数和实例成员函数之间不能重载
函数重载决议
- 优先选择作用域级别高的作用域内的函数。在嵌套的表达式或函数中,越是内层作用域级别越高。
open class Base {}
class Sub <: Base {}
func outer() {
func g(a: Sub) {
print("1")
}
func inner() {
func g(a: Base) {
print("2")
}
g(Sub()) // Output: 2
}
}
/*
inner 函数体内调用 g(Sub()) 时,候选集包括 inner 函数内定义的函数 g 和 inner 函数外定义的函数 g,函数决议选择作用域级别更高的 inner 函数内定义的函数 g。
*/
- 如果作用域级别相对最高的仍有多个函数,则需要选择最匹配的函数(对于函数 f 和 g 以及给定的实参,如果 f 可以被调用时 g 也总是可以被调用的,但反之不然,则我们称 f 比 g 更匹配)。
open class Base {}
class Sub <: Base {}
func outer() {
func g(a: Sub) {
print("1")
}
func g(a: Base) {
print("2")
}
g(Sub()) // Output: 1
}
- 子类和父类认为是同一作用域。
如下示例中,一个函数 g 定义在父类中,另一个函数 g 定义在子类中,在调用 s.g(Sub()) 时,两个函数 g 当成同一作用域级别决议,则选择更匹配的父类中定义的函数 g(a: Sub): Unit。
open class Base {
public func g(a: Sub) { print("1") }
}
class Sub <: Base {
public func g(a: Base) {
print("2")
}
}
func outer() {
let s: Sub = Sub()
s.g(Sub()) // Output: 1
}
操作符重载
如果希望在某个类型上支持此类型默认不支持的操作符,可以使用操作符重载实现。
如果需要在某个类型上重载某个操作符,可以通过为类型定义一个函数名为此操作符的函数的方式实现,这样,在该类型的实例使用该操作符时,就会自动调用此操作符函数。
定义操作符函数时需要在 func 关键字前面添加 operator 修饰符
操作符函数的参数个数需要匹配对应操作符的要求
操作符函数只能定义在 class、interface、struct、enum 和 extend 中
操作符函数具有实例成员函数的语义,所以禁止使用 static 修饰符
操作符函数不能为泛型函数
open class Point {
var x: Int64 = 0
var y: Int64 = 0
public init (a: Int64, b: Int64) {
x = a
y = b
}
public operator func -(): Point {
Point(-x, -y)
}
public operator func +(right: Point): Point {
Point(this.x + right.x, this.y + right.y)
}
}
main() {
let p1 = Point(8, 24)
let p2 = -p1 // p2 = Point(-8, -24)
let p3 = p1 + p2 // p3 = Point(0, 0)
}
const函数和常量求值
const 变量是一种特殊的变量,它以关键字 const 修饰,定义在编译时完成求值,并且在运行时不可改变的变量。
const 变量可以省略类型标注,但是不可省略初始化表达式。
const 变量可以是全局变量,局部变量,静态成员变量。但是 const 变量不能在扩展中定义。
const 变量可以访问对应类型的所有实例成员,也可以调用对应类型的所有非 mut 实例成员函数。
const G = 6.674e-11 // 万有引力常数
/// 记录行星的质量和半径,同时定义了一个 const 成员函数 gravity 用来计算该行星对距离为 r 质量为 m 的物体的万有引力
struct Planet {
const Planet(let mass: Float64, let radius: Float64) {}
const func gravity(m: Float64, r: Float64) {
G * mass * m / r**2
}
}
main() {
const myMass = 71.0
const earth = Planet(5.972e24, 6.378e6)
println(earth.gravity(myMass, earth.radius))
}
/* 编译执行得到地球对地面上一个质量为 71 kg 的成年人的万有引力(程序输出):
695.657257
*/
- const 变量初始化后该类型实例的所有成员都是 const 的(深度 const,包含成员的成员),因此不能被用于左值。
main() {
const myMass = 71.0
myMass = 70.0 // Error, cannot assign to immutable value
}
如果一个 struct 或 class 定义了 const 构造器,那么这个 struct/class 实例可以用在 const 表达式中。
如果当前类型是 class,则不能具有 var 声明的实例成员变量,否则不允许定义 const init 。如果当前类型具有父类,当前的 const init 必须调用父类的 const init(可以显式调用或者隐式调用无参const init),如果父类没有 const init 则报错。
当前类型的实例成员变量如果有初始值,初始值必须要是 const 表达式,否则不允许定义 const init。
对于 struct 和 class,只有定义了 const init 才能定义 const 实例成员函数。class 中的 const 实例成员函数不能是 open 的。struct 中的 const 实例成员函数不能是 mut 的。
接口中的 const 函数,实现类型必须也用 const 函数才算实现接口。
interface I {
const func f(): Int64
const static func f2(): Int64
}
class A <: I {
public const func f() { 0 }
public const static func f2() { 1 }
const init() {}
}
const func g<T>(i: T) where T <: I {
return i.f() + T.f2()
}
main() {
println(g(A()))
}
/* 编译执行上述代码,输出结果为:
1
*/
一直没有搞明白,既然已经有了let,为什么还要const?并且const还有这么多限制条件,显然非常不好用。