[TOC] 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") ) ~~~ ## init函数 最后,每个源文件可以定义自己的不带参数的(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") } ~~~