1. 程式人生 > >Go語言入門系列(六)之再探函式

Go語言入門系列(六)之再探函式

[Go語言入門系列](https://mp.weixin.qq.com/mp/appmsgalbum?action=getalbum&album_id=1441283546689404928)前面的文章: - [Go語言入門系列(三)之陣列和切片](https://mp.weixin.qq.com/s/zoCs5E9SLUZXjdwKfWlwmg) - [Go語言入門系列(四)之map的使用](https://mp.weixin.qq.com/s/XZQWTUhFj8nVDZR8p3cg8g) - [Go語言入門系列(五)之指標和結構體的使用](https://mp.weixin.qq.com/s/GFagQ1USAMoaMB8IAokIpw) 在[Go語言入門系列(二)之基礎語法總結](https://mp.weixin.qq.com/s/HQ4JRzMJK4EcrDjLPgZa5Q)這篇文章中已經介紹過了Go語言的函式的基本使用,包括宣告、引數、返回值。本文再詳細介紹一下函式的其他使用。 # 1. 變參 Go語言的函式除了支援0個或多個引數,還支援不定數量的引數,即變參。宣告方式為: ```go func foo(變參名 ...引數型別) 函式型別 { //函式體 } ``` 下面是一個具體的函式,它接收不定數量的`int`引數,並返回和: ```go package main import "fmt" func add(arg ...int) int { //變參函式 var sum int for _, value := range arg { sum += value } return sum } func main() { sum := add(1, 2, 3, 4) fmt.Println(sum) //10 } ``` `arg ...int`表明`add`函式接收不定數量的引數,且只能是`int`型別的。`arg`是我們給該變參取的名字,它實際上是一個切片,所以在`add`函式中可以使用`range`遍歷變數`arg`。 # 2. 傳值和傳指標 當我們呼叫一個有參函式時,肯定會向該函式中傳入引數: ```go package main import "fmt" //傳入一個值,列印它 func printX(x int) { fmt.Println(x) } func main() { var a int = 5 printX(a) //向函式中傳入引數:變數a } ``` 這裡有一個問題:我們真的是把變數`a`傳給了`printX`函式嗎?我們用兩個例子來說明問題。 ## 2.1. 例1 ```go package main import "fmt" func plusOne(x int) int { x = x + 1 fmt.Println("執行加一") return x } func main() { a := 5 fmt.Println("a =", a) //應該為5 實際為5 b := plusOne(a) fmt.Println("a =", a) //應該為6 實際為5 fmt.Println("b =", b) //應該為6 實際為6 } ``` `plusOne`函式的作用是把傳進來的引數加一,並返回結果。 `a=5`傳進`plusOne`函式,執行了`x = x + 1`語句,那麼執行過後`a`的值應該為6,但實際為5。 變數`b`接收了函式的返回值,所以為6,這沒問題。 這證明了,**我們的`a`變數根本就沒傳進函式中,那麼實際傳的是什麼?實際傳的是`a`變數的一份拷貝。** ![](https://gitee.com/xingrenguanxue/blog-images/raw/master/imgs/20200811180928.png) 所以,**我們向Go語言中的函式傳入一個值,實際上傳的是該值的拷貝,而非該值本身。** 那如果我們確實要把上例中的變數`a`傳入`plusOne`函式中呢?那此時就不應該傳值了,而是**應該傳入指標**。程式碼改進如下: ```go package main import "fmt" func plusOne(x *int) int { //引數是指標變數 *x = *x + 1 fmt.Println("執行加一") return *x } func main() { a := 5 fmt.Println("a =", a) //應該為5 實際為5 b := plusOne(&a) //傳入地址 fmt.Println("a =", a) //應該為6 實際為6 fmt.Println("b =", b) //應該為6 實際為6 } ``` `a=5`傳進`plusOne`函式,執行了`x = x + 1`語句,執行過後`a`的值實際為6。 這就證明,變數`a`確實被傳進`plusOne`函式並被修改了。因為我們傳進去的是一個指標,即變數的地址,有了地址我們可以直接操作變數。 ![](https://gitee.com/xingrenguanxue/blog-images/raw/master/imgs/20200811180939.png) 如果你對指標的使用不熟悉,這裡的程式碼可能會有點難理解,下面逐行解釋: ```go func plusOne(x *int) int { ``` 宣告`x`是一個`int`型別的指標引數,只接受`int`型別變數的地址 。 ```go *x = *x + 1 ``` 使用`*`操作符根據`x`中存的地址,獲取到對應的值,然後加一。 ```go return *x ``` 使用`*`操作符根據`x`中存的地址,獲取到對應的值,然後返回。 ```go b := plusOne(&a) ``` `plusOne`函式只接受`int`型別變數的地址,所以使用`&`操作符獲取`a`變數的地址,然後才傳入。 ## 2.2. 例2 下面我再舉一個經典的例子:寫一個函式,能夠交換兩個變數的值。 如果你不知道什麼是傳值和傳指標,那可能會寫成這樣: ```go package main import "fmt" func swap(x, y int) { tmp := x x = y y = tmp fmt.Println("函式中:x =", x, ", y =", y) } func main() { x, y := 2, 8 fmt.Println("交換前:x =", x, ", y =", y) swap(x, y) fmt.Println("交換後:x =", x, ", y =", y) } ``` 執行結果: ``` 交換前:x = 2 , y = 8 函式中:x = 8 , y = 2 交換後:x = 2 , y = 8 ``` 只在函式中完成了交換,出了函式又變回原樣了。 想要完成交換,就必須傳入指標,而非值拷貝: ```go package main import "fmt" func swap(x, y *int) { tmp := *x *x = *y *y = tmp fmt.Println("函式中:x =", *x, ", y =", *y) } func main() { x, y := 2, 8 fmt.Println("交換前:x =", x, ", y =", y) swap(&x, &y) fmt.Println("交換後:x =", x, ", y =", y) } ``` 執行結果: ``` 交換前:x = 2 , y = 8 函式中:x = 8 , y = 2 交換後:x = 8 , y = 2 ``` 傳入指標能夠真正交換兩個變數的值。 傳入指標的好處: 1. 傳入指標使我們能夠在函式中直接操作變數,多個函式也能操作同一個變數。 2. 不需要再拷貝一遍值了。如果你需要傳入比較大的結構體,再拷貝一遍就多花費系統開銷了,而傳入指標則小的多。 # 3. 函式作為值 在Go語言中,函式也可以作為值來傳遞。下面是一個例子: ```go package main import "fmt" type calculate func(int, int) int // 聲明瞭一個函式型別 func sum(x, y int) int { return x + y } func product(x, y int) int { return x * y } func choose(a, b int, f calculate) int { //函式作為引數 return f(a, b) } func main(){ diff := func(x, y int) int { //函式作為值賦給diff return x - y } fmt.Println(choose(2, 3, sum)) //5 fmt.Println(choose(4, 5, product)) //20 fmt.Println(choose(6, 7, diff)) //-1 fmt.Println(diff(9, 8)) //1 } ``` 函式作為值或者引數肯定要有對應的型別,型別是:`func(引數型別)返回值型別 `。比如`func(int,int) int` 可以使用`type`關鍵字給`func(int,int) int`起個別名叫`calculate`,方便使用。 `choose`函式中聲明瞭一個型別為`calculate`的函式引數`f`,而我們又編寫了`calculate`型別的函式`sum`和`product`,所以可以向`choose`函式中傳入這兩個函式。 我們給變數`diff`賦了一個函式,所以能夠使用`diff(9, 8)`,或者將其作為引數傳入`choose`函式。 # [作者簡介](https://mp.weixin.qq.com/s/PF7srGAwzd_w5pU6eOEZow) > 我是「[行小觀](https://mp.weixin.qq.com/s/PF7srGAwzd_w5pU6eOEZow)」,於千萬人中的一個普通人。陰差陽錯地走上了程式設計這條路,既然走上了這條路,那麼我會盡可能遠地走下去。 > > 我會在公眾號『[行人觀學](https://mp.weixin.qq.com/s/PF7srGAwzd_w5pU6eOEZow)』中持續更新「Java」、「Go」、「資料結構和演算法」、「計算機基礎」等相關文章。 > > 歡迎關注,我們一起踏上行程。 > > 本文章屬於系列文章「[Go語言入門系列](https://mp.weixin.qq.com/mp/appmsgalbum?action=getalbum&album_id=1441283546689404928)」。 > **如有錯誤,還請指