23. 一篇文章 說清楚 Go語言裏的函數

Hi,大家好,我是明哥。

在自己學習 Golang 的這段時間裏,我寫了詳細的學習筆記放在我的個人微信公衆號 《Go編程時光》,對於 Go 語言,我也算是個初學者,因此寫的東西應該會比較適合剛接觸的同學,如果你也是剛學習 Go 語言,不防關注一下,一起學習,一起成長。

我的在線博客:http://golang.iswbm.com
我的 Github:github.com/iswbm/GolangCodingTime


1. 關於函數

函數是基於功能或 邏輯進行封裝的可複用的代碼結構。將一段功能複雜、很長的一段代碼封裝成多個代碼片段(即函數),有助於提高代碼可讀性和可維護性。

在 Go 語言中,函數可以分爲兩種:

  • 帶有名字的普通函數
  • 沒有名字的匿名函數

由於 Go語言是編譯型語言,所以函數編寫的順序是無關緊要的,它不像 Python 那樣,函數在位置上需要定義在調用之前。

2. 函數的聲明

函數的聲明,使用 func 關鍵字,後面依次接 函數名參數列表返回值列表用 {} 包裹的代碼邏輯體

func 函數名(形式參數列表)(返回值列表){
    函數體
}
  • 形式參數列表描述了函數的參數名以及參數類型,這些參數作爲局部變量,其值由參數調用者提供

  • 返回值列表描述了函數返回值的變量名以及類型,如果函數返回一個無名變量或者沒有返回值,返回值列表的括號是可以省略的。

舉個例子,定義一個 sum 函數,接收兩個 int 類型的參數,在運行中,將其值分別賦值給 a,b,並規定必須返回一個int類型的值 。

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

3. 函數實現可變參數

上面舉的例子,參數個數都是固定的,這很好理解 ,指定什麼類型的參數就傳入什麼類型的變量,數量上,不能多一個,也不能少一個。(好像沒有可選參數)。

在 Python 中我們可以使用 *args 和 **kw ,還實現可變參數的函數。

可變參數分爲幾種:

  • 多個類型一致的參數
  • 多個類型不一致的參數

多個類型一致的參數

首先是多個類型一致的參數。

這邊定義一個可以對多個數值進行求和的函數,

使用 ...int,表示一個元素爲int類型的切片,用來接收調用者傳入的參數。

// 使用 ...類型,表示一個元素爲int類型的切片
func sum(args ...int) int {
	var sum int
	for _, v := range args {
		sum += v
	}
	return sum
}
func main() {
	fmt.Println(sum(1, 2, 3))
}

// output: 6

其中 ... 是 Go 語言爲了方便程序員寫代碼而實現的語法糖,如果該函數下會多個類型的函數,這個語法糖必須得是最後一個參數。

同時這個語法糖,只能在定義函數時使用。

多個類型不一致的參數

上面那個例子中,我們的參數類型都是 int,如果你希望傳多個參數且這些參數的類型都不一樣,可以指定類型爲 ...interface{},然後再遍歷。

比如下面這段代碼,是Go語言標準庫中 fmt.Printf() 的函數原型:

import "fmt"
func MyPrintf(args ...interface{}) {
    for _, arg := range args {
        switch arg.(type) {
            case int:
                fmt.Println(arg, "is an int value.")
            case string:
                fmt.Println(arg, "is a string value.")
            case int64:
                fmt.Println(arg, "is an int64 value.")
            default:
                fmt.Println(arg, "is an unknown type.")
        }
    }
}

func main() {
    var v1 int = 1
    var v2 int64 = 234
    var v3 string = "hello"
    var v4 float32 = 1.234
    MyPrintf(v1, v2, v3, v4)
}

在某些情況下,我們需要定義一個參數個數可變的函數,具體傳入幾個參數,由調用者自己決定,但不管傳入幾個參數,函數都能夠處理。

比如這邊實現一個累加

func myfunc(args ...int) {
    for _, arg := range args {
        fmt.Println(arg)
    }
}

4. 多個可變參數函數傳遞參數

上面提到了可以使用 ... 來接收多個參數,除此之外,它還有一個用法,就是用來解序列,將函數的可變參數(一個切片)一個一個取出來,傳遞給另一個可變參數的函數,而不是傳遞可變參數變量本身。

同樣這個用法,也只能在給函數傳遞參數裏使用。

例子如下:

import "fmt"

func sum(args ...int) int {
	var result int
	for _, v := range args {
		result += v
	}
	return result
}

func Sum(args ...int) int {
    // 利用 ... 來解序列
	result := sum(args...)
	return result
}
func main() {
	fmt.Println(sum(1, 2, 3))
}

5. 函數的返回值

Go語言中的函數,在你定義的時候,就規定了此函數

  1. 有沒有返回值?

    當沒有指明返回值的類型時, 函數體不能有 return,Go並不像 Python 那樣沒有return,就默認返回None

  2. 返回幾個值?

    Go 支持一個函數返回多個值

    func double(a int) (int, int) {
    	b := a * 2
    	return a, b
    }
    func main() {
        // 接收參數用逗號分隔
    	a, b := double(2)
    	fmt.Println(a, b)
    }
    
  3. 怎麼返回值?

    Go支持返回帶有變量名的值

    func double(a int) (b int) {
        // 不能使用 := ,因爲在返回值哪裏已經聲明瞭爲int
    	b = a * 2
        // 不需要指明寫回哪個變量,在返回值類型那裏已經指定了
    	return
    }
    func main() {
    	fmt.Println(double(2))
    }
    // output: 4
    

6. 方法與函數

方法,在上一節《08. 面向對象編程:結構體與繼承》裏已經介紹過了,它的定義與函數有些不同,你可以點擊前面的標題進行交叉學習。

方法和函數有什麼區別? 爲防會有朋友第一次接觸面向對象,這裏多嘴一句。

方法,是一種特殊的函數。當你一個函數和對象/結構體進行綁定的時候,我們就稱這個函數是一個方法。

7. 匿名函數的使用

所謂匿名函數,就是沒有名字的函數,它只有函數邏輯體,而沒有函數名。

定義的格式如下

func(參數列表)(返回參數列表){
    函數體
}

一個名字實際上並沒有多大區別,所有使用匿名函數都可以改成普通有名函數,那麼什麼情況下,會使用匿名函數呢?

定義變量名,是一個不難但是還費腦子的事情,對於那到只使用一次的函數,是沒必要擁有姓名的。這纔有了匿名函數。

有了這個背景,決定了匿名函數只有擁有短暫的生命,一般都是定義後立即使用。

就像這樣,定義後立馬執行(這裏只是舉例,實際代碼沒有意義)。

func(data int) {
    fmt.Println("hello", data)
}(100)

亦或是做爲回調函數使用

// 第二個參數爲函數
func visit(list []int, f func(int)) {
    for _, v := range list {
		// 執行回調函數
        f(v)
    }
}
func main() {
    // 使用匿名函數直接做爲參數
    visit([]int{1, 2, 3, 4}, func(v int) {
        fmt.Println(v)
    })
}

系列導讀

01. 開發環境的搭建(Goland & VS Code)

02. 學習五種變量創建的方法

03. 詳解數據類型:****整形與浮點型

04. 詳解數據類型:byte、rune與string

05. 詳解數據類型:數組與切片

06. 詳解數據類型:字典與布爾類型

07. 詳解數據類型:指針

08. 面向對象編程:結構體與繼承

09. 一篇文章理解 Go 裏的函數

10. Go語言流程控制:if-else 條件語句

11. Go語言流程控制:switch-case 選擇語句

12. Go語言流程控制:for 循環語句

13. Go語言流程控制:goto 無條件跳轉

14. Go語言流程控制:defer 延遲調用

15. 面向對象編程:接口與多態

16. 關鍵字:make 和 new 的區別?

17. 一篇文章理解 Go 裏的語句塊與作用域

18. 學習 Go 協程:goroutine

19. 學習 Go 協程:詳解信道/通道

20. 幾個信道死鎖經典錯誤案例詳解

21. 學習 Go 協程:WaitGroup

22. 學習 Go 協程:互斥鎖和讀寫鎖

23. Go 裏的異常處理:panic 和 recover

24. 超詳細解讀 Go Modules 前世今生及入門使用

25. Go 語言中關於包導入必學的 8 個知識點

26. 如何開源自己寫的模塊給別人用?

27. 說說 Go 語言中的類型斷言?

28. 這五點帶你理解Go語言的select用法


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章