Contents

golang-学习笔记-函数

go官方文档 go语言参考

在写过很多go代码之后,感觉自己并没有完全掌握go语言,还有很多知识盲区,所以有了这个go学习笔记系列,本系列是作者跟着电子书重新复习go语言相关内容的笔记

介绍

Go 里面有三种类型的函数

  • 普通的带有名字的函数
  • 匿名函数或者lambda函数
  • 方法 Methods

如果需要申明一个在外部定义的函数,你只需要给出函数名与函数签名,不需要给出函数体:

func flushICache(begin, end uintptr) // implemented externally

函数也可以以申明的方式被使用,作为一个函数类型,就像:

type binOp func(int, int) int

函数是一等值(first-class value):它们可以赋值给变量,就像 add := binOp 一样。

这个变量知道自己指向的函数的签名,所以给它赋一个具有不同签名的函数值是不可能的。

函数值(functions value)之间可以相互比较:如果它们引用的是相同的函数或者都是 nil 的话,则认为它们是相同的函数。

在函数中定义函数必须使用匿名函数

1
2
3
4
fn:=func () {
  ...
  fn()
}

CSDN 「已注销」 Go语言匿名函数的递归写法

如果要定义递归匿名函数:

1
2
3
4
5
6
7
var fn func()
fn=func (){
  ...
  fn()
  ...
}
...

函数参数与返回值

可以返回一组值

返回值多个参数可以直接给函数

1
2
3
4
5
6
7
func a() (a,b int){
  return 1,2
}

func b(c,d int){}

b(a(1,2))

按值传递(call by value) 按引用传递(call by reference)

Go 默认使用按值传递来传递参数,也就是传递参数的副本。如果希望函数可以直接修改参数的值,而不是对参数的副本进行操作,需要将参数的地址(变量名前面添加&符号,比如 &variable)传递给函数,这就是按引用传递(译者注:指针也是变量类型,有自己的地址和值,通常指针的值指向一个变量的地址。所以,按引用传递也是按值传递。)

几乎在任何情况下,传递指针(一个32位或者64位的值)的消耗都比传递副本来得少。

切片(slice)、字典(map)、接口(interface)、通道(channel)这样的引用类型都是默认使用引用传递(即使没有显式的指出指针),应该是因为底层结构本来就是一个类似指针的结构。

命名的返回值(named return variables)

即使函数使用了命名返回值,你依旧可以无视它而返回明确的值。

如果有一个返回值是命名的那么所有返回值都得是命名的

尽量使用命名返回值:会使代码更清晰、更简短,同时更加容易读懂。

空白符(blank identifier)

_空白符用来匹配一些不需要的值,然后丢弃掉

改变外部变量(outside variable)

指针解引用

传递变长参数

变长参数在函数里面就是一个切片

1
func myFunc(a, b, arg ...int) {}

如果参数被存储在一个数组 arr 中,则可以通过 arr… 的形式来传递参数调用变参函数。

1
2
arr := []int{7,9,3,5,1}
x = min(arr...)

使用空接口可以接受任意类型

defer 和追踪

defer语句在函数返回之前(执行 return 语句之后)执行。

具有返回值x的函数return yy时会隐式执行x=yy。defer在其之后执行,并在返回值给调用者前执行

defer函数定义时会类似压栈(实际上是一个defer链表,而且是头插法链表,所以能实现类似栈的先进后调用的顺序),其接收的参数是值传递的,形参的值在压栈的时候已经确定,但是defer函数捕获的外部变量是以地址传递给函数的,即其值不是在定义的时候确定的而是在最后调用defer函数的时候确定的。所以,下面的代码会输出0 1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main

import "fmt"

func main() {
	param, capture := 0, 0
	defer func(p int) {
		fmt.Println(p,capture)
	}(param)
	param, capture = 1, 1
}

当有多个 defer 行为被注册时,它们会以逆序执行(类似栈,即后进先出)

关键字 defer 允许我们进行一些函数执行完成后的收尾工作,例如:

  1. 关闭文件流
1
2
// open a file  
defer file.Close()
  1. 解锁一个加锁的资源
1
2
mu.Lock()  
defer mu.Unlock()
  1. 打印最终报告
1
2
printHeader()  
defer printFooter()
  1. 关闭数据库链接
1
2
// open a database connection  
defer disconnectFromDB()

使用 defer 语句实现代码追踪

1
2
3
4
5
6
7
8
func trace(s string) { fmt.Println("entering:", s) }
func untrace(s string) { fmt.Println("leaving:", s) }

func a() {
    trace("a")
    defer untrace("a")
    fmt.Println("in a")
}

使用 defer 语句来记录函数的参数与命名返回值(可修改)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import (
    "io"
    "log"
)

func func1(s string) (n int, err error) {
    defer func() {
        log.Printf("func1(%q) = %d, %v", s, n, err)
    }()
    return 7, io.EOF
}

func main() {
    func1("Go")
}

Defer必掌握的7知识点

go修养之路 Defer必掌握的7知识点

  1. 顺序先进后出
  2. defer在return语句之后执行
  3. 具名返回值初始化为默认空值,return语句有具体值时在defer之前执行赋值操作
  4. defer可以修改具名返回值的值
  5. panic后直接执行现有defer链表里的函数
  6. defer中panic会覆盖原来的panic,即同一时间只有一个panic
  7. defer压栈直接压值,当实参有函数的时候就会先调用函数然后把调用结果压栈

defer实现原理

go专家编程

通过groutine的_defer字段实现

https://static.sitestack.cn/projects/GoExpertProgramming/chapter02/images/defer-01-defer_stack.png

type _defer struct

属性

  • sp uintptr:函数栈指针
  • pc uintptr:程序计数器
  • fn *funcval:函数地址
  • link *_defer:下一个defer函数

内置函数

见原文

递归函数

当一个函数在其函数体内调用自身,则称之为递归。

将函数作为参数

函数可以作为其它函数的参数进行传递,然后在其它函数内调用执行,一般称之为回调。

闭包

1
fn:=func(参数) 返回值 { 函数体 }

应用闭包:将函数作为返回值

使用闭包调试

包 runtime 中的函数 Caller() 返回当前指向函数的信息,因此可以在需要的时候实现一个 where() 闭包函数来打印函数执行的位置:

1
2
3
4
where := func() {
    _, file, line, _ := runtime.Caller(1)
    log.Printf("%s:%d", file, line)
}

计算函数执行时间

使用 time 包中的 Now() 和 Sub 函数:

1
2
3
4
5
start := time.Now()
longCalculation()
end := time.Now()
delta := end.Sub(start)
fmt.Printf("longCalculation took this amount of time: %s\n", delta)

通过内存缓存来提升性能

备忘录优化

 |