go學習筆記-函式
函式
定義
格式
func function_name( [parameter list] ) [return_types] {
函式體
}
解析
- func:函式由 func 開始宣告
- function_name:函式名稱,函式名和引數列表一起構成了函式簽名。
- parameter list:引數列表,引數就像一個佔位符,當函式被呼叫時,你可以將值傳遞給引數,這個值被稱為實際引數。引數列表指定的是引數型別、順序、及引數個數。引數是可選的,也就是說函式也可以不包含引數。
- return_types:返回型別,函式返回一列值。return_types 是該列值的資料型別。有些功能不需要返回值,這種情況下 return_types 不是必須的。
- 函式體:函式定義的程式碼集合。
例如
func testFunc(a int, b int) (int, int) {
return a + b, a - b
}
變參
func testFunc1(a ...int) {
fmt.Println(a)
}
傳值與傳指標
當我們傳一個引數值到被呼叫函式裡面時,實際上是傳了這個值的一份copy,當在被呼叫函式中修改引數值的時候,呼叫函式中相應實參不會發生任何變化,因為數值變化只作用在copy上。
package main import "fmt" //簡單的一個函式,實現了引數+1的操作 func add1(a int) int { a = a+1 // 我們改變了a的值 return a //返回一個新值 } func main() { x := 3 fmt.Println("x = ", x) // 應該輸出 "x = 3" x1 := add1(x) //呼叫add1(x) fmt.Println("x+1 = ", x1) // 應該輸出"x+1 = 4" fmt.Println("x = ", x) // 應該輸出"x = 3" }
這就牽扯到了所謂的指標。我們知道,變數在記憶體中是存放於一定地址上的,修改變數實際是修改變數地址處的記憶體。只有add1函式知道x變數所在的地址,才能修改x變數的值。所以我們需要將x所在地址&x傳入函式,並將函式的引數的型別由int改為*int,即改為指標型別,才能在函式中修改x變數的值。此時引數仍然是按copy傳遞的,只是copy的是一個指標。
package main import "fmt" //簡單的一個函式,實現了引數+1的操作 func add1(a *int) int { // 請注意, *a = *a+1 // 修改了a的值 return *a // 返回新值 } func main() { x := 3 fmt.Println("x = ", x) // 應該輸出 "x = 3" x1 := add1(&x) // 呼叫 add1(&x) 傳x的地址 fmt.Println("x+1 = ", x1) // 應該輸出 "x+1 = 4" fmt.Println("x = ", x) // 應該輸出 "x = 4" }
channel,slice,map這三種類型的實現機制類似指標,所以可以直接傳遞,而不用取地址後傳遞指標。(注:若函式需改變slice的長度,則仍需要取地址傳遞指標)
函式作為值、型別
函式也是一種變數,我們可以通過type來定義它,它的型別就是所有擁有相同的引數,相同的返回值的一種型別
type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...])
函式作為型別到底有什麼好處呢?那就是可以把這個型別的函式當做值來傳遞,請看下面的例子
package main
import "fmt"
type testInt func(int) bool // 聲明瞭一個函式型別
func isOdd(integer int) bool {
if integer%2 == 0 {
return false
}
return true
}
func isEven(integer int) bool {
if integer%2 == 0 {
return true
}
return false
}
// 宣告的函式型別在這個地方當做了一個引數
func filter(slice []int, f testInt) []int {
var result []int
for _, value := range slice {
if f(value) {
result = append(result, value)
}
}
return result
}
func main(){
slice := []int {1, 2, 3, 4, 5, 7}
fmt.Println("slice = ", slice)
odd := filter(slice, isOdd) // 函式當做值來傳遞了
fmt.Println("Odd elements of slice are: ", odd)
even := filter(slice, isEven) // 函式當做值來傳遞了
fmt.Println("Even elements of slice are: ", even)
}
main函式和init函式
Go裡面有兩個保留的函式:init函式(能夠應用於所有的package)和main函式(只能應用於package main)。這兩個函式在定義時不能有任何的引數和返回值。雖然一個package裡面可以寫任意多個init函式,但這無論是對於可讀性還是以後的可維護性來說,我們都強烈建議使用者在一個package中每個檔案只寫一個init函式。
Go程式會自動呼叫init()和main(),所以你不需要在任何地方呼叫這兩個函式。每個package中的init函式都是可選的,但package main就必須包含一個main函式。
程式的初始化和執行都起始於main包。如果main包還匯入了其它的包,那麼就會在編譯時將它們依次匯入。有時一個包會被多個包同時匯入,那麼它只會被匯入一次(例如很多包可能都會用到fmt包,但它只會被匯入一次,因為沒有必要匯入多次)。當一個包被匯入時,如果該包還匯入了其它的包,那麼會先將其它包匯入進來,然後再對這些包中的包級常量和變數進行初始化,接著執行init函式(如果有的話),依次類推。等所有被匯入的包都載入完畢了,就會開始對main包中的包級常量和變數進行初始化,然後執行main包中的init函式(如果存在的話),最後執行main函式。