Go 函數

函數是構建 Go 程序的主要成分。其定義如下形式:

func (recvarg type) funcname(arg int) (ret1[, ret2] int) { return 0[,0] }

關鍵字 func 用於定義一個函數;recvarg 用於指定此函數可用於的類型,即該類型的方法。funcname 爲函數名;arg 爲函數參數,函數爲值傳遞;ret 爲函數命名返回值,也可以只有類型而不命名,可以有 0 個、1 個或者多個返回值;剩下的爲函數體,左花括號必須在同一行,不能新起一行。

func identity(in int) int {
    return in
}

注意: Go 不允許函數嵌套,然而可以利用匿名函數實現它。Go 的函數可以任意安排定義順序,編譯器會在執行前掃描每個文件,因此 Go 中不需要函數原型。函數可以遞歸調用。

1、作用域

在 Go 中,定義在函數外的變量是全局的,那些定義在函數內部的變量,對於函數來說是局部的。如果命名覆蓋 — 一個局部變量與一個全局變量有相同的名字 — 在函數執行的時候,局部變量將覆蓋全局變量。

2、多值返回

Go 的函數和方法可以返回多個值。多值返回避免了傳遞指針模擬引用參數來返回值。當有多個返回值時需要使用圓括號將返回值括起來。

// 返回數組當前位置的值和下一個位置
func nextInt(b []byte, i int) (int, int) {
    x := 0
    // 假設所有的都是數字
    for ; i < len(b) ; i++ {
        x = x*10 + int(b[i])-'0'
    }
    return x, i
}

3、命名返回值

Go 函數的返回值或者結果參數可以指定一個名字(名字不是強制的),並且像原始的變量那樣使用,就像輸入參數那樣。如果對其命名,在函數開始時,它們會用其類型的零值初始化;如果函數在不加參數的情況下執行了 return 語句,結果參數的當前值會作爲返回值返回。

// 計算斐波那契數列
func Factorial(x int) (result int) {
    if x == 0 {
        result = 1
    } else {
        result = x * Factorial(x - 1)
    }
    return
}

4、匿名函數

匿名函數即沒有函數名的函數,只能放在函數中,可以實現函數嵌套定義的功能。

func f(n int) int {
    sum := 0
    func(x int) {
        for i := 0; i < x; i++ {
            sum += i
        }
    }(n) // 匿名函數,圓括號必須,也可以沒有參數

    return sum
}

5、延遲代碼執行

關鍵字 defer可以延遲函數的執行。調用函數時,在函數前加 defer 關鍵字,可以延遲該函數執行,在 defer 後指定的函數會在函數退出前調用。

func ReadWrite() bool {
    file.Open("file")
    defer file.Close() // close() 函數加入延遲列表
    // 做一些工作
    if failureX {
        return false // 若此處退出,Close() 現在自動調用
    }
    if failureY {
        return false // 這裏也是
    }
    return true
}

可以將多個函數放入“延遲列表”中,延遲的函數是按照後進先出( LIFO)的順序執行,因此,下面函數模擬反序輸出:

for i := 0; i < 5; i++ {
    defer fmt.Printf("%d ", i)
}

// 輸出爲:4 3 2 1 0

利用 defer 甚至可以修改返回值。

func f() (ret int) { // ret 初始化爲零
    defer func() {
        ret++  // ret 增加爲 1
    }() // 匿名函數,括號必須的
    return 0 // 返回的是 1 而不是 0!先執行 return
}

6、變參

接受變參的函數是有着不定數量的參數的。其函數定義形式如下:

func funcname(arg ... type) { }

arg ... type 告訴 Go 這個函數接受不定數量的參數。注意,在函數體中,變量 arg 是一個type類型的 slice,可以使用 range 遍歷,也可以將其作爲實參全部或者部分傳遞給調用函數。

for _, n := range arg {
    fmt.Println(n)
}

func myfunc(arg ... int) {
    myfunc2(arg...)  // 按原樣傳遞
    myfunc2(arg[:2]...) //傳遞部分
}

如果不指定變參的類型,默認是空的接口 interface{}

7、函數作爲值

就像其他在 Go 中的其他東西一樣,函數也是值而已。它們可以像下面這樣賦值給變量:

func main() {
    a := func() { // 定義一個匿名函數,並且賦值給 a
        fmt.Println("Hello")
    } //這裏沒有 ()
    a() //調用函數
}

函數作爲值也可以用於其他一些地方,如 map

var xs = map[int]func() int{
    1: func() int { return 10 },
    2: func() int { return 20 },
    3: func() int { return 30 }, ← 必須有逗號
    /* ... */
}

8、回調函數

函數也可以作爲另一個函數的參數,即回調函數。回調函數需要調用函數的形參格式和被調用函數原型相同。

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
}

// 聲明的函數類型在這個地方當做了一個參數
// 也可以將第二個形參寫成 f func(int) bool
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}
    odd := filter(slice, isOdd) // 函數當做值來傳遞了
    even := filter(slice, isEven) // 函數當做值來傳遞了
    /*...*/
}

9、命令行參數

關於命令行參數的幾個概念:

  • 命令行參數(或參數):是指運行程序時提供的參數。
  • 已定義命令行參數:是指程序中通過 flag.Xxx 等這種形式定義了的參數。輸入參數時需要 -flag 形式。
  • 非 flag(non-flag)命令行參數(或保留的命令行參數):不符合 -flag 形式的參數。-----flag 都屬於 non-flag 參數。

來自命令行的參數在程序中通過字符串 slice os.Args 獲取,導入包 os 即可。 flag 包有着精巧的接口,提供瞭解析命令行參數標識的方法。

其中 os.Args[0] 爲執行的程序名,os.Args[1] ~ os.Args[n-1] 是具體的參數。

比如在命令行中運行 test.exe 1 2 3,則 os.Args[0] ~ os.Args[3]的值分別爲 "test.exe" "1" "2" "3"

import (
    "fmt"
    "os"
)

func main() int{
    arg_num := len(os.Args) // 計算參數個數
    fmt.Printf("the num of input is %d\n",arg_num)

    fmt.Printf("they are :\n")
    for i := 0 ; i < arg_num ;i++{
        fmt.Println(os.Args[i]) // 打印命令行參數,參數都字符串
    }
} 

10、flag 包

flag 包實現了對命令行的解析。

定義 flag 通常使用 flag.Xxx() 形式,其中 Xxx 是對應類型的首字母大寫形式,可以是 Int、String等;返回一個相應類型的指針,如:

package main
/**flagtest.go**/
import (
    "flag"
    "fmt"
)

func main() {
    // 定義 -H 參數
    shelp := flag.Int("H", 1234, "help message for flagname")

    flag.Parse() // 解析參數,必須有
    fmt.Println(*shelp)
}

假設在命令行中運行帶有此參數的程序 flagtest.go,用如下形式:

go run flagtest.go -H 666

則輸出爲 666。

如果不帶參數,

go run flagtest.go

則輸出默認值 1234。

若格式錯誤,即以如下形式運行,

go run flagtest.go -H

則會輸入提示字符串:help message for flagname。

flag 語法

命令行 flag 的語法有如下三種形式:

-flag    // 只支持 bool 類型
-flag=x                     // 等號前後沒有空格
-flag x  // 只支持非 bool 類型

int 類型可以是十進制、十六進制、八進制甚至是負數;bool 類型可以是 1, 0, t, f, true, false, TRUE, FALSE, True, False。對於 bool 類型,如果不是採用 -flag=x的形式指定確切的值, 如果命令行中有該選項,無論默認值是 true 還是 false,結果都是 true,否則,如果無此選項,結果爲默認值。

/**booltest.go**/
func main() {
    // 定義 -B 參數,類型爲 bool
    fbool := flag.Bool("B", true, "without is false")

    flag.Parse() // 解析參數,必須有
    fmt.Println(*fbool)
}

在命令行中以如下形式運行輸出爲 false。

go run booltest.go -B=false

以如下形式運行輸出爲 true。

go run booltest.go -B // 非 bool 類型以此格式爲錯誤格式,會輸出提示
go run booltest.go -B=true

參數解析

在使用 flag 參數前,必須調用 flag.Parse() 對參數進行解析。

Parse() 只解析已定義的命令行參數,即(-flag 參數),當遇到第一個 non-flag 參數時停止解析。單獨的 --- 都不是 flag 參數。因此,flag 參數必須出現在命令行 non-flag 參數之前,否則無法解析。

當命令行中對 flag 參數指定值時,則參數將採用指定的值;如果不指定 flag 參數或者參數不是處在 non-falg 之前,則採用默認值;如果參數格式錯誤,將無法解析而輸出提示信息,即 flag.Xxx 函數的第三個參數。

/** parse.go **/
func main(){
    af := flag.Int("I", 2, "int")
    bf := flag.String("S", "hello", "string")
    cf := flag.Bool("b", true, "bool")

    flag.Parse()
    fmt.Printf("%v, %s, %v\n", *af,*bf,*cf) 
}

若命令行沒有指定已定義參數,或者 flag 參數出現在 non-falg 參數之後(此時會被當做 non-falg 參數),即輸入爲:

go run parse.go 或者 go run parse.go befor -I 3

輸出默認值: 2, hello, true

若正確輸入,則 flag 參數會採用指定值:

go run parse.go -I 3 -S world -b 或者 go run parse.go -I 3 -b -S world

也可以在 flag 參數後加上 non-flag 參數,即

go run parse.go -I 3 last

則輸出都是:3, world, true

上面實例可以看出,只要 flag 參數格式正確,位置前後順序沒有要求。即 -S world -b-b -S world 結果都一樣。

Arg(i int) 和 Args()、NArg()、NFlag()

Arg(i int)Args() 這兩個方法是獲取 non-flag 參數的;NArg() 獲得 non-flag 個數;NFlag() 獲得命令行中被設置了的 flag 參數的個數。

flag.Args()os.Args() 區別是:os.Args() 包括可執行文件名和所有的參數(flag 參數 和 non-falg 參數),即索引 0 爲可執行文件名, 而 flag.Args() 只包括 non-flag 參數,且索引 0 爲命令行中第一個non-flag 參數

/** flagtest.go **/
func main(){
    _ = flag.Int("I", 2, "int") // 定義三個 flag 參數
    _ = flag.String("S", "hello", "string")
    _ = flag.Bool("B", true, "bool")

    flag.Parse()

    for _, val := range os.Args{ // os.Args 獲取所有的命令行參數
        fmt.Printf("%s ", val)
    }
    fmt.Printf("\n")

    fmt.Println(flag.NFlag()) // NFlag() 獲取命令行中設置了的 flag 參數個數

    for _, val := range flag.Args(){ // 使用 flag.Ags() 獲取 non-flag 參數
        fmt.Printf("%v ", val)
    }
    fmt.Printf("\n")

    for i := 0; i < flag.NArg(); i++{  // 計算 non-flag 參數個數
        fmt.Printf("%v ", flag.Arg(i)) // 使用索引獲取 non-flag 參數
    }
}

命令行中運行結果爲:

flagtest.go測試

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