1. 程式人生 > >go學習筆記-函式

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函式。