4.1-函数

函数

函数的基本形式

//函数定义。a,b是形参
func argf(a int, b int) { 
    a = a + b 
}
var x, y int = 3, 6
argf(x, y) //函数调用。x,y是实参
  • 形参是函数内部的局部变量,实参的值会拷贝给形参。
  • 函数定义时的第一个的大括号不能另起一行。
  • 形参可以有0个或多个。
  • 参数类型相同时可以只写一次,比如argf(a,b int)。
  • 在函数内部修改形参的值,实参的值不受影响。
  • 如果想通过函数修改实参,就需要指针类型。
func argf(a, b *int) { 
    *a = *a + *b
    *b = 888
}
var x, y int = 3, 6
argf(&x, &y)

  slice、map、channel都是引用类型,它们作为函数参数时其实跟普通struct没什么区别,都是对struct内部的各个字段做一次拷贝传到函数内部。

package main

import "fmt"

func slice_arg_1(arr []int) { //slice作为参数,实际上是把slice的arrayPointer、len、cap拷贝了一份传进来
    arr[0] = 1           //修改底层数据里的首元素
    arr = append(arr, 1) //arr的len和cap发生了变化,不会影响实参
}

func main() {
    arr := []int{8}
    slice_arg_1(arr)
    fmt.Println(arr[0])   //1
    fmt.Println(len(arr)) //1
}

关于函数返回值

  • 可以返回0个或多个参数。
  • 可以在func行直接声明要返回的变量。
  • return后面的语句不会执行。
  • 无返回参数时return可以不写。
func returnf(a, b int) (c int) { //返回变量c已经声明好了
    a = a + b
    c = a //直接使用c
    return //由于函数要求有返回值,即使给c赋过值了,也需要显式写return
}

  不定长参数实际上是slice类型。

func variable_ength_arg(a int, other ...int) int { 
    sum := a
    for _, ele := range other {//不定长参数实际上是slice类型
        sum += ele
    }
    fmt.Printf("len %d cap %d\n", len(other), cap(other))
    return sum
}
variable_ength_arg(1)
variable_ength_arg(1,2,3,4)

  append函数接收的就是不定长参数。

arr = append(arr, 1, 2, 3)
arr = append(arr, 7)
arr = append(arr)
slice := append([]byte("hello "), "world"...) //...自动把"world"转成byte切片,等价于[]byte("world")...
slice2 := append([]rune("hello "), []rune("world")...) //需要显式把"world"转成rune切片

  在很多场景下string都隐式的转换成了byte切片,而非rune切片,比如"a中"[1]是228而非"中"。
递归函数

func Fibonacci(n int) int {
    if n == 0 || n == 1 {
        return n //凡是递归,一定要有终止条件,否则会进入无限循环
    }
    return Fibonacci(n-1) + Fibonacci(n-2) //递归调用函数自身
}

匿名函数

  函数也是一种数据类型。

func function_arg1(f func(a, b int) int, b int) int { //f参数是一种函数类型
    a := 2 * b
    return f(a, b)
}

type foo func(a, b int) int //foo是一种函数类型
func function_arg2(f foo, b int) int { //参数类型看上去简洁多了
    a := 2 * b
    return f(a, b)
}

type User struct {
    Name string
    bye foo //bye的类型是foo,而foo代表一种函数类型
    hello func(name string) string //使用匿名函数来声明struct字段的类型
}

ch := make(chan func(string) string, 10)
ch <- func(name string) string {  //使用匿名函数
    return "hello " + name
}

闭包

  闭包(Closure)是引用了自由变量的函数,自由变量将和函数一同存在,即使已经离开了创造它的环境。闭包复制的是原对象的指针。

package main

import "fmt"

//闭包(Closure)是引用了自由变量的函数。自由变量将和函数一同存在,即使已经离开了创造它的环境。
func sub() func() {
    i := 10
    fmt.Printf("%p\n", &i)
    b := func() {
        fmt.Printf("i addr %p\n", &i) //闭包复制的是原对象的指针
        i--                           //b函数内部引用了变量i
        fmt.Println(i)
    }
    return b //返回了b函数,变量i和b函数将一起存在,即使已经离开函数sub()
}

// 外部引用函数参数局部变量
func add(base int) func(int) int {
    return func(i int) int {
        fmt.Printf("base addr %p\n", &base)
        base += i
        return base
    }
}

func main() {
    b := sub()
    b()
    b()
    fmt.Println()

    tmp1 := add(10)
    fmt.Println(tmp1(1), tmp1(2)) //11,13
    // 此时tmp1和tmp2不是一个实体了
    tmp2 := add(100)
    fmt.Println(tmp2(1), tmp2(2)) //101,103
}

延迟调用defer

  • defer用于注册一个延迟调用(在函数返回之前调用)。
  • defer典型的应用场景是释放资源,比如关闭文件句柄,释放数据库连接等。
  • 如果同一个函数里有多个defer,则后注册的先执行。
  • defer后可以跟一个func,func内部如果发生panic,会把panic暂时搁置,当把其他defer执行完之后再来执行这个。
  • defer后不是跟func,而直接跟一条执行语句,则相关变量在注册defer时被拷贝或计算。
func basic() {
    fmt.Println("A")
    defer fmt.Println(1) fmt.Println("B")
    //如果同一个函数里有多个defer,则后注册的先执行
    defer fmt.Println(2)
    fmt.Println("C")
}
func defer_exe_time() (i int) {
    i = 9
    defer func() { //defer后可以跟一个func
        fmt.Printf("first i=%d\n", i) //打印5,而非9。充分理解“defer在函数返回前执行”的含义,不是在“return语句前执行defer”
    }()
    defer func(i int) {
        fmt.Printf("second i=%d\n", i) //打印9
    }(i)
    defer fmt.Printf("third i=%d\n", i) //defer后不是跟func,而直接跟一条执行语句,则相关变量在注册defer时被拷贝或计算
    return 5
}

异常处理

  go语言没有try catch,它提倡返回error。

func divide(a, b int) (int, error) {
    if b == 0 {
        return -1, errors.New("divide by zero")
    }
    return a / b, nil
}
if res, err := divide(3, 0); err != nil {//函数调用方判断error是否为nil
    fmt.Println(err.Error())
}

  Go语言定义了error这个接口,自定义的error要实现Error()方法。

type PathError struct {    //自定义error
    path string
    op string
    createTime string
    message string
}
func (err PathError) Error() string {    //error接口要求实现Error() string方法
    return err.createTime + ": " + err.op + " " + err.path + " " + err.message
}

何时会发生panic:

  • 运行时错误会导致panic,比如数组越界、除0。
  • 程序主动调用panic(error)。

panic会执行什么:

  1. 逆序执行当前goroutine的defer链(recover从这里介入)。
  2. 打印错误信息和调用堆栈。
  3. 调用exit(2)结束整个进程。

  recover会使程序从panic中恢复,并返回panic value。recover所在的函数后续的代码不会执行,但函数可以正常返回。在未发生panic时调用recover,会返回nil。recover()必须在defer中才能生效。

func soo() {
    fmt.Println("enter soo")

    defer func() { //去掉这个defer试试,看看panic的流程。把这个defer放到soo函数末尾试试。把这个defer移到main()里试试。
        //recover必须在defer中才能生效
        if panic_value := recover(); panic_value != nil {
            fmt.Printf("soo函数中发生了panic:%v\n", panic_value)
        }
    }()
    fmt.Println("regist recover")

    defer fmt.Println("hello")
    defer func() {
        n := 0
        _ = 3 / n //除0异常,发生panic,下一行的defer没有注册成功
        defer fmt.Println("how are you")
    }()
}
package main

import (
    "fmt"
)

func B() {
    // defer func() { //方式一,recover()在B()函数里,则在B()函数中panic后面的代码不会执行。不影响BBBBBBB的打印
    //  if panicValue := recover(); panicValue != nil {
    //      fmt.Printf("panic info %v\n", panicValue)
    //  }
    // }()
    panic(5)
}

func main() {
    defer func() { //方式二,recover()在main()函数里,则在main()函数中panic后面的代码不会执行。BBBBBBB不会打印出来
        if panicValue := recover(); panicValue != nil {
            fmt.Printf("panic info %v\n", panicValue)
        }
    }()

    B()
    fmt.Println("BBBBBBB")
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 函数 Golang函数特点 无需声明原型支持多返回值不定参数传参 也就是函数的参数个数不是固定的 但是后面的类型是...
    TZX_0710阅读 668评论 0 0
  • 函数包含函数名、行参列表、函数体和返回值列表,使用func进行声明,函数无参数或返回值时则形参列表和返回值列表省略...
    imsilence阅读 211评论 0 0
  • 函数的声明 在 Go 语言中,函数声明通用语法如下: 1.函数的声明以关键词 func开始 。 函数名和参数列表一...
    _羊羽_阅读 1,737评论 1 4
  • Go 函数 函数声明 函数声明包括函数名、形式参数列表、返回值列表(可省略)以及函数体。 func 函数名(形式参...
    Lyi_Zri阅读 821评论 0 3
  • 函数 不支持 嵌套 (nested)、重载 (overload) 和 默认参数 (default paramete...
    技术学习阅读 510评论 0 0