函数

定义一个函数

不支持 嵌套 (nested)、重载 (overload) 和 默认参数 (default parameter)。

  • 无需声明原型。(与C不同)
  • 支持不定长变参。(参数列表可不定长)
  • 支持多返回值。
  • 支持命名返回参数。
  • 支持匿名函数和闭包。

使用关键字 func 定义函数,左大括号依旧不能另起一行。

func test(x, y int, s string) (int, string) { // 类型相同的相邻参数可合并,多返回值必须用括号。
    n := x + y 
    return n, fmt.Sprintf(s, n)
}

函数是一种数据类型

Let’s Go 2 — 类型与字符串中的类型列表中提到,函数是一种数据类型(与java不同)

因此,函数可以作为参数,作为变量传递

package main

import "fmt"

func test(fn func() int) int {//函数可以作为变参
    return fn()
}

type FormatFunc func(s string, x, y int) string // 定义函数类型。

func format(fn FormatFunc, s string, x, y int) string {
    return fn(s, x, y)
}

func main() {
    s1 := test(func() int { return 100 }) // 直接将匿名函数当参数。

    s2 := format(func(s string, x, y int) string {
        return fmt.Sprintf(s, x, y)
    }, "%d, %d", 10, 20)

    fmt.Println(s1, s2)
}

变参

变参本质上就是 slice。只能有一个,且必须是最后一个。(和java一样)

func test(s string, n ...int){
    //本质上是个slice,可用range遍历
    for _,v := range n{
        fmt.Println(s,v)
    }
}

使用 slice 对象做变参时,必须展开。

package main

import "fmt"

func main() {
    test(1,2,3)
    s := []int{1,2,3}
    //test(s) 报错,必须展开,这一点和java一样
}

func test(n ...int){
    for _,v := range n{
        fmt.Println(v)
    }
}

返回值

不能用容器对象接收多返回值。只能用多个变量,或 "_" 忽略。

func test() (int, int) {
    return 1, 2
}

func main() {
    // s := make([]int, 2)
    // s = test() // Error: multiple-value test() in single-value context

    x, _ := test()
    println(x)
}

多返回值可直接作为其他函数调用实参。

func test() (int,int){
    return 1,2
}

func add(x, y int) int {
    return x + y
}

func sum(n ...int) int {
    var x int
    for _, i := range n {
        x += i
    }

    return x
}

func main() {
    //多返回值函数可以直接用作参数
    println(add(test()))
    println(sum(test()))
}

命名返回参数可看做与形参类似的局部变量,最后由 return 隐式返回。

func add(x, y int) (z int) {
    z = x + y
    return //返回z
}

func main() {
    println(add(1, 2))
}

命名返回参数可被同名局部变量遮蔽,此时需要显式返回。

func add(x, y int) (z int) {
    { // 不能在一个级别,引发 "z redeclared in this block" 错误。
        var z = x + y
        // return // Error: z is shadowed during return
        return z // 必须显式返回。
    }
}

命名返回参数允许 defer 延迟调用通过闭包读取和修改。

func add(x, y int) (z int) {
    defer func() {
        z += 100
    }()

    z = x + y
    return
}

func main() {
    println(add(1, 2)) // 输出: 103
}

显式 return 返回前,会先修改命名返回参数。

func add(x, y int) (z int) {
    defer func() {
        println(z) // 输出: 203
    }()

    z = x + y
    return z + 200 // 执⾏行顺序: (z = x + y) -> (z = z + 200) -> (call defer) -> (return)
}

func main() {
    println(add(1, 2)) // 输出: 203
}

匿名函数

匿名函数可赋值给变量,做为结构字段,或者在 channel 里传送。

// --- 函数作为变量 ---

fn := func() { println("Hello, World!") }
fn()

// --- 函数数组 ---

fns := [](func(x int) int){
    func(x int) int { return x + 1 },
    func(x int) int { return x + 2 },
}

println(fns[0](100))

// --- 函数作为属性 ---

d := struct {
    fn func() string
}{
    fn: func() string { return "Hello, World!" },
}

println(d.fn())

// --- 函数管道 ---

fc := make(chan func() string, 2)
fc <- func() string { return "Hello, World!" }
println((<-fc)())

延迟引用现象

闭包复制的是原对象指针!!

func main(){
    i, n := 1 ,2
    // defer:编译仍然从上到下,但会放下面执行
    defer func(a int){
        fmt.Println("defer:", a , n) //n被闭包引用,引用的是指针,所以后续的修改对此处有影响
    }(i) //复制i的值,此时i为1
    i , n = i+1,n+2
    fmt.Println(i , n)
}
/*
    输出
    2 4
    defer: 1 4
*/

参考

Go 边看变练

golang关于一些新手不注意会出现的小问题


此 生 无 悔 恋 真 白 ,来 世 愿 入 樱 花 庄 。