GO語言入門 - (六)函式和方法

周兆東發表於2021-10-18
是時候開始研究函式和方法了。。。

函式

通過函式,可以把開發任務分解成一個個小的單元,這些小單元可以被其他單元複用,進而提高開發效率、降低程式碼重合度。

1. 函式宣告

func funcName(params) result {
    body
}
  • 關鍵字 func
  • 函式名字 funcName
  • 函式的引數 params,用來定義形參的變數名和型別
  • result 是返回的函式值,用於定義返回值的型別,如果沒有可以省略
  • body 就是函式體,可以在這裡寫函式的程式碼邏輯

寫一個計算兩數相加的函式:

package main

import (
    "fmt"
)

// 計算兩值之和
// 變數名稱在前,變數型別在後
// 變數名稱叫做引數名稱,也就是函式的形參
func addTwoNum(a int, b int) int{
    return a + b
}

func main(){
    fmt.Println(addTwoNum(12, 21))
}

執行結果為:
image.png

2. 多值返回

Go 語言的函式可以返回多個值,也就是多值返回
第一個值返回函式的結果,第二個值返回函式出錯的資訊

package main

import (
    "errors"
    "fmt"
)

// 計算兩值之和,如果為負數就返回錯誤
func addTwoNum(a int, b int) (int, error) {
    if a < 0 || b < 0 {
        return 0, errors.New("a或者b不能為負數")
    }
    return a + b, nil
}

func main() {
    // 獲取結果和錯誤資訊
    a, err := addTwoNum(-12, 21)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println("計算結果為:", a)
    }
}

執行結果為:
image.png

3.命名返回引數

函式的返回值也可以有變數名稱,這個並不常用,瞭解一下
改造函式為:

package main

import (
    "errors"
    "fmt"
)

// 計算兩值之和,如果為負數就返回錯誤
func addTwoNum(a int, b int) (sum int, err error) {
    if a < 0 || b < 0 {
        // 這裡按照正常進行返回
        return 0, errors.New("a或者b不能為負數")
    }
    // 這裡按照返回值給相關引數賦值,return後面不需要任何引數
    sum = a + b
    err = nil
    return
}

func main() {
    // 獲取結果和錯誤資訊
    a, err := addTwoNum(-12, 21)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println("計算結果為:", a)
    }
}

執行結果為:
image.png

4. 可變引數

可變引數,就是函式的引數數量是可變的
如果函式中既有普通引數又有可變引數,那麼可變引數一定要放到引數列表的末尾
主要就是在引數型別前面新增三個點
例如:

// 沒有引數
fmt.Println()
// 一個引數
fmt.Println("zhouzhaodong")
// 兩個引數
fmt.Println("zhouzhaodong","xiaoqiang")

我們寫一個計算所有數字之和的函式:

package main

import (
    "fmt"
)

// 計算所有值之和
func addAllNum(params ...int) int {
    sum := 0
    for _, i := range params {
        sum += i
    }
    return sum
}

func main() {
    fmt.Println("計算結果為:", addAllNum(1, 2, 3, 3, 3, 4, 5, 9))
}

執行結果為:
image.png

5. 包級函式

不管是自定義的函式
還是我們使用到的函式,都會從屬一個包
也就是 package
不同包的函式要被呼叫,那麼函式的作用域必須是公有的,也就是函式名稱的首字母要大寫

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

6. 匿名函式和閉包

匿名函式就是沒有名字的函式

package main

import (
    "fmt"
)

func main() {
    sum := func(a, b int) int {
        return a + b
    }
    fmt.Println("計算結果為:", sum(1, 2))
}

執行結果為:
image.png

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

方法

方法必須要有一個接收者,這個接收者是一個型別
這樣方法就和這個型別繫結在一起,成為這個型別的方法

接收者的定義和普通變數、函式引數等一樣
前面是變數名,後面是接收者型別

package main

import (
    "fmt"
)

// 定義一個新的型別,該型別等價於 uint
// 可以理解為 uint 的重新命名
type Age uint

// 定義一個方法,引數就是Age
func (age Age) String() {
    fmt.Println("the age is", age)
}

func main() {
    age := Age(25)
    age.String()
}

執行結果為:
image.png

接收者就是函式和方法最大的不同

1. 值型別接收者和指標型別接收者

定義的方法的接收者型別是指標,所以我們對指標的修改是有效的,如果不是指標,修改就沒有效果,如下所示:

package main

import (
    "fmt"
)

// 定義一個新的型別,該型別等價於 uint
// 可以理解為 uint 的重新命名
type Age uint

// 定義一個方法,引數就是Age
func (age Age) String() {
    fmt.Println("the age is", age)
}

// 定義一個方法,引數就是Age指標
func (age *Age) Modify() {
    *age = Age(30)
}

func main() {
    age := Age(25)
    age.String()
    // 修改age的值
    age.Modify()
    age.String()
}

執行結果為:
image.png

提示:在呼叫方法的時候,傳遞的接收者本質上都是副本,只不過一個是這個值副本,一是指向這個值指標的副本。指標具有指向原有值的特性,所以修改了指標指向的值,也就修改了原有的值。我們可以簡單地理解為值接收者使用的是值的副本來呼叫方法,而指標接收者使用實際的值來呼叫方法。

這就是 Go 語言編譯器幫我們自動做的事情:

  • 如果使用一個值型別變數呼叫指標型別接收者的方法,Go 語言編譯器會自動幫我們取指標呼叫,以滿足指標接收者的要求。
  • 如果使用一個指標型別變數呼叫值型別接收者的方法,Go 語言編譯器會自動幫我們解引用呼叫,以滿足值型別接收者的要求。

總之,方法的呼叫者,既可以是值也可以是指標,不用太關注這些,Go 語言會幫我們自動轉義,大大提高開發效率,同時避免因不小心造成的 Bug。

不管是使用值型別接收者,還是指標型別接收者,要先確定你的需求:在對型別進行操作的時候是要改變當前接收者的值,還是要建立一個新值進行返回?這些就可以決定使用哪種接收者。

個人部落格地址:

http://www.zhouzhaodong.xyz/

相關文章