倉頡入門:函數(shù)

函數(shù)是一個參數(shù)化的代碼塊,在調(diào)用函數(shù)時,這些代碼塊實(shí)現(xiàn)特定功能并可以被求值,結(jié)合函數(shù)參數(shù)實(shí)現(xiàn)特定范圍的代碼復(fù)用

定義函數(shù)

倉頡使用關(guān)鍵字 func 來表示函數(shù)定義的開始,func 之后依次是函數(shù)名、參數(shù)列表、可選的函數(shù)返回值類型、函數(shù)體。用下面的偽代碼表示:

func 函數(shù)名 (參數(shù)列表):返回值類型{
    函數(shù)體
}

函數(shù)名可以是任意的合法標(biāo)識符,下面依次對函數(shù)定義中的參數(shù)列表、函數(shù)返回值類型和函數(shù)體作進(jìn)一步介紹。

參數(shù)列表

一個函數(shù)可以擁有 0 個多個參數(shù),可以將參數(shù)列表中的參數(shù)分為兩類非命名參數(shù)和命名參數(shù)。

非命名參數(shù)

非命名參數(shù)的定義方式是 p: T,其中 p 表示參數(shù)名,T 表示參數(shù) p 的類型,參數(shù)名和其類型間使用冒號連接。

func add(a: Int64, b: Int64): Int64 {
    return a + b
}

命名參數(shù)

命名參數(shù)還可以設(shè)置默認(rèn)值,通過 p!: T = e 方式將參數(shù) p 的默認(rèn)值設(shè)置為表達(dá)式 e 的值。

func add(a!: Int64 = 1, b!: Int64 = 1): Int64 {
    return a + b
}

注意

1.只能為命名參數(shù)設(shè)置默認(rèn)值,不能為非命名參數(shù)設(shè)置默認(rèn)值。
2.非命名參數(shù)只能定義在命名參數(shù)之前
3.函數(shù)參數(shù)均為不可變變量,在函數(shù)定義內(nèi)不能對其賦值。

函數(shù)返回值類型

函數(shù)返回值類型是函數(shù)被調(diào)用后得到的值的類型。可以顯式地定義返回值類型,也可以不定義返回值類型,交由編譯器推導(dǎo)確定。
當(dāng)顯式地定義了函數(shù)返回值類型時,就要求函數(shù)體的類型、函數(shù)體中所有 return e 表達(dá)式中 e 的類型是返回值類型的子類型。

func add(a:Int16,b:Int16):Int16{
    if(a-b>0){
        return a
    }else{
        return ''  //error: mismatched types
    }
}

注意

函數(shù)的返回值類型并不是任何情況下都可以被推導(dǎo)出來的,如果返回值類型推導(dǎo)失敗,編譯器會報錯。

函數(shù)體

在函數(shù)體內(nèi)定義的變量屬于局部變量的一種,它的作用域從其定義之后開始到函數(shù)體結(jié)束。
對于一個局部變量,允許在其外層作用域中定義同名變量,并且在此局部變量的作用域內(nèi),局部變量會遮蓋外層作用域的同名變量。

var s = 10
func add(a:Int16,b:Int16):Int16{
    var s:Int16 = 0
    return s
}
main() {
    println(add(1,2))  // 輸出 0
}

調(diào)用函數(shù)

根據(jù)函數(shù)定義時參數(shù)是非命名參數(shù)還是命名參數(shù)的差異,函數(shù)調(diào)用時傳實(shí)參的方式也有所不同:對于非命名參數(shù),它對應(yīng)的實(shí)參是一個表達(dá)式;對于命名參數(shù),它對應(yīng)的實(shí)參需要使用 p: e 的形式

非命名參數(shù)調(diào)用

func add(a: Int64, b: Int64) {
    return a + b
}

main() {
    let s = add(10086, 2025)
    println("求和: ${s}")  //求和: 12111
}

命名參數(shù)調(diào)用

對于多個命名參數(shù),調(diào)用時的傳參順序可以和定義時的參數(shù)順序不同。
對于擁有默認(rèn)值的命名參數(shù),調(diào)用時如果沒有傳實(shí)參,那么此參數(shù)將使用默認(rèn)值作為實(shí)參的值。

func add(a: Int64, b!: Int64) {
    return a + b
}
func add2(a!: Int64, b!: Int64) {
    return a + b
}
func add3(a!: Int64=1, b!: Int64=2) {
    return a + b
}
main() {
    let s = add(1, b:2)
    let s2 = add2(a:1, b:2)
    let s3 = add2(b:1,a:2)
    let s4 = add3(a:2)
    let s5 = add3()
    println("一個命名參數(shù):${s}")  //一個命名參數(shù):3
    println("多個命名參數(shù):${s2}")  //一個命名參數(shù):3
    println("多個命名參數(shù):${s3}")  //一個命名參數(shù):3
    println("命名參數(shù)有默認(rèn)值:${s4}")  //一個命名參數(shù):4
    println("命名參數(shù)有默認(rèn)值:${s5}")  //一個命名參數(shù):3
}

函數(shù)類型

倉頡編程語言中,函數(shù)是一等公民,可以作為函數(shù)的參數(shù)或返回值,也可以賦值給變量。因此函數(shù)本身也有類型,稱之為函數(shù)類型。
函數(shù)類型由函數(shù)的參數(shù)類型和返回類型組成,參數(shù)類型和返回類型之間使用 ->連接。參數(shù)類型使用圓括號 () 括起來,可以有 0 個或多個參數(shù),如果參數(shù)超過一個,參數(shù)類型之間使用逗號(,)分隔。

func display(a: Int64): Unit {  // 類型 (Int64) -> Unit
    println(a)
}
func add(a: Int64, b: Int64): Int64 {  // 類型 (Int64, Int64) -> Int64
    a + b
}
func returnTuple(a: Int64, b: Int64): (Int64, Int64) { //類型 (Int64, Int64) -> (Int64, Int64)
    (a, b)
}

函數(shù)類型的類型參數(shù)

可以為函數(shù)類型標(biāo)記顯式的類型參數(shù)名

func add(a: Int64, b: Int64) {
    println("求和: ${a+b}")
}

main() {
    let add2: (a: Int64, b: Int64) -> Unit
    add2 = add
    add2(11, 10)
}

函數(shù)類型作為參數(shù)類型

func add(a: Int64, b: Int64):Int64 {
    return a+b
}
func printAdd(add: (Int64, Int64) -> Int64, a: Int64, b: Int64): Int64 {
    return add(a,b)
}
main() {
    println(printAdd(add,1,2))
}

函數(shù)類型作為返回類型

func add(a: Int64, b: Int64): Int64 {
   return a + b
}
func returnAdd(): (Int64, Int64) -> Int64 {
    return add
}
main() {
    var a: (Int64, Int64) ->  Int64 
    a = add
    println(a(1,2))  //輸出 3
}

函數(shù)類型作為變量類型

函數(shù)名本身也是表達(dá)式,它的類型為對應(yīng)的函數(shù)類型。

func add(a: Int64, b: Int64): Int64 {
   return a + b
}
main() {
    var a: (Int64, Int64) ->  Int64  =add
    println(a(1,2))  //輸出 3
}

嵌套函數(shù)

定義在源文件頂層的函數(shù)被稱為全局函數(shù)。定義在函數(shù)體內(nèi)的函數(shù)被稱為嵌套函數(shù)。

func foo() {
    func nestAdd(a: Int64, b: Int64) {
        a + b
    }
    return nestAdd
}
main() {
    let f = foo()
    println(f(1, 2))
}

Lambda 表達(dá)式

Lambda 表達(dá)式是一種匿名函數(shù)(即沒有函數(shù)名的函數(shù)),其核心設(shè)計目的是在程序中快速定義簡短的函數(shù)邏輯,無需顯式聲明函數(shù)名稱。
Lambda 表達(dá)式的語法為如下形式: { 參數(shù)列表也可沒參數(shù) => 表達(dá)式或聲明 }。
Lambda 表達(dá)式中 不支持聲明返回類型,其返回類型總是從上下文中推斷出來,若無法推斷則報錯。

Lambda 表達(dá)式調(diào)用

let add = { a: Int64, b: Int64 => a + b } // 定義Lambda 表達(dá)式
var display = { => println("Hello") } // 定義Lambda 表達(dá)式
var sum1: (Int64, Int64) -> Int64 = { a, b => a + b }
func sum2(a1: (Int64) -> Int64): Int64 {
    a1(1)
}
main() {
    let f = add     // 定義Lambda 表達(dá)式
    println(f(2, 2))  //輸出4
    display()   //輸出 hello
    println(sum1(2,2))  //輸出4
    println(sum2({ a2 => a2+1  })) //輸出2
    let r1 = { a: Int64, b: Int64 => a + b }(1, 2)
    println(r1)   // 輸出3 立即調(diào)用
    var r2 = { x: Int64,y:Int64 => println(x+y) }
    r2(1,2)    // 輸出3 先賦值給一個變量,再調(diào)用
}

閉包

在嵌套函數(shù)的基礎(chǔ)上,內(nèi)層函數(shù)捕獲了外層局部變量。函數(shù)和捕獲的變量一起被稱為一個閉包。

變量捕獲

1.函數(shù)的參數(shù)缺省值中訪問了本函數(shù)之外定義的局部變量;
2.函數(shù)內(nèi)訪問了本函數(shù)之外定義的局部變量;
3.class/struct 內(nèi)定義的不是成員函數(shù)的函數(shù)訪問了實(shí)例成員變量或 this。

func add(): (Int64) -> Int64 {
    let num: Int64 = 10
    func sum(a: Int64) {
        return a + num   //閉包 add,捕獲了 let 聲明的局部變量 num
    }
    sum
}
main() {
    println(add()(1))
}

為了防止捕獲了 var 聲明變量的閉包逃逸,這類閉包只能被調(diào)用,不能作為一等公民使用,包括不能賦值給變量,不能作為實(shí)參或返回值使用,不能直接將閉包的名字作為表達(dá)式使用。

func f() {
    var x = 1
    func g() {
        println(x)  //捕獲了一個可變變量
    }
    // g //捕獲可變變量的函數(shù)需要直接調(diào)用
    g() // 正常
}

main() {
    println(f())
}

函數(shù)調(diào)用語法糖

尾隨 lambda

當(dāng)函數(shù)最后一個形參是函數(shù)類型,并且函數(shù)調(diào)用對應(yīng)的實(shí)參是 lambda 時,可以使用尾隨 lambda 語法,將 lambda 放在函數(shù)調(diào)用的尾部,圓括號外面。

func add(a: Bool, fn: (Int64) -> Int64) {
     if(a) {
        fn(1)
    } else {
        0
    }
}
func add2(fn2:(Int64)->Int64){
    fn2(2)
}
main() {
    println(add(true,{b:Int64=>b+1}))
    println(add(true){
        b:Int64=>b+1
    })
    println(add2{a=>a*2})  //當(dāng)函數(shù)調(diào)用有且只有一個 lambda 實(shí)參時,還可以省略 (),只寫 lambda。
}

Flow表達(dá)式

流操作符包括兩種:表示數(shù)據(jù)流向的中綴操作符 |> (稱為 pipeline)和表示函數(shù)組合的中綴操作符 ~> (稱為 composition)。

pipeline 表達(dá)式

當(dāng)需要對輸入數(shù)據(jù)做一系列的處理時,可以使用 pipeline 表達(dá)式來簡化描述。pipeline 表達(dá)式的語法形式如下:e1 |> e2。等價于如下形式的語法糖:let v = e1; e2(v) 。
其中 e2 是函數(shù)類型的表達(dá)式,e1 的類型是 e2 的參數(shù)類型的子類型。函數(shù)依次執(zhí)行,前面函數(shù)的執(zhí)行結(jié)果是后面函數(shù)參數(shù)的子類型。

func inc(x: Array<Int64>): Array<Int64> { // 將數(shù)組中每個元素的值增加 “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 { // 獲取數(shù)組中元素的總和
    var s = 0
    for (j in y) {
        s += j
    }
    s
}
main() {
    let arr: Array<Int64> = [1, 3, 5]
    let res = arr |> inc |> sum
    // inc [2,4,6] sum = 2+4+6
    println(res)  //輸出12
}

composition 表達(dá)式

composition 表達(dá)式表示兩個單參函數(shù)的組合。composition 表達(dá)式語法為 f ~> g,等價于 { x => g(f(x)) }。
其中 f,g 均為只有一個參數(shù)的函數(shù)類型的表達(dá)式。
f 和 g 組合,則要求 f(x) 的返回類型是 g(...) 的參數(shù)類型的子類型。

func add1(a:Int64):Int64{
    return a+1
}
func add2(a2:Int64):Int64{
    return a2+2
}
main() {
    let a = add1~>add2
    let a1 =add1~>add1~>add1 //多次執(zhí)行
    println(a(1))  //輸出4
    println(a1(1))  //輸出4
}

變長參數(shù)

變長參數(shù)是一種特殊的函數(shù)調(diào)用語法糖。當(dāng)形參最后一個非命名參數(shù)是 Array 類型時,實(shí)參中對應(yīng)位置可以直接傳入?yún)?shù)序列代替 Array 字面量(參數(shù)個數(shù)可以是 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))
}

注意

只有最后一個非命名參數(shù)可以作為變長參數(shù),命名參數(shù)不能使用這個語法糖。

函數(shù)重載

在倉頡編程語言中,如果一個作用域中,一個函數(shù)名對應(yīng)多個函數(shù)定義,這種現(xiàn)象稱為函數(shù)重載。

函數(shù)重載定義

1.函數(shù)名相同,函數(shù)參數(shù)不同(是指參數(shù)個數(shù)不同,或者參數(shù)個數(shù)相同但參數(shù)類型不同)的兩個函數(shù)構(gòu)成重載。
2.對于兩個同名泛型函數(shù),如果重命名一個函數(shù)的泛型形參后(使泛型參數(shù)順序相同),其非泛型部分與另一個函數(shù)的非泛型部分函數(shù)參數(shù)不同,則兩個函數(shù)構(gòu)成重載,否則這兩個泛型函數(shù)構(gòu)成重復(fù)定義錯誤(類型變元的約束不參與判斷)。

func f1<X, Y>(a: X, b: Y) {}
// func f1<Y, X>(a: X, b: Y) {} //屬于重載 參數(shù)類型順序相同
// func f1<Y, X>(b: X, a: Y) {} //屬于重載 參數(shù)類型順序相同
// func f1<X, Y>(a: Y, b: X) {}  //屬于重載
func f1<Y, X>(a: Y, b: X) {}  //重載沖突

3.同一個類內(nèi)的兩個構(gòu)造函數(shù)參數(shù)不同,構(gòu)成重載。
4.同一個類內(nèi)的主構(gòu)造函數(shù)和 init 構(gòu)造函數(shù)參數(shù)不同,構(gòu)成重載。
5.如果子類中存在與父類同名的函數(shù),并且函數(shù)的參數(shù)類型不同,則構(gòu)成函數(shù)重載。

函數(shù)重載決議

函數(shù)調(diào)用時,所有可被調(diào)用的函數(shù)構(gòu)成候選集,候選集中有多個函數(shù),究竟選擇候選集中哪個函數(shù),需要進(jìn)行函數(shù)重載決議,有如下規(guī)則:
1.優(yōu)先選擇作用域級別高的作用域內(nèi)的函數(shù)。
2.如果作用域級別相對最高的仍有多個函數(shù),則需要選擇實(shí)參最匹配的函數(shù)。
3.子類和父類認(rèn)為是同一作用域。

操作符重載

如果希望在某個類型上支持此類型默認(rèn)不支持的操作符,可以使用操作符重載實(shí)現(xiàn)。
如果需要在某個類型上重載某個操作符,可以通過為類型定義一個函數(shù)名為此操作符的函數(shù)的方式實(shí)現(xiàn),這樣,在該類型的實(shí)例使用該操作符時,就會自動調(diào)用此操作符函數(shù)。
操作符函數(shù)定義與普通函數(shù)定義相似,區(qū)別如下:
1.定義操作符函數(shù)時需要在 func關(guān)鍵字前面添加 operator修飾符;
2.操作符函數(shù)的參數(shù)個數(shù)需要匹配對應(yīng)操作符的要求
3.操作符函數(shù)只能定義在 class、interface、struct、enum 和 extend 中;
4.操作符函數(shù)具有實(shí)例成員函數(shù)的語義,所以禁止使用 static修飾符;
5.操作符函數(shù)不能為泛型函數(shù)。

操作符函數(shù)重載定義和使用

定義操作符函數(shù)有兩種方式:

  1. 對于可以直接包含函數(shù)定義的類型 (包括 struct、enum、class 和 interface ),可以直接在其內(nèi)部定義操作符函數(shù)的方式實(shí)現(xiàn)操作符的重載。
  2. 使用 extend的方式為其添加操作符函數(shù),從而實(shí)現(xiàn)操作符在這些類型上的重載。對于無法直接包含函數(shù)定義的類型(是指除 struct、class、enum 和 interface 之外其他的類型)或無法改變其實(shí)現(xiàn)的類型,比如第三方定義的 struct、class、enum 和 interface,只能采用這種方式。

操作符函數(shù)對參數(shù)類型的約定如下:

  1. 對于一元操作符,操作符函數(shù)沒有參數(shù),對返回值的類型沒有要求。
  2. 對于二元操作符,操作符函數(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)
    println('p3.x:${p3.x} p3.y:${p3.y}') //p3.x:0 p3.y:0
}

3.索引操作符([])分為取值 let a = arr[i] 和賦值 arr[i] = a 兩種形式。除 enum 外的不可變類型不支持重載索引操作符賦值形式。
4.函數(shù)調(diào)用操作符(())重載函數(shù),輸入?yún)?shù)和返回值類型可以是任意類型。

const 函數(shù)和常量求值

常量求值允許某些特定形式的表達(dá)式在編譯時求值,可以減少程序運(yùn)行時需要的計算。

const上下文與const表達(dá)式

const 上下文是指 const 變量初始化表達(dá)式,這些表達(dá)式始終在編譯時求值。因此需要對 const 上下文中允許的表達(dá)式加以限制,避免修改全局狀態(tài)、I/O 等副作用,確保其可以在編譯時求值。

const 函數(shù)

const 函數(shù)是一類特殊的函數(shù),這些函數(shù)具備了可以在編譯時求值的能力。在 const 上下文中調(diào)用這種函數(shù)時,這些函數(shù)會在編譯時執(zhí)行計算。而在其他非 const 上下文,const 函數(shù)會和普通函數(shù)一樣在運(yùn)行時執(zhí)行。

const init

如果一個 struct 或 class 定義了 const 構(gòu)造器,那么這個 struct/class 實(shí)例可以用在 const 表達(dá)式中。const init 與 const 函數(shù)的區(qū)別是 const init 內(nèi)允許對實(shí)例成員變量進(jìn)行賦值(需要使用賦值表達(dá)式)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容