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测试

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