Parts of Effective Go
Data
new
1 | // new是用来分配内存的内建函数 |
构造函数与复合字段
1 | func NewFile(fd int, name string) *File { |
复合字段同样可用于创建数组、切片以及映射,field是索引还是映射键视具体情况而定。
在Go
中,返回一个局部变量的地址完全没有问题,局部变量的数据在函数返回后依然有效。
make
内建函数make(T, args)
的目的不同于new(T)
make(T, args)
只用于创建切片、映射和信道,并返回类型为T
(非*T
)的一个已初始化(非置零)的值
1 | // 会分配一个具有100个int的数组空间 |
make
只适用于映射、切片和信道且不返回指针;若要获得明确的指针,使用new
。
Arrays
数组是值
。若将一个数组赋予另一个数组会复制其所有元素。
若将某个数组传入某个函数,它将接收到该数组的一份副本
而非指针。数组的大小是类型的一部分
。类型[10]int
和[20]int
是不同的。
数组并不是Go
的习惯用法,切片才是。
Slices
1 | var n int |
切片保存了对底层数组的引用,若将某个切片赋予另一个切片,它们会引用同一个数组
若某个函数将一个切片作为参数传入,则函数对切片元素的修改对调用者同样可见
切片的长度决定了可读取数据的上限
切片的长度不能超出底层数组的限制(切片的容量可通过内建函数cap
获得)
1 | // 可以通过Append追加数据到切片 |
二维切片Two-dimensional slices
Go
的数组和切片都是一维的。
要创建等价的二维数组或二维切片,就必须定义一个数组的数组
或切片的切片
。
1 | // A 3*3 array |
1 | // 常用分配二维切片的方法 |
Maps
映射可以关联不同类型的值
任何相等性操作符支持的类型(整数/浮点数/复数/字符串/指针/接口/结构体/数组
)都可以做映射的键
切片不能用作映射键(切片的相等性未定义)
若将映射传入函数中,映射修改对调用者同样可见
1 | // 初始化 |
Printing
1 | // 以下各行产生的输出是一样的 |
1 | var x uint64 = 1<<64 - 1 |
Append
1 | func append(slice []T, elements ...T) []T |
初始化Initialization
Go
在初始化过程中,不仅可以构建复杂的结构,还能正确处理不同包对象间的初始化顺序。
常量Constants
常量在编译时创建
常量只能是数字、字符、字符串或布尔值
定义常量的表达式必须是可以被编译器求值的常量表达式1<<3
是一个常量表达式math.Sin(math.Pi/4)
则不是math.Sin
的函数调用在运行时才会发生
枚举常量使用枚举器iota
创建iota
可以是表达式的一部分
表达式可以被隐式地重复
1 | type ByteSize float64 |
这里用Sprinf
实现ByteSize
的String
方法很安全(不会无限递归),不是因为类型转换,而是因为它以%f
调用了Sprintf
,它并不是一种字符串格式:Sprinf
只会在它需要字符串时才会调用String
方法,而%f
需要一个浮点数值。
变量Variables
1 | // 变量的初始化与常量有区别 |
init
函数
每个包都可以通过定义自己的无参数init
函数来设置一些必要的状态(每个包可以拥有多个init
函数-顺序不可依赖)。
全部init
函数结束就意味着初始化结束。
当该包中所有的变量声明都通过初始化器求值后,并且所有已导入的包都被初始化后,init
才会被调用。
假设PackageA import PackageB import PackageC
,则init
执行顺序为1.PackageCInit 2.PackageBInit 3.PackageAInit
。
在一个应用的启动过程中,每个包的每个init
函数只会被执行一次。
1 | // init除了用于那些不能被表示成声明的初始化外 |