函式: 函式是怎麼使用的?

Swenson1992發表於2021-02-01

函式和方法是我們邁向程式碼複用、多人協作開發的第一步。通過函式,可以把開發任務分解成一個個小的單元,這些小單元可以被其他單元複用,進而提高開發效率、降低程式碼重合度。再加上現成的函式已經被充分測試和使用過,所以其他函式在使用這個函式時也更安全,比你自己重新寫一個相似功能的函式 Bug 率更低。
雖然在 Go 語言中有函式和方法兩種概念,但它們的相似度非常高,只是所屬的物件不同。我們先從函式開始瞭解。

函式

函式初探

Go 語言中一個非常重要的函式:main 函式,它是一個 Go 語言程式的入口函式,如下所示:

func main() {
}

它由以下幾部分構成:

  1. 任何一個函式的定義,都有一個 func 關鍵字,用於宣告一個函式,就像使用 var 關鍵字宣告一個變數一樣;
  2. 然後緊跟的 main 是函式的名字,命名符合 Go 語言的規範即可,比如不能以數字開頭;
  3. main 函式名字後面的一對括號 () 是不能省略的,括號裡可以定義函式使用的引數,這裡的 main 函式沒有引數,所以是空括號 () ;
  4. 括號 () 後還可以有函式的返回值,因為 main 函式沒有返回值,所以這裡沒有定義;
  5. 最後就是大括號 {} 函式體了,你可以在函式體裡書寫程式碼,寫該函式自己的業務邏輯。

函式宣告

現在總結出函式的宣告格式,如下面的程式碼所示:

func funcName(params) result {
    body
}

這就是一個函式的簽名定義,它包含以下幾個部分:

  1. 關鍵字 func;
  2. 函式名字 funcName;
  3. 函式的引數 params,用來定義形參的變數名和型別,可以有一個引數,也可以有多個,也可以沒有;
  4. result 是返回的函式值,用於定義返回值的型別,如果沒有返回值,省略即可,也可以有多個返回值;
  5. body 就是函式體,可以在這裡寫函式的程式碼邏輯。

自定義一個函式,如下所示:

func sum(a int,b int) int{
    return a+b
}

這是一個計算兩數之和的函式,函式的名字是 sum,它有兩個引數 a、b,引數的型別都是 int。sum 函式的返回值也是 int 型別,函式體部分就是把 a 和 b 相加,然後通過 return 關鍵字返回,如果函式沒有返回值,可以不用使用 return 關鍵字。
函式中形參的定義和我們定義變數是一樣的,都是變數名稱在前,變數型別在後,只不過在函式裡,變數名稱叫作引數名稱,也就是函式的形參,形參只能在該函式體內使用。函式形參的值由呼叫者提供,這個值也稱為函式的實參,現在我們傳遞實參給 sum 函式,演示函式的呼叫,如下面的程式碼所示:

func main() {
    result:=sum(1,2)
    fmt.Println(result)
}

我們自定義的 sum 函式,在 main 函式中直接呼叫,呼叫的時候需要提供真實的引數,也就是實參 1 和 2。
函式的返回值被賦值給變數 result,然後把這個結果列印出來。執行一下,能看到結果是 3,這樣通過函式 sum 達到了兩數相加的目的,如果其他業務邏輯也需要兩數相加,那麼就可以直接使用這個 sum 函式,不用再定義了。

在以上函式定義中,a 和 b 形參的型別是一樣的,這個時候我們可以省略其中一個型別的宣告,如下所示:

func sum(a, b int) int {
    return a + b
}

像這樣使用逗號分隔變數,後面統一使用 int 型別,這和變數的宣告是一樣的,多個相同型別的變數都可以這麼宣告。

多值返回

同有的程式語言不一樣,Go 語言的函式可以返回多個值,也就是多值返回。在 Go 語言的標準庫中,你可以看到很多這樣的函式:第一個值返回函式的結果,第二個值返回函式出錯的資訊,這種就是多值返回的經典應用。

對於 sum 函式,假設我們不允許提供的實參是負數,可以這樣改造:在實參是負數的時候,通過多值返回,返回函式的錯誤資訊,如下面的程式碼所示:

func sum(a, b int) (int,error){
    if a<0 || b<0 {
        return 0,errors.New("a或者b不能是負數")
    }
    return a + b,nil
}

這裡需要注意的是,如果函式有多個返回值,返回值部分的型別定義需要使用小括號括起來,也就是 (int,error)。這代表函式 sum 有兩個返回值,第一個是 int 型別,第二個是 error 型別,我們在函式體中使用 return 返回結果的時候,也要符合這個型別順序。

在函式體中,可以使用 return 返回多個值,返回的多個值通過逗號分隔即可,返回多個值的型別順序要和函式宣告的返回型別順序一致,比如下面的例子:

return 0,errors.New("a或者b不能是負數")

返回的第一個值 0 是 int 型別,第二個值是 error 型別,和函式定義的返回型別完全一致。

定義好了多值返回的函式,現在我們用如下程式碼嘗試呼叫:

func main() {
    result,err := sum(1, 2)
    if err!=nil {
        fmt.Println(err)
    }else {
        fmt.Println(result)
    }
}

函式有多值返回的時候,需要有多個變數接收它的值,示例中使用 result 和 err 變數,使用逗號分開。

如果有的函式的返回值不需要,可以使用下劃線 _ 丟棄它,這種方式我在 for range 迴圈那節課裡也使用過,如下所示:

result,_ := sum(1, 2)

這樣即可忽略函式 sum 返回的錯誤資訊,也不用再做判斷。

提示:這裡使用的 error 是 Go 語言內建的一個介面,用於表示程式的錯誤資訊。

命名返回引數

不止函式的引數可以有變數名稱,函式的返回值也可以,也就是說你可以為每個返回值都起一個名字,這個名字可以像引數一樣在函式體內使用。

現在我們繼續對 sum 函式的例子進行改造,為其返回值命名,如下面的程式碼所示:

func sum(a, b int) (sum int,err error){
    if a<0 || b<0 {
        return 0,errors.New("a或者b不能是負數")
    }
    sum=a+b
    err=nil
    return 
}

返回值的命名和引數、變數都是一樣的,名稱在前,型別在後。以上示例中,命名的兩個返回值名稱,一個是 sum,一個是 err,這樣就可以在函式體中使用它們了。
通過下面示例中的這種方式直接為命名返回引數賦值,也就等於函式有了返回值,所以就可以忽略 return 的返回值了,也就是說,示例中只有一個 return,return 後沒有要返回的值。

sum=a+b
err=nil

通過命名返回引數的賦值方式,和直接使用 return 返回值的方式結果是一樣的,所以呼叫以上 sum 函式,返回的結果也一樣。

雖然 Go 語言支援函式返回值命名,但是並不是太常用,根據自己的需求情況,酌情選擇是否對函式返回值命名。

可變引數

可變引數,就是函式的引數數量是可變的,比如最常見的 fmt.Println 函式。

同樣一個函式,可以不傳引數,也可以傳遞一個引數,也可以兩個引數,也可以是多個等等,這種函式就是具有可變引數的函式,如下所示:

fmt.Println()
fmt.Println("Hello")
fmt.Println("Hello","Golang")

下面是 Println 函式的宣告,從中可以看到,定義可變引數,只要在引數型別前加三個點 … 即可:

func Println(a ...interface{}) (n int, err error)

現在我們也可以定義自己的可變引數的函式了。還是以 sum 函式為例,在下面的程式碼中,通過可變引數的方式,計算呼叫者傳遞的所有實參的和:

func sumAll(params ...int) int {
    sum := 0
    for _, i := range params {
        sum += i
    }
    return sum
}

該函式的引數是一個可變引數,然後通過 for range 迴圈來計算這些引數之和。
可變引數的型別其實就是切片,比如示例中 params 引數的型別是 []int,所以可以使用 for range 進行迴圈。
函式有了可變引數,就可以靈活地進行使用了。

如下面的呼叫者示例,傳遞幾個引數都可以,非常方便,也更靈活:

fmt.Println(sum1(1,2))
fmt.Println(sum1(1,2,3))
fmt.Println(sum1(1,2,3,4))

這裡需要注意,如果你定義的函式中既有普通引數,又有可變引數,那麼可變引數一定要放在引數列表的最後一個,比如 sum1(tip string,params …int) ,params 可變引數一定要放在最末尾。

包級函式

不管是自定義的函式 sum,sumAll,還是使用到的函式 Println,都會從屬於一個包,也就是 package。sum 函式屬於 main 包,Println 函式屬於 fmt 包。

同一個包中的函式哪怕是私有的(函式名稱首字母小寫)也可以被呼叫。如果不同包的函式要被呼叫,那麼函式的作用域必須是公有的,也就是函式名稱的首字母要大寫,比如 Println。

  1. 函式名稱首字母小寫代表私有函式,只有在同一個包中才可以被呼叫;
  2. 函式名稱首字母大寫代表公有函式,不同的包也可以呼叫;
  3. 任何一個函式都會從屬於一個包。

    小提示:Go 語言沒有用 public、private 這樣的修飾符來修飾函式是公有還是私有,而是通過函式名稱的大小寫來代表,這樣省略了煩瑣的修飾符,更簡潔。

匿名函式和閉包

顧名思義,匿名函式就是沒有名字的函式,這是它和正常函式的主要區別。

在下面的示例中,變數 sum2 所對應的值就是一個匿名函式。需要注意的是,這裡的 sum2 只是一個函式型別的變數,並不是函式的名字。

func main() {
    sum2 := func(a, b int) int {
        return a + b
    }
    fmt.Println(sum2(1, 2))
}

通過 sum2,我們可以對匿名函式進行呼叫,以上示例算出的結果是 3,和使用正常的函式一樣。

有了匿名函式,就可以在函式中再定義函式(函式巢狀),定義的這個匿名函式,也可以稱為內部函式。更重要的是,在函式內定義的內部函式,可以使用外部函式的變數等,這種方式也稱為閉包

func main() {
    cl:=colsure()
    fmt.Println(cl())
    fmt.Println(cl())
    fmt.Println(cl())
}

func colsure() func() int {
    i:=0
    return func() int {
        i++
        return i
    }
}

執行這個程式碼,看到輸出列印的結果是:

1
2
3

這都得益於匿名函式閉包的能力,讓我們自定義的 colsure 函式,可以返回一個匿名函式,並且持有外部函式 colsure 的變數 i。因而在 main 函式中,每呼叫一次 cl(),i 的值就會加 1。

小提示:在 Go 語言中,函式也是一種型別,它也可以被用來宣告函式型別的變數、引數或者作為另一個函式的返回值型別。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
?

相關文章