Go编程tip-4

从Panic中恢复

recover()的调用仅当它在defer函数中被直接调用时才有效。在defer中和直接调用这两个是必要条件

在Slice, Array, and Map “range”语句中更新引用元素的值

在“range”语句中生成的数据的值是真实集合元素的拷贝。它们不是原有元素的引用。这意味着更新这些值将不会修改原来的数据。同时也意味着使用这些值的地址将不会得到原有数据的指针。

如果你需要更新原有集合中的数据,使用索引操作符来获得数据。
package main

import "fmt"

func main() {
    data := []int{1, 2, 3}
    for i, _ := range data {
        data[i] *= 10
    }

    fmt.Println("data:", data) //prints data: [10 20 30]
}

如果你的集合保存的是指针,那规则会稍有不同。如果要更新原有记录指向的数据,你依然需要使用索引操作,但你可以使用for range语句中的第二个值来更新存储在目标位置的数据。
package main

import "fmt"

func main() {
    data := []*struct{ num int }{{1}, {2}, {3}}

    for _, v := range data {
        v.num *= 10
    }

    fmt.Println(data[0], data[1], data[2]) //prints &{10} &{20} &{30}
}

slice中的隐藏数据

因为slice是一个指针引用,所以如果引用原有数据的一小部分,实际指向的还是原来的整个内存对象。

对slice截断对象的append操作

因为slice很像指针,整个操作很容易产生“越界覆盖”

类型声明和方法

当你通过把一个现有(非interface)的类型定义为一个新的类型时,新的类型不会继承现有类型的方法。
Fails:
package main

import "sync"

type myMutex sync.Mutex

func main() {
    var mtx myMutex
    mtx.Lock()   //error
    mtx.Unlock() //error
}

如果你确实需要原有类型的方法,你可以定义一个新的struct类型,用匿名方式把原有类型嵌入其中。
package main

import "sync"

type myLocker struct {
    sync.Mutex
}

func main() {
    var lock myLocker
    lock.Lock()   //ok
    lock.Unlock() //ok
}

defer的函数什么时候执行?

被defer的调用会在包含的函数的末尾执行,而不是包含代码块的末尾。对于Go新手而言,一个很常犯的错误就是无法区分被defer的代码执行规则和变量作用规则。如果你有一个长时运行的函数,而函数内有一个for循环试图在每次迭代时都defer资源清理调用,那就会出现问题。
解决办法就是将相关的逻辑封装成内部匿名函数,这样在函数结束时,就会调用defer指定的函数

更新map的值

如果你有一个struct值的map,你无法更新单个的struct值。这个操作无效是因为map元素是无法取址的。
但是slice元素是可以取址的。

栈和堆变量

你并不总是知道变量是分配到栈还是堆上。在C++中,使用new创建的变量总是在堆上。在Go中,即使是使用new()或者make()函数来分配,变量的位置还是由编译器决定。编译器根据变量的大小和“泄露分析”的结果来决定其位置。这也意味着在局部变量上返回引用是没问题的,而这在C或者C++这样的语言中是不行的。

如果你想知道变量分配的位置,在“go build”或“go run”上传入“-m“ gc标志(即,go run -gcflags -m app.go)。

多goroutine的读写顺序可能被重排

多个goroutine运行时,顺序的语句执行次序可能会发生调换。

发表评论

电子邮件地址不会被公开。 必填项已用*标注