golang-学习笔记-函数
在写过很多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 的话,则认为它们是相同的函数。
在函数中定义函数必须使用匿名函数
|
|
如果要定义递归匿名函数:
|
|
函数参数与返回值
可以返回一组值
返回值多个参数可以直接给函数
|
|
按值传递(call by value) 按引用传递(call by reference)
Go 默认使用按值传递来传递参数,也就是传递参数的副本。如果希望函数可以直接修改参数的值,而不是对参数的副本进行操作,需要将参数的地址(变量名前面添加&符号,比如 &variable)传递给函数,这就是按引用传递(译者注:指针也是变量类型,有自己的地址和值,通常指针的值指向一个变量的地址。所以,按引用传递也是按值传递。)
几乎在任何情况下,传递指针(一个32位或者64位的值)的消耗都比传递副本来得少。
切片(slice)、字典(map)、接口(interface)、通道(channel)这样的引用类型都是默认使用引用传递(即使没有显式的指出指针),应该是因为底层结构本来就是一个类似指针的结构。
命名的返回值(named return variables)
即使函数使用了命名返回值,你依旧可以无视它而返回明确的值。
如果有一个返回值是命名的那么所有返回值都得是命名的
尽量使用命名返回值:会使代码更清晰、更简短,同时更加容易读懂。
空白符(blank identifier)
_
空白符用来匹配一些不需要的值,然后丢弃掉
改变外部变量(outside variable)
指针解引用
传递变长参数
变长参数在函数里面就是一个切片
|
|
如果参数被存储在一个数组 arr 中,则可以通过 arr… 的形式来传递参数调用变参函数。
|
|
使用空接口可以接受任意类型
defer 和追踪
defer语句在函数返回之前(执行 return 语句之后)执行。
具有返回值x的函数return yy时会隐式执行x=yy。defer在其之后执行,并在返回值给调用者前执行
defer函数定义时会类似压栈(实际上是一个defer链表,而且是头插法链表,所以能实现类似栈的先进后调用的顺序),其接收的参数是值传递的,形参的值在压栈的时候已经确定,但是defer函数捕获的外部变量是以地址传递给函数的,即其值不是在定义的时候确定的而是在最后调用defer函数的时候确定的。所以,下面的代码会输出0 1
|
|
当有多个 defer 行为被注册时,它们会以逆序执行(类似栈,即后进先出)
关键字 defer 允许我们进行一些函数执行完成后的收尾工作,例如:
- 关闭文件流
|
|
- 解锁一个加锁的资源
|
|
- 打印最终报告
|
|
- 关闭数据库链接
|
|
使用 defer 语句实现代码追踪
|
|
使用 defer 语句来记录函数的参数与命名返回值(可修改)
|
|
Defer必掌握的7知识点
- 顺序先进后出
- defer在return语句之后执行
- 具名返回值初始化为默认空值,return语句有具体值时在defer之前执行赋值操作
- defer可以修改具名返回值的值
- panic后直接执行现有defer链表里的函数
- defer中panic会覆盖原来的panic,即同一时间只有一个panic
- defer压栈直接压值,当实参有函数的时候就会先调用函数然后把调用结果压栈
defer实现原理
通过groutine的_defer字段实现
type _defer struct
属性
- sp uintptr:函数栈指针
- pc uintptr:程序计数器
- fn *funcval:函数地址
- link *_defer:下一个defer函数
内置函数
递归函数
当一个函数在其函数体内调用自身,则称之为递归。
将函数作为参数
函数可以作为其它函数的参数进行传递,然后在其它函数内调用执行,一般称之为回调。
闭包
|
|
应用闭包:将函数作为返回值
使用闭包调试
包 runtime 中的函数 Caller() 返回当前指向函数的信息,因此可以在需要的时候实现一个 where() 闭包函数来打印函数执行的位置:
|
|
计算函数执行时间
使用 time 包中的 Now() 和 Sub 函数:
|
|
通过内存缓存来提升性能
备忘录优化