2015-12-09 07:45:11 +00:00
|
|
|
|
### 2.6.2. 包的初始化
|
|
|
|
|
|
2016-04-25 11:09:38 +00:00
|
|
|
|
包的初始化首先是解决包级变量的依赖顺序,然后按照包级变量声明出现的顺序依次初始化:
|
2015-12-09 07:45:11 +00:00
|
|
|
|
|
|
|
|
|
```Go
|
2016-02-15 03:06:34 +00:00
|
|
|
|
var a = b + c // a 第三个初始化, 为 3
|
|
|
|
|
var b = f() // b 第二个初始化, 为 2, 通过调用 f (依赖c)
|
|
|
|
|
var c = 1 // c 第一个初始化, 为 1
|
2015-12-09 07:45:11 +00:00
|
|
|
|
|
|
|
|
|
func f() int { return c + 1 }
|
|
|
|
|
```
|
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
如果包中含有多个.go源文件,它们将按照发给编译器的顺序进行初始化,Go语言的构建工具首先会将.go文件根据文件名排序,然后依次调用编译器编译。
|
2015-12-09 07:45:11 +00:00
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
对于在包级别声明的变量,如果有初始化表达式则用表达式初始化,还有一些没有初始化表达式的,例如某些表格数据初始化并不是一个简单的赋值过程。在这种情况下,我们可以用一个特殊的init初始化函数来简化初始化工作。每个文件都可以包含多个init初始化函数
|
2015-12-09 07:45:11 +00:00
|
|
|
|
|
|
|
|
|
```Go
|
|
|
|
|
func init() { /* ... */ }
|
|
|
|
|
```
|
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
这样的init初始化函数除了不能被调用或引用外,其他行为和普通函数类似。在每个文件中的init初始化函数,在程序开始执行时按照它们声明的顺序被自动调用。
|
2015-12-09 07:45:11 +00:00
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
每个包在解决依赖的前提下,以导入声明的顺序初始化,每个包只会被初始化一次。因此,如果一个p包导入了q包,那么在p包初始化的时候可以认为q包必然已经初始化过了。初始化工作是自下而上进行的,main包最后被初始化。以这种方式,可以确保在main函数执行之前,所有依然的包都已经完成初始化工作了。
|
2015-12-09 07:45:11 +00:00
|
|
|
|
|
2016-04-25 11:09:38 +00:00
|
|
|
|
下面的代码定义了一个PopCount函数,用于返回一个数字中含二进制1bit的个数。它使用init初始化函数来生成辅助表格pc,pc表格用于处理每个8bit宽度的数字含二进制的1bit的bit个数,这样的话在处理64bit宽度的数字时就没有必要循环64次,只需要8次查表就可以了。(这并不是最快的统计1bit数目的算法,但是它可以方便演示init函数的用法,并且演示了如何预生成辅助表格,这是编程中常用的技术)。
|
2015-12-09 07:45:11 +00:00
|
|
|
|
|
2016-01-20 15:16:19 +00:00
|
|
|
|
<u><i>gopl.io/ch2/popcount</i></u>
|
2015-12-09 07:45:11 +00:00
|
|
|
|
```Go
|
|
|
|
|
package popcount
|
|
|
|
|
|
|
|
|
|
// pc[i] is the population count of i.
|
|
|
|
|
var pc [256]byte
|
|
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
|
for i := range pc {
|
|
|
|
|
pc[i] = pc[i/2] + byte(i&1)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PopCount returns the population count (number of set bits) of x.
|
|
|
|
|
func PopCount(x uint64) int {
|
|
|
|
|
return int(pc[byte(x>>(0*8))] +
|
|
|
|
|
pc[byte(x>>(1*8))] +
|
|
|
|
|
pc[byte(x>>(2*8))] +
|
|
|
|
|
pc[byte(x>>(3*8))] +
|
|
|
|
|
pc[byte(x>>(4*8))] +
|
|
|
|
|
pc[byte(x>>(5*8))] +
|
|
|
|
|
pc[byte(x>>(6*8))] +
|
|
|
|
|
pc[byte(x>>(7*8))])
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
译注:对于pc这类需要复杂处理的初始化,可以通过将初始化逻辑包装为一个匿名函数处理,像下面这样:
|
2015-12-26 12:53:13 +00:00
|
|
|
|
|
|
|
|
|
```Go
|
|
|
|
|
// pc[i] is the population count of i.
|
|
|
|
|
var pc [256]byte = func() (pc [256]byte) {
|
|
|
|
|
for i := range pc {
|
|
|
|
|
pc[i] = pc[i/2] + byte(i&1)
|
|
|
|
|
}
|
2016-01-31 06:10:17 +00:00
|
|
|
|
return
|
2015-12-26 12:53:13 +00:00
|
|
|
|
}()
|
|
|
|
|
```
|
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
要注意的是在init函数中,range循环只使用了索引,省略了没有用到的值部分。循环也可以这样写:
|
2015-12-09 07:45:11 +00:00
|
|
|
|
|
|
|
|
|
```Go
|
|
|
|
|
for i, _ := range pc {
|
|
|
|
|
```
|
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
我们在下一节和10.5节还将看到其它使用init函数的地方。
|
2015-12-09 07:45:11 +00:00
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
**练习 2.3:** 重写PopCount函数,用一个循环代替单一的表达式。比较两个版本的性能。(11.4节将展示如何系统地比较两个不同实现的性能。)
|
2015-12-09 07:45:11 +00:00
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
**练习 2.4:** 用移位算法重写PopCount函数,每次测试最右边的1bit,然后统计总数。比较和查表算法的性能差异。
|
2015-12-09 07:45:11 +00:00
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
**练习 2.5:** 表达式`x&(x-1)`用于将x的最低的一个非零的bit位清零。使用这个算法重写PopCount函数,然后比较性能。
|