函数

定义一个函数

不支持 嵌套 (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关于一些新手不注意会出现的小问题

Q.E.D.


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