Go 学习笔记5 - 函数 |Go主题月

394 阅读3分钟

1.概述

函数是非常重要的,我们写程序会把能复用的代码提取到一个函数中,方便下次复用,所以一个函数最好只做一件事情。在 golang 中函数是一等公民,可以定义为变量的类型、能够在函数参数中传递等。

2.声明

函数包括函数名、形式参数列表、返回值列表(可省略)以及函数体。

func name(parameter-list) (result-list) {
    body
}

形式参数列表:参数名和参数类型。

返回值列表:参数名和参数类型,无返回值是可省略。可以定义多个返回值(这点和其他语言不同)

func add(x, y int) int {
  return x + y
}
fmt.Println(add(3,4)) // 7

x 和 y 是行参名,3和4是调用时的传入的实参,函数返回一个 int 的值。返回值可以是命名的,命名的返回值是一个被初始化类型零值的局部变量。

func add(x, y int) (res int) { // 命名返回值,括号不能省略
  res = x + y // res 是一个局部变量
  return  // 命名返回值时,直接使用 return 即可
}
fmt.Println(add(3,4)) // 7

形参和返回值有相同类型的,不需要为每一个形参和返回值定义类型。 "_" 代表忽略,未被使用的变量。

func f(i, j, k int, s, t string) (z,x int) {/* ... */}

func add(x int, y int) int   {return x + y}
func sub(x, y int) (z int)   { z = x - y; return}
func first(x int, _ int) int { return x }
func zero(int, int) int      { return 0 }

fmt.Printf("%T\n", add)   // "func(int, int) int"
fmt.Printf("%T\n", sub)   // "func(int, int) int"
fmt.Printf("%T\n", first) // "func(int, int) int"
fmt.Printf("%T\n", zero)  // "func(int, int) int"

实参:调用者传递给参数的值。实参是值传递,所以形参是实参的拷贝。对形参进行修改不会影响实参。但是,如果实参包括引用类型,如指针,slice、map、function、channel等类型,实参可能会由于函数的间接引用被修改。

3.可变参数

参数数量可变的被称为可变参数。使用 "..." 表示,同时可变参数只能被放在形参列表最后。

func sum(vals ...int) int {
    total := 0
    for _, val := range vals {
        total += val
    }
    return total
}

sum 函数返回任意个 int 参数的和。在函数体中,vals被看作是类型为[] int的切片。sum 可以接收任意数量的int型参数:

fmt.Println(sum())           // "0"
fmt.Println(sum(3))          // "3"
fmt.Println(sum(1, 2, 3, 4)) // "10"

在上面的代码中,调用者隐式的创建一个数组,并将原始参数复制到数组中,再把数组的一个切片作为参数传给被调用函数。如果原始参数已经是切片类型,参数后使用"..."即可:

values := []int{1, 2, 3, 4}
fmt.Println(sum(values...)) // "10"

4.匿名函数与闭包

package main

import "fmt"

func main() {
    f := closure(10)
    fmt.Println(f(1))
    fmt.Println(f(2))
}

func closure(x int) func(int) int {
    fmt.Printf("%p\n", &x)
    return func(y int) int {
      fmt.Printf("%p\n", &x)
      return x + y
    }
}

返回:

0xc420076008
0xc420076008
11
0xc420076008
12

变量 f 的值是一个函数,里面保存着对变量 x 的引用,形成了闭包。可以想象成 f 中存了 x 的地址。

6.defer 用法

go 的 defer 会在当前函数返回前调用其后的函数。一般被用来做资源释放关闭、 panic recover等

package main

import (
    "fmt"
    "os"
)

var sum int

func main() {
    open, err := os.Open("file.txt")
    // 先判断错误,打开失败就不需要 close
    if err != nil {
        fmt.Println(err)
        return
    }
    defer open.Close()
}

defer 会延迟调用后面的函数:

func main() {
    for i := 0; i < 3; i++ {
        defer fmt.Println(i)
    }
}
// outut:
// 2
// 1
// 0

7.panic 与 recover

package main

import "fmt"

func main() {
    A()
    B()
    C()
}

func A() {
	fmt.Println("func A")
}

func B() {
    // 此处defer的匿名函数要注册在panic之前,否则程序还未执行到该函数就终止了
    defer func() {
    	if err := recover(); err != nil {
            fmt.Println("recover in B")
    	}
    }()
    panic("panic in B")
}

func C() {
    fmt.Println("func C")
}