定義函數(shù)
- 倉頡使用關(guān)鍵字 func 來表示函數(shù)定義的開始,func 之后依次是函數(shù)名、參數(shù)列表、可選的函數(shù)返回值類型、函數(shù)體。
func add(a: Int64, b: Int64): Int64 {
return a + b
}
- 只能為命名參數(shù)設(shè)置默認(rèn)值,不能為非命名參數(shù)設(shè)置默認(rèn)值。
func add(a!: Int64 = 1, b!: Int64 = 1): Int64 {
return a + b
}
- 函數(shù)參數(shù)均為不可變變量,在函數(shù)定義內(nèi)不能對(duì)其賦值。
func add(a: Int64, b: Int64): Int64 {
a = a + b // Error
return a
}
調(diào)用函數(shù)
- 函數(shù)調(diào)用的形式為 f(arg1, arg2, ..., argn)。其中,f 是要調(diào)用的函數(shù)的名字,arg1 到 argn 是 n 個(gè)調(diào)用時(shí)的參數(shù)(稱為實(shí)參),要求每個(gè)實(shí)參的類型必須是對(duì)應(yīng)參數(shù)類型的子類型。實(shí)參可以有 0 個(gè)或多個(gè),當(dāng)實(shí)參個(gè)數(shù)為 0 時(shí),調(diào)用方式為 f()。
函數(shù)類型
倉頡編程語言中,函數(shù)是一等公民(first-class citizens),可以作為函數(shù)的參數(shù)或返回值,也可以賦值給變量。因此函數(shù)本身也有類型,稱之為函數(shù)類型。
函數(shù)類型由函數(shù)的參數(shù)類型和返回類型組成,參數(shù)類型和返回類型之間使用 -> 連接。參數(shù)類型使用圓括號(hào) () 括起來,可以有 0 個(gè)或多個(gè)參數(shù),如果參數(shù)超過一個(gè),參數(shù)類型之間使用逗號(hào)(,)分隔。
func hello(): Unit {
println("Hello!")
}
// 函數(shù)名為 hello,其類型是 () -> Unit,表示該函數(shù)沒有參數(shù),返回類型為 Unit。
- 可以為函數(shù)類型標(biāo)記顯式的類型參數(shù)名,下面例子中的 name 和 price 就是 類型參數(shù)名。
main() {
let fruitPriceHandler: (name: String, price: Int64) -> Unit
fruitPriceHandler = {n, p => println("fruit: ${n} price: ${p} yuan")}
fruitPriceHandler("banana", 10)
}
- 對(duì)于一個(gè)函數(shù)類型,只允許統(tǒng)一寫類型參數(shù)名,或者統(tǒng)一不寫類型參數(shù)名,不能交替存在。
let handler: (name: String, Int64) -> Int64 // Error
- 函數(shù)類型作為參數(shù)類型的例子
func printAdd(add: (Int64, Int64) -> Int64, a: Int64, b: Int64): Unit {
println(add(a, b))
}
// 函數(shù)名為 printAdd,其類型是 ((Int64, Int64) -> Int64, Int64, Int64) -> Unit,表示該函數(shù)有三個(gè)參數(shù),參數(shù)類型分別為函數(shù)類型 (Int64, Int64) -> Int64 和兩個(gè) Int64,返回類型為 Unit。
- 函數(shù)類型作為返回類型的例子
func add(a: Int64, b: Int64): Int64 {
a + b
}
func returnAdd(): (Int64, Int64) -> Int64 {
add
}
main() {
var a = returnAdd()
println(a(1,2))
}
// 函數(shù)名為 returnAdd,其類型是 () -> (Int64, Int64) -> Int64,表示該函數(shù)無參數(shù),返回類型為函數(shù)類型 (Int64, Int64) -> Int64。注意,-> 是右結(jié)合的。
- 函數(shù)類型作為變量類型的例子
func add(p1: Int64, p2: Int64): Int64 {
p1 + p2
}
let f: (Int64, Int64) -> Int64 = add
// 函數(shù)名是 add,其類型為 (Int64, Int64) -> Int64。變量 f 的類型與 add 類型相同,add 被用來初始化 f
嵌套函數(shù)
定義在源文件頂層的函數(shù)被稱為全局函數(shù)。定義在函數(shù)體內(nèi)的函數(shù)被稱為嵌套函數(shù)。
例子:函數(shù) foo 內(nèi)定義了一個(gè)嵌套函數(shù) nestAdd,可以在 foo 內(nèi)調(diào)用該嵌套函數(shù) nestAdd,也可以將嵌套函數(shù) nestAdd 作為返回值返回,在 foo 外對(duì)其進(jìn)行調(diào)用
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}")
}
/* 程序會(huì)輸出:
6
result: 6
*/
Lambda表達(dá)式
Lambda 表達(dá)式的語法為如下形式: { p1: T1, ..., pn: Tn => expressions | declarations }。
Lambda 表達(dá)式不管有沒有參數(shù),都不可以省略 =>,除非其作為尾隨 lambda。
var display = { => println("Hello") }
func f2(lam: () -> Unit) { }
let f2Res = f2{ println("World") } // OK to omit the =>
Lambda 表達(dá)式中不支持聲明返回類型,其返回類型總是從上下文中推斷出來,若無法推斷則報(bào)錯(cuò)。
Lambda 表達(dá)式支持立即調(diào)用
let r1 = { a: Int64, b: Int64 => a + b }(1, 2) // r1 = 3
let r2 = { => 123 }() // r2 = 123
- Lambda 表達(dá)式也可以賦值給一個(gè)變量,使用變量名進(jìn)行調(diào)用
func f() {
var g = { x: Int64 => println("x = ${x}") }
g(2)
}
閉包
一個(gè)函數(shù)或 lambda 從定義它的靜態(tài)作用域中捕獲了變量,函數(shù)或 lambda 和捕獲的變量一起被稱為一個(gè)閉包,這樣即使脫離了閉包定義所在的作用域,閉包也能正常運(yùn)行。
函數(shù)或 lambda 的定義中對(duì)于以下幾種變量的訪問,稱為變量捕獲:
- 函數(shù)的參數(shù)缺省值中訪問了本函數(shù)之外定義的局部變量;
- 函數(shù)或 lambda 內(nèi)訪問了本函數(shù)或本 lambda 之外定義的局部變量;
- class/struct 內(nèi)定義的不是成員函數(shù)的函數(shù)或 lambda 訪問了實(shí)例成員變量或 this。
- 示例 1:閉包 add,捕獲了 let 聲明的局部變量 num,之后通過返回值返回到 num 定義的作用域之外,調(diào)用 add 時(shí)仍可正常訪問 num。
func returnAddNum(): (Int64) -> Int64 {
let num: Int64 = 10
func add(a: Int64) {
return a + num
}
add
}
main() {
let f = returnAddNum()
println(f(10))
}
/* 程序會(huì)輸出:
20
*/
函數(shù)調(diào)用語法糖
尾隨Lambda
- 當(dāng)函數(shù)最后一個(gè)形參是函數(shù)類型,并且函數(shù)調(diào)用對(duì)應(yīng)的實(shí)參是 lambda 時(shí),我們可以使用尾隨 lambda 語法,將 lambda 放在函數(shù)調(diào)用的尾部,圓括號(hào)外面。
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
}
}
- 當(dāng)函數(shù)調(diào)用有且只有一個(gè) lambda 實(shí)參時(shí),我們還可以省略 (),只寫 lambda。
func f(fn: (Int64) -> Int64) { fn(1) }
func test() {
f { i => i * i }
}
Flow表達(dá)式
流操作符包括兩種:表示數(shù)據(jù)流向的中綴操作符 |> (稱為 pipeline)和表示函數(shù)組合的中綴操作符 ~> (稱為 composition)。
當(dāng)需要對(duì)輸入數(shù)據(jù)做一系列的處理時(shí),可以使用 pipeline 表達(dá)式來簡(jiǎn)化描述。pipeline 表達(dá)式的語法形式如下:e1 |> e2。等價(jià)于如下形式的語法糖: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 表達(dá)式表示兩個(gè)單參函數(shù)的組合。composition 表達(dá)式語法如下: f ~> g。等價(jià)于如下形式: { 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)) }
變長(zhǎng)參數(shù)
- 變長(zhǎng)參數(shù)是一種特殊的函數(shù)調(diào)用語法糖。當(dāng)形參最后一個(gè)非命名參數(shù)是 Array 類型時(shí),實(shí)參中對(duì)應(yīng)位置可以直接傳入?yún)?shù)序列代替 Array 字面量(參數(shù)個(gè)數(shù)可以是 0 個(gè)或多個(gè))。
func sum(arr: Array<Int64>) {
var total = 0
for (x in arr) {
total += x
}
return total
}
main() {
println(sum())
println(sum(1, 2, 3))
}
/* 程序會(huì)輸出:
0
6
*/
函數(shù)重載
在倉頡編程語言中,如果一個(gè)作用域中,一個(gè)函數(shù)名對(duì)應(yīng)多個(gè)函數(shù)定義,這種現(xiàn)象稱為函數(shù)重載。
- 函數(shù)名相同,函數(shù)參數(shù)不同(是指參數(shù)個(gè)數(shù)不同,或者參數(shù)個(gè)數(shù)相同但參數(shù)類型不同)的兩個(gè)函數(shù)構(gòu)成重載。
// Scenario 1
func f(a: Int64): Unit {
}
func f(a: Float64): Unit {
}
func f(a: Int64, b: Float64): Unit {
}
- 同一個(gè)類內(nèi)的兩個(gè)構(gòu)造函數(shù)參數(shù)不同,構(gòu)成重載。
// 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
}
}
- 同一個(gè)類內(nèi)的主構(gòu)造函數(shù)和 init 構(gòu)造函數(shù)參數(shù)不同,構(gòu)成重載(認(rèn)為主構(gòu)造函數(shù)和 init 函數(shù)具有相同的名字)。
// 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
}
}
- 兩個(gè)函數(shù)定義在不同的作用域,在兩個(gè)函數(shù)可見的作用域中構(gòu)成重載。
// Scenario 4
func f(a: Int64): Unit {
}
func g() {
func f(a: Float64): Unit {
}
}
- 兩個(gè)函數(shù)分別定義在父類和子類中,在兩個(gè)函數(shù)可見的作用域中構(gòu)成重載。
// Scenario 5
open class Base {
public func f(a: Int64): Unit {
}
}
class Sub <: Base {
public func f(a: Float64): Unit {
}
}
class、interface、struct 類型的靜態(tài)成員函數(shù)和實(shí)例成員函數(shù)之間不能重載
enum 類型的 constructor、靜態(tài)成員函數(shù)和實(shí)例成員函數(shù)之間不能重載
函數(shù)重載決議
- 優(yōu)先選擇作用域級(jí)別高的作用域內(nèi)的函數(shù)。在嵌套的表達(dá)式或函數(shù)中,越是內(nèi)層作用域級(jí)別越高。
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 函數(shù)體內(nèi)調(diào)用 g(Sub()) 時(shí),候選集包括 inner 函數(shù)內(nèi)定義的函數(shù) g 和 inner 函數(shù)外定義的函數(shù) g,函數(shù)決議選擇作用域級(jí)別更高的 inner 函數(shù)內(nèi)定義的函數(shù) g。
*/
- 如果作用域級(jí)別相對(duì)最高的仍有多個(gè)函數(shù),則需要選擇最匹配的函數(shù)(對(duì)于函數(shù) f 和 g 以及給定的實(shí)參,如果 f 可以被調(diào)用時(shí) g 也總是可以被調(diào)用的,但反之不然,則我們稱 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
}
- 子類和父類認(rèn)為是同一作用域。
如下示例中,一個(gè)函數(shù) g 定義在父類中,另一個(gè)函數(shù) g 定義在子類中,在調(diào)用 s.g(Sub()) 時(shí),兩個(gè)函數(shù) g 當(dāng)成同一作用域級(jí)別決議,則選擇更匹配的父類中定義的函數(shù) 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
}
操作符重載
如果希望在某個(gè)類型上支持此類型默認(rèn)不支持的操作符,可以使用操作符重載實(shí)現(xiàn)。
如果需要在某個(gè)類型上重載某個(gè)操作符,可以通過為類型定義一個(gè)函數(shù)名為此操作符的函數(shù)的方式實(shí)現(xiàn),這樣,在該類型的實(shí)例使用該操作符時(shí),就會(huì)自動(dòng)調(diào)用此操作符函數(shù)。
定義操作符函數(shù)時(shí)需要在 func 關(guān)鍵字前面添加 operator 修飾符
操作符函數(shù)的參數(shù)個(gè)數(shù)需要匹配對(duì)應(yīng)操作符的要求
操作符函數(shù)只能定義在 class、interface、struct、enum 和 extend 中
操作符函數(shù)具有實(shí)例成員函數(shù)的語義,所以禁止使用 static 修飾符
操作符函數(shù)不能為泛型函數(shù)
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函數(shù)和常量求值
const 變量是一種特殊的變量,它以關(guān)鍵字 const 修飾,定義在編譯時(shí)完成求值,并且在運(yùn)行時(shí)不可改變的變量。
const 變量可以省略類型標(biāo)注,但是不可省略初始化表達(dá)式。
const 變量可以是全局變量,局部變量,靜態(tài)成員變量。但是 const 變量不能在擴(kuò)展中定義。
const 變量可以訪問對(duì)應(yīng)類型的所有實(shí)例成員,也可以調(diào)用對(duì)應(yīng)類型的所有非 mut 實(shí)例成員函數(shù)。
const G = 6.674e-11 // 萬有引力常數(shù)
/// 記錄行星的質(zhì)量和半徑,同時(shí)定義了一個(gè) const 成員函數(shù) gravity 用來計(jì)算該行星對(duì)距離為 r 質(zhì)量為 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))
}
/* 編譯執(zhí)行得到地球?qū)Φ孛嫔弦粋€(gè)質(zhì)量為 71 kg 的成年人的萬有引力(程序輸出):
695.657257
*/
- const 變量初始化后該類型實(shí)例的所有成員都是 const 的(深度 const,包含成員的成員),因此不能被用于左值。
main() {
const myMass = 71.0
myMass = 70.0 // Error, cannot assign to immutable value
}
如果一個(gè) struct 或 class 定義了 const 構(gòu)造器,那么這個(gè) struct/class 實(shí)例可以用在 const 表達(dá)式中。
如果當(dāng)前類型是 class,則不能具有 var 聲明的實(shí)例成員變量,否則不允許定義 const init 。如果當(dāng)前類型具有父類,當(dāng)前的 const init 必須調(diào)用父類的 const init(可以顯式調(diào)用或者隱式調(diào)用無參const init),如果父類沒有 const init 則報(bào)錯(cuò)。
當(dāng)前類型的實(shí)例成員變量如果有初始值,初始值必須要是 const 表達(dá)式,否則不允許定義 const init。
對(duì)于 struct 和 class,只有定義了 const init 才能定義 const 實(shí)例成員函數(shù)。class 中的 const 實(shí)例成員函數(shù)不能是 open 的。struct 中的 const 實(shí)例成員函數(shù)不能是 mut 的。
接口中的 const 函數(shù),實(shí)現(xiàn)類型必須也用 const 函數(shù)才算實(shí)現(xiàn)接口。
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()))
}
/* 編譯執(zhí)行上述代碼,輸出結(jié)果為:
1
*/
一直沒有搞明白,既然已經(jīng)有了let,為什么還要const?并且const還有這么多限制條件,顯然非常不好用。