Go 里面有三種類型的函數:
1. 普通的帶有名字的函數
2. 匿名函數或者 lambda 函數
3. 方法
## init() 函數
init() 函數的特性如下:
- init() 函數會在程序執行前(main() 函數執行前)被自動調用。
- 調用順序為 main() 中引用的包,以深度優先順序初始化。
- 同一個包中的多個 init() 函數的調用順序不可預期。
- init() 函數不能被其他函數調用。
例如,假設有這樣的包引用關系:main→A→B→C,那么這些包的 init() 函數調用順序為:
```
C.init→B.init→A.init→main
```
除了 main ()、init () 函數外,其它所有類型的函數都可以有參數與返回值。函數參數、返回值以及它們的類型被統稱為函數簽名。
函數可以將其他函數調用作為它的參數,只要這個被調用函數的返回值個數、返回值類型和返回值的順序與調用函數所需求的實參是一致的,例如:
假設 f1 需要 3 個參數 f1(a, b, c int),同時 f2 返回 3 個參數 f2(a, b int) (int, int, int),就可以這樣調用 f1:f1(f2(a, b))。
函數能夠接收參數供自己使用,也可以返回零個或多個值(我們通常把返回多個值稱為返回一組值)。
函數定義時,它的形參一般是有名字的,不過我們也可以定義沒有形參名的函數,只有相應的形參類型,就像這樣:func f(int, int, float64)。
沒有參數的函數通常被稱為 niladic 函數(niladic function),就像 main.main()。
### 按值傳遞和按引用傳遞
Go 默認使用按值傳遞來傳遞參數,也就是傳遞參數的副本。函數接收參數副本之后,在使用變量的過程中可能對副本的值進行更改,但不會影響到原來的變量,比如 Function(arg1)。
如果你希望函數可以直接修改參數的值,而不是對參數的副本進行操作,你需要將參數的地址(變量名前面添加 & 符號,比如 &variable)傳遞給函數,這就是按引用傳遞,比如 Function(&arg1),此時傳遞給函數的是一個指針。如果傳遞給函數的是一個指針,指針的值(一個地址)會被復制,但指針的值所指向的地址上的值不會被復制;我們可以通過這個指針的值來修改這個值所指向的地址上的值。
幾乎在任何情況下,傳遞指針(一個 32 位或者 64 位的值)的消耗都比傳遞副本來得少。
在函數調用時,像切片(slice)、字典(map)、接口(interface)、通道(channel)這樣的引用類型都是默認使用引用傳遞(即使沒有顯式的指出指針)。
### 命名返回值和非命名返回值
```
package main
import "fmt"
var num int = 10
var numx2, numx3 int
func main() {
numx2, numx3 = getX2AndX3(num)
PrintValues()
numx2, numx3 = getX2AndX3_2(num)
PrintValues()
}
func PrintValues() {
fmt.Printf("num = %d, 2x num = %d, 3x num = %d\n", num, numx2, numx3)
}
// 非命名返回值,如果只有一個返回值可以省略括號
func getX2AndX3(input int) (int, int) {
return 2 * input, 3 * input
}
// 命名返回值,即使僅有一個命名返回值也需要用括號括起來
func getX2AndX3_2(input int) (x2 int, x3 int) {
x2 = 2 * input
x3 = 3 * input
//return x2, x3 兩種返回方法都可以
return
}
```
### 改變外部變量
傳遞指針給函數不但可以節省內存(因為沒有復制變量的值),而且賦予了函數直接修改外部變量的能力,所以被修改的變量不再需要使用 return 返回。如下的例子,reply 是一個指向 int 變量的指針,通過這個指針,我們在函數內修改了這個 int 變量的數值。
```
package main
import (
"fmt"
)
// this function changes reply:
func Multiply(a, b int, reply *int) {
*reply = a * b
}
func main() {
n := 0
reply := &n
Multiply(10, 5, reply)
fmt.Println("Multiply:", *reply) // Multiply: 50
}
```
這僅僅是個指導性的例子,當需要在函數內改變一個占用內存比較大的變量時,性能優勢就更加明顯了。然而,如果不小心使用的話,傳遞一個指針很容易引發一些不確定的事,所以,我們要十分小心那些可以改變外部變量的函數,在必要時,需要添加注釋以便其他人能夠更加清楚的知道函數里面到底發生了什么。
### 傳遞變長參數
如果函數的最后一個參數是采用 ...type 的形式,那么這個函數就可以處理一個變長的參數,這個長度可以為 0,這樣的函數稱為變長函數。
```
定義:
func myFunc(a, b, arg ...int) {}
使用:
myFunc(a, b, c, d)
或者
slice := []int{a, b, c, d}
myFunc(slice...)
```
```
package main
import "fmt"
func main() {
x := min(1, 3, 2, 0)
fmt.Printf("The minimum is: %d\n", x)
slice := []int{7,9,3,5,1}
x = min(slice...)
fmt.Printf("The minimum in the slice is: %d", x)
}
func min(s ...int) int {
if len(s)==0 {
return 0
}
min := s[0]
for _, v := range s {
if v < min {
min = v
}
}
return min
}
```
### defer 和追蹤
關鍵字 defer 允許我們推遲到函數返回之前(或任意位置執行 return 語句之后)一刻才執行某個語句或函數(為什么要在返回之后才執行這些語句?因為 return 語句同樣可以包含一些操作,而不是單純地返回某個值)。
```
package main
import "fmt"
func main() {
function1()
}
func function1() {
fmt.Printf("In function1 at the top\n")
defer function2()
fmt.Printf("In function1 at the bottom!\n")
}
func function2() {
fmt.Printf("function2: Deferred until the end of the calling function!")
}
```
輸出
```
In Function1 at the top
In Function1 at the bottom!
Function2: Deferred until the end of the calling function!
```
```
func returnValues() int {
var result int
defer func() {
result++
}()
return result // 0
}
func namedReturnValues() (result int) {
defer func() {
result++
}()
return result // 1
}
```
先執行 return,確定返回值為 0,之后執行 defer,變量 result++,因為非命名返回值所以此時修改的變量并沒有賦值給返回值,所以返回的仍然是 0;而方法二是命名返回值,所以一直都在操作返回值,所以返回1
將函數作為返回值
```
package main
import "fmt"
func main() {
var f = Adder()
fmt.Print(f(1), " - ") // 1 -
fmt.Print(f(20), " - ") // 1 - 21 -
fmt.Print(f(300)) // 1 - 21 - 321
}
func Adder() func(int) int {
var x int
return func(delta int) int {
x += delta
return x
}
}
```
三次調用函數 f 的過程中函數 Adder () 中變量 delta 的值分別為:1、20 和 300。
我們可以看到,在多次調用中,變量 x 的值是被保留的,即 0 + 1 = 1,然后 1 + 20 = 21,最后 21 + 300 = 321:閉包函數保存并積累其中的變量的值,不管外部函數退出與否,它都能夠繼續操作外部函數中的局部變量。
**defer執行順序**
當一個方法中有多個defer時, defer會將要延遲執行的方法“壓棧”,當defer被觸發時,將所有“壓棧”的方法“出棧”并執行。所以defer的執行順序是LIFO。
```
func stackingDefers() {
defer func() {
fmt.Println("1")
}()
defer func() {
fmt.Println("2")
}()
defer func() {
fmt.Println("3")
}()
}
```
輸出:3 2 1