Go中的初始化,虽然表面上看和C或者C++差别不大,但功能更加强大。在初始化过程中可以构建复杂的结构体,并且能够正确处理初始化对象之间,甚至不同程序包之间的顺序问题。
Go中的常量仅仅就是—常量。它们是在编译时被创建,即使被定义为函数局部的也如此,并且只能是数字,字符(符文),字符串或者布尔类型。由于编译时的限制,定义它们的表达式必须为能被编译器求值的常量表达式。例如,1<<3
是一个常量表达式,而math.Sin(math.Pi/4)
不是,因为函数调用math.Sin
需要在运行时才发生。
在Go中,枚举常量使用iota
枚举器来创建。由于iota
可以为表达式的一部分,并且表达式可以被隐式的重复,所以很容易创建复杂的值集。
type ByteSize float64
const (
_ = iota // ignore first value by assigning to blank identifier
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.09TB
。
这里使用Sprintf
来实现ByteSize
的String
方法是安全的(避免了无穷递归),这并不是因为做了转换,而是因为它是使用%f
来调用Sprintf
的,其不是一个字符串格式:Sprintf
只有当想要一个字符串的时候,才调用String
方法,而%f
是想要一个浮点值。
变量可以像常量那样进行初始化,不过初始值可以为运行时计算的通用表达式。
var (
home = os.Getenv("HOME")
user = os.Getenv("USER")
gopath = os.Getenv("GOPATH")
)
最后,每个源文件可以定义自己的不带参数的(niladic)init
函数,来设置它所需的状态。(实际上每个文件可以有多个init
函数。)init
是在程序包中所有变量声明都被初始化,以及所有被导入的程序包中的变量初始化之后才被调用。
除了用于无法通过声明来表示的初始化以外,init
函数的一个常用法是在真正执行之前进行验证或者修复程序状态的正确性。
func init() {
if user == "" {
log.Fatal("$USER not set")
}
if home == "" {
home = "/home/" + user
}
if gopath == "" {
gopath = home + "/go"
}
// gopath may be overridden by --gopath flag on command line.
flag.StringVar(&gopath, "gopath", gopath, "override default GOPATH")
}