defer用于资源的释放,会在函数返回之前进行调用。如果有多个defer表达式,调用顺序类似于栈,越后面的defer表达式越先被调用。
defer的触发时机
- 包裹着defer语句的函数返回时
- 包裹着defer语句的函数执行到最后时
-
当前goroutine发生Panic时
//输出结果:return前执行defer
func f1() {
defer fmt.Println("return前执行defer")
return
}
//输出结果:函数执行
// 函数执行到最后
func f2() {
defer fmt.Println("函数执行到最后")
fmt.Println("函数执行")
}
//输出结果:panic前 第一个defer在Panic发生时执行,第二个defer在Panic之后声明,不能执行到
func f3() {
defer fmt.Println("panic前")
panic("panic中")
defer fmt.Println("panic后")
}
defer,return,返回值的执行顺序
- 先给返回值赋值
- 执行defer语句
- 包裹函数return返回
func f1() int { //匿名返回值
var r int = 6
defer func() {
r *= 7
}()
return r
}
func f2() (r int) { //有名返回值
defer func() {
r *= 7
}()
return 6
}
func f3() (r int) { //有名返回值
defer func(r int) {
r *= 7
}(r) // 复制 r 的值,defer执行时使用的是当时拷贝过来的r.函数传参机制
return 6
}
f1的执行结果是6, f2的执行结果是42,f3的执行结果是6
最后看example3。它改写后变成
func f1() (r int) {
r = 6 //给返回值赋值
func(r int) { //这里改的r是传值传进去的r,不会改变要返回的那个r值
r *= 7
}(r)
return //空的return
}
f1的结果是6。f1是匿名返回值,匿名返回值是在return执行时被声明,因此defer声明时,还不能访问到匿名返回值,defer的修改不会影响到返回值。
f2先给返回值r赋值,r=6,执行defer语句,defer修改r, r = 42,然后函数return。
f3是有名返回值,但是因为r是作为defer的传参,在声明defer的时候,就进行参数拷贝传递,所以defer只会对defer函数的局部参数有影响,不会影响到调用函数的返回值。
var a bool = true
func main() { // 2 1
defer func() {
fmt.Println("1")
}()
if a == true {
fmt.Println("2")
return
}
defer func() {
fmt.Println("3")
}()
}
return 之后的 defer 是不能注册的, 也就不能执行后面的函数或方法
闭包与匿名函数
匿名函数:没有函数名的函数。函数也是一种类型,一个函数可以赋值给变量
闭包:在函数中返回匿名函数,匿名函数可以使用函数作用域中的变量。
for i := 0; i <= 3; i++ {
defer func() {
fmt.Print(i)
}
}
//输出结果时 3,3,3,3
因为defer函数的i是对for循环i的引用,defer延迟执行,for循环到最后i是3,到defer执行时i就是3
for i := 0; i <= 3; i++ {
defer func(i int) {
fmt.Print(i)
}(i)
}
//输出结果时 3,2,1,0
因为defer函数的i是在defer声明的时候,就当作defer参数传递到defer函数中
匿名函数
func main() {
/* 匿名函数切片初始化 */
fns := [](func(x int) int){
func(x int) int { return x + 1 },
func(x int) int { return x + 113 },
}
println(fns[1](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)())
}
闭包
package main
import "fmt"
func test() func() {
x := 100
fmt.Printf("x (%p) = %d\n", &x, x)
return func() {
fmt.Printf("x (%p) = %d\n", &x, x)
}
}
func main() {
f := test()
f()
}
输出:
x (0xc42007c008) = 100
x (0xc42007c008) = 100
闭包复制的是原对象指针,这就很容易解释延迟引用现象。函数test返回了一个函数,返回的这个函数就是一个闭包。这个函数中本身是没有定义变量x的,而是引用了它所在的环境(函数test)中的变量x。
go语言中 函数式编程可用函数作为类型 将函数类型传递到函数里面直接可以使用
func fibonacco() intGen {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}
type intGen func() int //将函数定义为类型
func (g intGen) Read(p []byte) (n int, err error) { //定义接口 go语言中任何类型都可以实现接口 函数只是一个特殊的参数
next := g() //next是 类型传输值
if next > 10000 { //限制无限循环
return 0, io.EOF
}
s := fmt.Sprintf("%d\n", next) //将整形转化为字符串 strings.NewReader使用
return strings.NewReader(s).Read(p) //使用s做打印 p为uint8可使用位数
}
func printFileContents(reader io.Reader) { //打印任何类型
scanner := bufio.NewScanner(reader) //将函数转换 赋值给scanner
for scanner.Scan() { //将scanner里面的值全部循环打印出来
fmt.Println(scanner.Text())
}
}
func main() {
a := fibonacco()
printFileContents(a)
}
类似的
func hello() []string {
return nil
}
func main() {
h := hello
if h == nil {
fmt.Println("nil")
} else {
fmt.Println("not nil")
}
/*myPrintln := fmt.Println
myPrintln(myPrintln)
myPrintln("内容")*/
}
是将 hello() 赋值给变量 h,而不是函数的返回值,所以输出 not nil
全局变量特点:
- 常驻内存
- 污染全局
局部变量特点:
- 不常驻内存
- 不污染全局
闭包特点:
- 可以让一个变量常驻内存
- 可以让一个变量不污染全局
函数与方法的区别
1、对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然。
2、对于方法(如struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以。
Go方法规则
根据调用者不同,方法分为两种表现形式:
instance.method(args...) ---> <type>.func(instance, args...)
前者称为 method value,后者 method expression。两者都可像普通函数那样赋值和传参,区别在于 method value 绑定实例,而 method expression 则须显式传参。
package main
import "fmt"
type User struct {
id int
name string
}
func (self *User) Test() {
fmt.Printf("%p, %v\n", self, self)
}
func main() {
u := User{1, "Tom"}
u.Test()
mValue := u.Test
mValue() // 隐式传递 receiver
mExpression := (*User).Test
mExpression(&u) // 显式传递 receiver
}
需要注意,method value 会复制 receiver。
package main
import "fmt"
type User struct {
id int
name string
}
func (self User) Test() {
fmt.Println(self)
}
func main() {
u := User{1, "Tom"}
mValue := u.Test // 立即复制 receiver,因为不是指针类型,不受后续修改影响。
u.id, u.name = 2, "Jack"
u.Test()
mValue()
}
输出结果
{2 Jack}
{1 Tom}
可依据方法集转换 method expression,注意 receiver 类型的差异。
package main
import "fmt"
type User struct {
id int
name string
}
func (self *User) TestPointer() {
fmt.Printf("TestPointer: %p, %v\n", self, self)
}
func (self User) TestValue() {
fmt.Printf("TestValue: %p, %v\n", &self, self)
}
func main() {
u := User{1, "Tom"}
fmt.Printf("User: %p, %v\n", &u, u)
mv := User.TestValue
mv(u)
mp := (*User).TestPointer
mp(&u)
mp2 := (*User).TestValue // *User 方法集包含 TestValue。签名变为 func TestValue(self *User)。实际依然是 receiver value copy。
mp2(&u)
}
输出:
User: 0xc000004078, {1 Tom}
TestValue: 0xc0000040a8, {1 Tom}
TestPointer: 0xc000004078, &{1 Tom}
TestValue: 0xc0000040f0, {1 Tom}
将方法 "还原" 成函数,就容易理解下面的代码了。
package main
type Data struct{}
func (Data) TestValue() {}
func (*Data) TestPointer() {}
func main() {
var p *Data = nil
p.TestPointer()
(*Data)(nil).TestPointer() // method value
(*Data).TestPointer(nil) // method expression
// p.TestValue() // invalid memory address or nil pointer dereference
// (Data)(nil).TestValue() // cannot convert nil to type Data
// Data.TestValue(nil) // cannot use nil as type Data in function argument
}