2.1.1.常量和变量
常量使用关键字const声明;变量可以使用关键字var声明,也可以使用快捷变量声明语法。Go语言可以自动推断出所声明变量的类型,但是如果需要,显式指定其类型也是合法的,比如声明一种与Go语言的常规推断不同的类型。下面是一些声明的例子:
1 2 3 4 5 6 7 8 9 | const limit = 123 //常量,其类型兼容任何数字 const top uint16 = 1234 //常量,类型:uint16 start := 1 //变量,推断类型:int end := int64(12345) //变量,类型:int64 var i int //变量,值为0,类型:int var debug = false //变量,推断类型:bool debug := true //变量,推断类型:bool price := 1.5 //变量,推断类型:float64 name = "johnson" //变量,推断类型:string |
对于整型常量Go语言将其类型推断为int,对于浮点型常量Go语言将其类型推断为int,对于复数常量Go语言将其类型推断为complex128(名字里的数字代表它们占用多少位)。通常的做法是不Go显式地声明其类型,除非我们需要使用一个Go语言无法推断的特殊类型。我们将会在2·3节继续讨论。指定类型的数值常量(例如top)只可用于和其它的具有相同类型的数值的表达式中(除非经过转换)。未指定类型的数值常量可用于那些数值类型为任何内置类型的表达式中。
变量i并没有被显式的初始化。这在Go语言中是非常安全的,因为Go语言总是将那些未被赋值的变量赋值为零值。这意味着每一个数值变量的默认值都保证为0,而每个字符串都默认为空,除非我们显示的将它们初始化。这保证了Go程序不会遭受像其他语言中未初始化而导致出现垃圾值的问题。
2.1.1.1.枚举
当我们需要设置多个常量的时候,我们不必重复使用const关键字,只需使用const关键字一次就可以将所有常量声明组合在一起。(在第1章中我们导入包时使用了相同的语法。该语法也可以被用于使用var关键字来声明一组变量。) 当我们只希望声明具有不同值的常量且不关心其值是多少时,我们可以使用Go语言中相对比较简陋的枚举语法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | const Monday = 0 const Tuesday = 1 const Wednesday = 3 const ( Monday = 0 Tuesday = 1 Wednesday = 3 ) const ( Monday = iota //0 Tuesday //1 Wednesday //2 ) |
这3个代码片段都可以实现相同的功能。一组常量的运转方式是第一个常量被设置为零值,除非该常量被显式的设置为其他值(一个值或者是iota),第二个以及随后的常量则被设置为它们前面一个常量的值——或者如果前面常量的值为iota,则将其后续值也设为iota。后续的每一个iota值都比前面的值大1。
更正式的,iota预定义标识符表示连续的无类型整数常量。每次关键字const出现时,它的值被重置为零值(所以每次一组新的常量被定义),而每个常量的声明的增量为1。因此在右侧的代码片段中,所有常量都被设为iota值(指Magenta和Yellow的值)。由于Cyan紧跟着一个const关键字,其iota值重设为0,即Cyan的值。Magenta的值也被设置为Iota但是这里iota的值为1。类似地,Yellow的值也是iota,它的值为2。如果这种情况下,我们在末尾再添加一个Black变量(在const组内部),它的值就被隐式地设为iota,这时它的值是3。
另一方面,如果右侧代码片段中没有iota标识符,Cyan的值将会被设为0,而Magenta的值则会设为Cyan的值,Yellow的值则被设为Magenta的值——所以最终它们的值都被设为0。类似的,如果Cyan的值被设为9,那么所有的值都将被设为9。或者,如果Magenta的值被设为5,那么Cyan的值将被设为0(因为是组中的第一个值且没有被设为一个显式的值或者iota),Magenta的值将是5(显式地设置),而Yellow的值也将是5(前一个常量的值)。将iota与浮点数、简单表达式以及自定义类型一起使用也是可以的。
1 2 3 4 5 6 7 | type BitF1ag int const ( Active BitF1ag = 1 << iota //1<<0==1 Send //隐式地设置成BitF1ag=1<<iota//1<<1==2 Receive //隐式地设置成BitF1ag=1<<iota//1<<2=4 ) flag := Active1 | Send |
在这个代码片段中,我们创建了3个自定义类型BitFlag的位标识,并将变量flag(类型为BitFlag)的值设为其中两个值的按位或(所以flag的值为3)。在忽略自定义类型的情况下,Go会将常量作为无类型整数,并将flag的类型推断成整型。BitF1ag类型的变量可以被赋值为任何int类型的值;由于BitFlag是一个用户自定义的独特类型,所以只有在将其转换成int类型后才能将其与int型数据一起操作(或者将int类型转换成BitF1ag类型)。
在这种情况下BitFlag类型是很有用的,但是它不利于调试。如果打印flag的值,我们只能得到一个数字3,毫无标记指明这是什么意思。Go语言可以很容易的控制应该如何打印自定义类型的值,因为fmt包中的函数可以使用类型自定义的String()方法来打印其值。因此为了让BitFlag类型可以打印更多的信息,我们可以简单的为其添加一个合适的String()方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | func (flag BitFlag) String() string { var flags []string if flag&Active == Active { flags = append(flags, "Active") } if flag&Send == Send { flags = append(flags, "Send") } if flag&Receive == Receive { flags = append(flags, "Receive") } if len(flags) > 0 { // int(flag) is vital to avoid infinite recursion! return fmt.Sprintf("%d(%s)", int(flag), strings.Join(flags, "|")) } return "0()" } |
该方法为那些设置好值的位域构建了一个(可能为空)字符串切片,并将其作为十进制整型或字符串打印出该位域的值。(我们通过将%d格式标识符替换为%b就可以轻松的将该值以二进制的形式打印出来。)正如其中的注释所说,当将flag传递给fmt.Sprintf()函数时将其(BitFlag类型)转换为底层的int类型是极其必要的,否则BitFlag.String()方法将会在flag上被递归调用并导致我们陷入无限的递归调用。
1 2 | Println(BitFlag(0), Active, Send, flag, Receive, flag|Receive) 0() 1(Active) 2(Send) 3(Active|Send) 4(Receive) 7(Active|Send|Receive) |
该代码片段展示了带String()方法的BitFlags的打印结果——显然,与使用纯整型相比,这样更有利于代码调试。当然,创建一个特定范围的自定义整数类型,或者创建一个更复杂的自定义枚举类型也是可以的。Go语言的关于枚举的极简实现方式是Go语言的经典哲学:Go语言旨在为程序员提供他们付所需要的一切——包括许多强大且便捷的特性——同时又尽可能的保持Go语言的简小、连贯且快速(构建并运行)。