Go Effective

数据

  • new(T)不会初始化内存,只会将内存置零。 也就是说,new(T)会为类型为T的新项分配已置零的内存空间, 并返回它的地址。每当获取一个复合字面的地址时,都将为一个新的实例分配内存。
// 少数情况下,若复合字面不包括任何字段,它将创建该类型的零值。表达式 new(File) 和 &File{} 是等价的。
f := new(File)

return &File{fd, name, nil, 0}
  • 内建函数make(T, args)的目的不同于new(T)。它只用于创建切片、映射和信道,并返回类型为T(而非*T)的一个已初始化 (而非置零)的值。出现这种用差异的原因在于,这三种类型本质上为引用数据类型,它们在使用前必须初始化。
// 切片是一个具有三项内容的描述符,包含一个指向(数组内部)数据的指针、长度以及容量, 在这三项被初始化之前,该切片为 nil。
// 对于切片、映射和信道,make 用于初始化其内部的数据结构并准备好将要使用的值。

// 如下会分配一个具有100个 int 的数组空间,接着创建一个长度为10, 容量为100并指向该数组中前10个元素的切片结构。与此相反,
// new([]int) 会返回一个指向新分配的,已置零的切片结构, 即一个指向 nil 切片值的指针。
make([]int, 10, 100)

数组

  • 数组是值。将一个数组赋予另一个数组会复制其所有元素。

  • 特别地,若将某个数组传入某个函数,它将接收到该数组的一份副本而非指针。

  • 数组的大小是其类型的一部分。类型[10]int[20]int是不同的。

// 数组为值的属性很有用,但代价高昂;若你想要C那样的行为和效率,你可以传递一个指向该数组的指针。
// 但这并不是Go的习惯用法,切片才是。
func Sum(a *[3]float64) (sum float64) {
    for _, v := range *a {
        sum += v
    }
    return
}

array := [...]float64{7.0, 8.5, 9.1}
x := Sum(&array)  // 注意显式的取址操作

切片

  • 切片保存了对底层数组的引用,若你将某个切片赋予另一个切片,它们会引用同一个数组。 若某个函数将一个切片作为参数传入,则它对该切片元素的修改对调用者而言同样可见, 这可以理解为传递了底层数组的指针。

  • 只要切片不超出底层数组的限制,它的长度就是可变的,只需将它赋予其自身的切片即可。 切片的容量可通过内建函数 cap 获得,它将给出该切片可取得的最大长度。若数据超出其容量,则会重新分配该切片。返回值即为所得的切片。 该函数中所使用的lencap在应用于nil切片时是合法的,它会返回0。

func Append(slice, data[]byte) []byte {
    l := len(slice)
    if l + len(data) > cap(slice) {  // 重新分配
        // 为了后面的增长,需分配两份。
        newSlice := make([]byte, (l+len(data))*2)
        // copy 函数是预声明的,且可用于任何切片类型。
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0:l+len(data)]
    for i, c := range data {
        slice[l+i] = c
    }
    return slice
}

映射

  • 切片不能用作映射键,因为它们的相等性还未定义。

  • 与切片一样,映射也是引用类型。 若将映射传入函数中,并更改了该映射的内容,则此修改对调用者同样可见。

  • 若试图通过映射中不存在的键来取值,就会返回与该映射中项的类型对应的零值。有时你需要区分某项是不存在还是其值为零值,你可以使用多重赋值的形式来分辨这种情况。

func offset(tz string) int {
    if seconds, ok := timeZone[tz]; ok {
        return seconds
    }
    log.Println("unknown time zone:", tz)
    return 0
}
  • 要删除映射中的某项,可使用内建函数 delete,它以映射及要被删除的键为实参。 即便对应的键不在该映射中,此操作也是安全的。

空白标识符

  • 多重赋值中的空白标识符: 当调用某个函数时,它会返回一个值和一个错误,但只有错误很重要, 那么可使用空白标识符来丢弃无关的值。你偶尔会看见为忽略错误而丢弃错误值的代码,这是种糟糕的实践。请务必检查错误返回。
if _, err := os.Stat(path); os.IsNotExist(err) {
    fmt.Printf("%s does not exist\n", path)
}
  • 未使用的导入和变量: 导入某个包或声明某个变量而不使用它就会产生错误。未使用的包会让程序膨胀并拖慢编译速度, 而已初始化但未使用的变量不仅会浪费计算能力,还有可能暗藏着更大的Bug。 然而在程序开发过程中,经常会产生未使用的导入和变量。虽然以后会用到它们, 但为了完成编译又不得不删除它们才行。
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"

// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
  • 为副作用而导入: 导入某个包只是为了其副作用, 而没有任何明确的使用。
// 为了执行该包下的init函数
import _ "net/http/pprof"
  • 接口检查: 仅当代码中不存在静态类型转换时才能用接口检查声明。
// 在此声明中,我们调用了一个 *RawMessage 转换并将其赋予了 Marshaler,以此来要求 *RawMessage 实现 Marshaler,
// 这时其属性就会在编译时被检测。 若 json.Marshaler 接口被更改,此包将无法通过编译, 而我们则会注意到它需要更新。
var _ json.Marshaler = (*RawMessage)(nil)

内嵌

  • 区分内嵌与子类的重要手段。当内嵌一个类型时,该类型的方法会成为外部类型的方法, 但当它们被调用时,该方法的接收者是内部类型,而非外部的。
// 当 bufio.ReadWriter 的 Read 方法被调用时, 它与之前写的转发方法具有同样的效果;接收者是 ReadWriter 的 reader
// 字段,而非 ReadWriter 本身。
func (rw *ReadWriter) Read(p []byte) (n int, err error) {
    return rw.reader.Read(p)
}
  • 内嵌类型会引入命名冲突的问题,但解决规则却很简单。首先,字段或方法X会隐藏该类型中更深层嵌套的其它项X
// 若 log.Logger 包含一个名为 Command 的字段或方法,Job 的 Command 字段会覆盖它。
type Job struct {
    Command string
    *log.Logger
}
  • 其次,若相同的嵌套层级上出现同名冲突,通常会产生一个错误。然而,若重名永远不会在该类型定义之外的程序中使用,那就不会出错。 这种限定能够在外部嵌套类型发生修改时提供某种保护。 因此,就算添加的字段与另一个子类型中的字段相冲突,只要这两个相同的字段永远不会被使用就没问题。
// 若 Job 结构体中包含名为 Logger 的字段或方法,再将 log.Logger 内嵌到其中的话就会产生错误。
// 某种保护: 如果修改外部嵌套类型字段名时出现重名现象,并使用了该字段,编译器会产生一个错误提示。

并发

  • 不要通过共享内存来通信,而应通过通信来共享内存。
// 引用计数通过为整数变量添加互斥锁来很好地实现。 但作为一种高级方法,通过信道来控制访问能够让你写出更简洁,正确的程序。
  • 若信道是不带缓冲的,那么在接收者收到值前, 发送者会一直阻塞;若信道是带缓冲的,则发送者仅在值被复制到缓冲区前阻塞; 若缓冲区已满,发送者会一直等待直到某个接收者取出一个值为止。

参考链接: 实效Go编程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值