高效的 Go 编程 Effective Go - 初始化
免费 - Sunrise - - 浏览量: 0 - 文章来源
尽管从表面上看,Go 的初始化过程与 C 或 C++ 差别并不算太大,但它确实更为强大。 在初始化过程中,不仅可以构建复杂的结构,还能正确处理不同包对象间的初始化顺序。
常量
Go 中的常量就是不变量。它们在编译时创建,即便它们可能是函数中定义的局部变量。 常量只能是数字、字符(符文)、字符串或布尔值。由于编译时的限制, 定义它们的表达式必须也是可被编译器求值的常量表达式。例如 1<<3
就是一个常量表达式,而 math.Sin(math.Pi/4)
则不是,因为对 math.Sin
的函数调用在运行时才会发生。
在Go中,枚举常量使用枚举器 iota
创建。由于 iota
可为表达式的一部分,而表达式可以被隐式地重复,这样也就更容易构建复杂的值的集合了。
type ByteSize float64
const (
_ = iota // 通过赋予空白标识符来忽略第一个值
KB ByteSize = 1 << (10 * iota)
MB
GB
TB
PB
EB
ZB
YB
)
由于可将 String
之类的方法附加在用户定义的类型上, 因此它就为打印时自动格式化任意值提供了可能性,即便是作为一个通用类型的一部分。 尽管你常常会看到这种技术应用于结构体,但它对于像 ByteSize
之类的浮点数标量等类型也是有用的。
func (b ByteSize) String() string {
switch {
case b >= YB:
return fmt.Sprintf("%.2fYB", b/YB)
case b >= ZB:
return fmt.Sprintf("%.2fZB", b/ZB)
case b >= EB:
return fmt.Sprintf("%.2fEB", b/EB)
case b >= PB:
return fmt.Sprintf("%.2fPB", b/PB)
case b >= TB:
return fmt.Sprintf("%.2fTB", b/TB)
case b >= GB:
return fmt.Sprintf("%.2fGB", b/GB)
case b >= MB:
return fmt.Sprintf("%.2fMB", b/MB)
case b >= KB:
return fmt.Sprintf("%.2fKB", b/KB)
}
return fmt.Sprintf("%.2fB", b)
}
表达式 YB
会打印出 1.00YB
,而 ByteSize(1e13)
则会打印出 9.09
。
在这里用 Sprintf
实现 ByteSize
的 String
方法很安全(不会无限递归),这倒不是因为类型转换,而是它以 %f
调用了 Sprintf
,它并不是一种字符串格式:Sprintf
只会在它需要字符串时才调用 String
方法,而 %f
需要一个浮点数值。
变量
变量能像常量一样初始化,而且可以初始化为一个可在运行时得出结果的普通表达式。
var (
home = os.Getenv("HOME")
user = os.Getenv("USER")
gopath = os.Getenv("GOPATH")
)
init 函数
最后,每个源文件都可以通过定义自己的无参数 init
函数来设置一些必要的状态。 (其实每个文件都可以拥有多个 init
函数。)而它的结束就意味着初始化结束: 只有该包中的所有变量声明都通过它们的初始化器求值后 init
才会被调用, 而包中的变量只有在所有已导入的包都被初始化后才会被求值。
除了那些不能被表示成声明的初始化外,init
函数还常被用在程序真正开始执行前,检验或校正程序的状态。
func init() {
if user == "" {
log.Fatal("$USER not set")
}
if home == "" {
home = "/home/" + user
}
if gopath == "" {
gopath = home + "/go"
}
// gopath 可通过命令行中的 --gopath 标记覆盖掉。
flag.StringVar(&gopath, "gopath", gopath, "override default GOPATH")
}