1. 程式人生 > >Golang簡單入門教程——函式進階篇

Golang簡單入門教程——函式進階篇

本文始發於個人公眾號:**TechFlow**,原創不易,求個關注

今天是golang專題的第八篇,我們來聊聊golang當中的函式。

我們在之前的時候已經介紹過了函式的基本用法,知道了怎麼樣設計或者是定義一個函式,以及怎麼樣呼叫一個函式,還了解了defer的用法。今天這篇文章我們來繼續深入這個話題,來看看golang當中關於函式的一些進階的用法。

返回error

前文當中我們曾經提到過,在golang當中並沒有try catch捕獲異常的機制。在其他語言當中異常只有一種,可以通過try catch語句進行捕獲,而golang當中做了區分,將異常分為兩種,一種是可以在函式當中返回的error,另外一種是嚴重的會引起程式崩潰的panic。

在golang中,error也是一個數據型別,由於golang支援函式的多值返回,所以我們可以設定一個返回值是error。我們通過對這個error的判斷來獲取執行函式的情況。

舉個例子,比如說,假設我們實現一個Divide函式實現兩個int相除。那麼顯然我們需要除數不能為0,當除數為0的時候我們需要返回一個異常。這個時候我們可以把程式碼寫成這樣:

// Divide test
func Divide(a, b int) (ret int, err error) {
 if b == 0 {
  err = errors.New("divisor is zero")
  return
 }
 return a / b, nil
}

當我們呼叫函式的時候,我們用兩個變數去接收這個函式返回的結果,第二個變數的型別是error。當這個函式成功執行的時候第二個變數的結果為nil,我們只需要判斷它是否等於nil,就可以知道函式執行是否成功。如果不成功,我們還可以記錄失敗的原因。

func main() {
 ret, err := Divide(5, 2)
 if err == nil {
  fmt.Println(ret)
 } else {
  fmt.Println(err)
 }
}

這種用法在golang當中非常常見,我們之前在介紹字串相關操作的時候也介紹過返回error的用法。我們在設計函式的時候如果需要判斷輸入的合法性可以使用error,這樣就可以保證handle住非法的情況,並且也能讓下游感知到。

不定引數

不定引數的用法在很多語言當中都有,比如在Python當中,不定引數是*args。通過*args我們可以接受任何數量的引數,由於Python是弱變數型別的語言,所以args這些引數的型別可以互不相同。但是golang不行,golang嚴格限制類型,不定引數必須要保證型別一樣。除此之外,其他的用法和Python一樣,不定引數會以陣列的形式傳入函式內部,我們可以使用陣列的api進行訪問。

我們來看一個例子,我們通過...來定義不定引數。比如我們可以實現一個sum函式,可以將任意個int進行累加。

func Sum(nums ... int) int{
    ret := 0
    for _, num := range nums {
        ret += num
    }
    return ret
}

我們來仔細研究一下上面這個例子,在這個例子當中,我們通過...傳入了一個不定引數,我們不定引數的型別只寫一次,寫在...的後面。從底層實現的機制上來說,不定引數本質上是將傳入的引數轉化成陣列的切片。但是這就有了一個問題,既然傳入的是一個數組的切片,我們為什麼要專門設定一個關鍵字,而不是規定傳入一個切片呢?

比如上面的程式碼我們完全可以寫成這樣:

func Sum(nums []int) int{
    ret := 0
    for _, num := range nums {
        ret += num
    }
    return ret
}

無論從程式碼的閱讀還是編寫上來看相差並不大,好像這樣做完全沒有意義,其實不是這樣的。這個關鍵字簡化的並不是函式的設計方,而是函式的使用方。如果我們規定了函式的輸入是一個切片,那麼當我們在傳入資料的時候,必須要使用強制轉化,將我們的資料轉化成切片,比如這樣:

Sum([]int(3, 4, 6, 8))

而使用...關鍵字我們則可以省略掉強制轉化的過程,上面的程式碼我們寫成這樣就可以了:

Sum(3, 4, 6, 8)

很明顯可以看出差異,使用不定引數的話呼叫方會輕鬆很多,不需要再進行額外的轉換。如果我們要傳入的也是一個數組,那麼在傳遞的時候也需要用...符號將它展開。

a := make([]int)
a = append(a, 3)
a = append(a, 4)
Sum(a...)
Sum(a[1:]...)

既然聊到不定引數的傳遞,那麼又涉及到了一個問題,當我們想要像Python那樣傳遞多個型別不同的引數的時候,應該怎麼辦呢?按照道理golang是靜態型別的語言,限制死了引數的型別,是不能隨便轉換的才對。但是偏偏這樣操作是可以的,因為golang當中有一個特殊的型別,叫做interface。

interface的用法很多,一個很重要的用法是用在面向物件當中充當結構體的介面。這裡我們不做過多深入,我們只需要知道,interface的一個用法是可以用來代替所有型別的變數。我們來看一個例子:

func testInterface(args ...interface{}) {
    for _, arg := range args {
        switch arg.(type) {
            case int:
             fmt.Println("it's a int")
         case string:
             fmt.Println("it's a string")    
            case float32:
             fmt.Println("it's a float")
            default:
             fmt.Println("it's an unknown type")
        }
    }
}


func main() {
    testInterface(3, 4.5, "abc")
}

我們可以用.(type)獲取一個interface變數實際的型別,這樣我們就實現了任意型別任意數量引數的傳入。

匿名函式和閉包

匿名函式我們在Python當中經常使用到,其實這個概念出現已久,最早可以追溯到1958年Lisp語言。所以這並不是一個新鮮的概念,只是傳統的C、C++等語言沒有支援匿名函式的功能,所以顯得好像是一個新出現的概念一樣。golang當中也支援匿名函式,但是golang當中匿名函式的使用方式和Python等語言稍稍有些不同。

在Python當中我們是通過lambda關鍵字來定義匿名函式,它可以被傳入另一個函式當中,也可以賦值給一個變數。golang當中匿名函式的定義方式和普通函式基本是一樣的,只是沒有函式名而已,不過它也可以被傳入函式或者是賦值給另一個變數。

比如:

s := func(a, b int) int {
    return a + b
}

c := s(3, 4)

除了匿名函式之外,golang還支援閉包。閉包的概念我們在之前Python閉包的介紹當中曾經提到過,我們之前也用過好幾次,閉包的本質不是一個包,而是一個函式,是一個持有外部環境變數的函式。比如在Python當中,我們經常可以看到這樣的寫法:

def outside(x):
    def inside(y):
        print(x, y)
    return inside


ins = outside(3)
ins(5) #3, 5

我們可以看到outside這個函式返回了inside這個函式,對於inside這個函式而言,它持有了x這個變數。x這個變數並不是屬於它的,而是定義在它的外部域的。並且我們在呼叫inside的時候是無法干涉這個變數的,這就是一個閉包的典型例子。根據輪子哥的說法,閉包的閉的意思並不是封閉內部,而是封閉外部。當外部scope失效的時候,函式仍然持有一份外部的環境的值。

golang當中閉包的使用方法大同小異,我們來看一個類似的例子:

func main() {
    a := func(x int) (func(int)) {
        return func(y int){
            fmt.Println(x, y)
        }
    }
    b := a(4)
    b(5)
}

這個閉包的例子和剛才上面Python那個例子是一樣的,唯一不同的是由於golang是強型別的語言,所以我們需要在定義閉包的時候將輸入和輸出的型別定義清楚。

總結

關於golang當中函式的高階用法就差不多介紹完了,這些都是實際程式設計當中經常使用的方法,如果想要學好golang這門語言的話,這些是基本功。如果你之前有其他語言的基礎,來寫go的話,整體上手的難度還是不大的,很多設計都可以在其他的語言當中找到影子,有了參照來學會簡單得多。

我很難描述實際工作當中寫golang的體驗,和我寫任何一門其他的語言都不一樣,有一種一開始期望很低,慢慢慢慢總能發現驚喜的感覺。我強烈建議大家去實際感受一下。

如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

本文使用 mdnice 排版

![](https://user-gold-cdn.xitu.io/2020/6/15/172b5d2842e8a47b?w=258&h=258&f=png&