声明、类型、语句与控制结构
8 使用一致的变量声明形式
Go 语言有两类变量:
- 包级变量:在 package 级别可见的变量,如果是导出变量,则该包级变量也可以被视为全局变量。
- 局部变量:函数或方法体内声明的变量,仅在函数或方法体内可见。
包级变量的声明形式:
- 声明并同时显式初始化:
var variableName = InitExpression。Go 编译器会自动根据右侧的 InitExpression 表达式求值的类型确定左侧所声明变量的类型,整型初始化默认为 int,浮点值初始化默认为 float64,如果不接受默认类型,而是要显示指定类型,推荐使用var a = float32(3.14),不推荐使用var a float32 = 3.14。 - 声明但延迟初始化:
var variableName variableType。虽然没有显式初始化,但 Go 语言会让这些变量拥有初始的“零值”。如果是自定义的类型,保证其零值可用是非常必要的。 - 声明聚类:将同一类的变量声明放在一个 var 块中,将不同类的声明放在不同的 var 块中;或者将延迟初始化的变量放在一个 var 块,而将声明并显式初始化的变量放在另一个 var 块中。
- 就近原则:尽可能在靠近第一次使用变量的位置声明该变量。就近原则实际上是变量的作用域最小化的一种实现手段。
局部变量的声明形式:
- 对于延迟初始化的局部变量声明,采用带有 var 关键字的声明形式:
var b []byte。 - 对于声明且显式初始化的局部变量,建议使用短变量声明形式:
a := 3.14,对于不接受默认类型的变量:a := float32(3.14)。 - 尽量在分支控制时应用短变量声明形式:
for _, b := range *v。体现出“就近原则”,让变量的作用域最小化。 - 对于良好的函数/方法设计讲究的是“单一职责”,因此每个函数/方法的规模都不大,很少需要应用 var 块来聚类声明局部变量。当然,如果遇到适合聚类的应用场景,也应该毫不犹豫地使用 var 块来声明多个局部变量。
9 使用无类型常量简化代码
Go 语言中的 const 整合了 C 语言中宏定义常量、const 只读变量和枚举变量三种形式,并消除了每种形式的不足,使得 Go 常量成为类型安全且对编译器优化友好的语法元素。Go 常量在声明时并不显式指定类型,也就是说使用的是无类型常量。
所有常量表达式的求值计算都可以在编译期而不是在运行期完成,这样既可以减少运行时的工作,也能方便编译器进行编译优化。当操作数是常量时,在编译时也能发现一些运行时的错误,例如整数除零、字符串索引越界等。
无类型常量是 Go 语言推荐的实践,它拥有和字面值一样的灵活特性,可以直接用于更多的表达式而不需要进行显示类型转换,从而简化了代码编写。此外,按照 Go 官方语言规范 的描述,数值型无类型常量可以提供比基础类型更高精度的算术运算,至少有 256bit 的运算精度。
10 使用 iota 实现枚举常量
Go 的 const 语法提供了“隐式重复前一个非空表达式”的机制。
const (
a, b = 1, 11
c, d // 1, 11
e, f // 1, 11
)
iota 是 Go 语言的一个预定义标识符,它表示的是 const 声明块(包括单行声明)中每个常量所处位置在块中的偏移值(从零开始)。
const (
_ = iota
Blue // 1
Black // 2
Red // 3
_
Yellow = iota * 2 // 10
Green // 12
)
iota 让 Go 在枚举常量定义上表达力大增,主要体现在:
- 能够以更为灵活的形式为枚举常量赋初值。
- 不限于整型值,也可以定义浮点型的枚举常量。
- 使得维护枚举常量列表更容易。
- 使用有类型枚举常量保证类型安全。
11 尽量定义零值可用的类型
保持零值可用。——Go 谚语
Go 语言中的每个原生类型都有其默认值,这个默认值就是这个类型的零值。如下所示:
- 整型类型:0
- 浮点类型:0.0
- 布尔类型:false
- 字符串类型:""
- 指针、interface、切片、channel、map、function:nil
Go 的零值初始是递归的,即数组、结构体等类型的零值初始化就是对其组成元素逐一进行零值初始化。
Go 语言零值可用理念给内置类型、标准库的使用者带来很多便利。不过 Go 并非所有类型都是零值可用的,并且零值可用也有一定的限制。
// 零值可用的切片不能通过下标形式操作数据
var s []int
s[0] = 12 // 报错!
s = append(s, 12) // 正确
// map 没有提供零值可用支持
var m map[string]int
m["go"] = 1 // 报错!
m1 := make(map[string]int)
m1["go"] = 1 // 正确
// 尽量避免值复制
var mu sync.Mutex
mu1 := mu
foo(mu)
// 可以通过指针方式传递类似 Mutex 这样的类型
var mu sync.Mutex
foo(&mu)
12 使用复合字面值作为初值构造器
复合字面值(composite literal)由两部分组成:一部分是类型;另一部分是由大括号{}包裹的字面值。
结构体复合字面值:Go 推荐使用field: value的复合字面值形式对 struct 类型变量进行值构造,这种值构造方式可以降低结构体类型使用者与结构体类型设计者之间的耦合,因为一旦该结构体类型增加了一个新的字段,即使是未导出的,这种值构造方式也将导致编译失败,这也是 Go 语言的惯用法。结构体中的字段可以以任意次序出现,未显式出现在字面值的结构体中的字段将采用其对应类型的零值,但不允许将从其他包导入的结构体中的未导出字段作为复合字面值中的 field,这会导致编译错误。
数组/切片复合字面值:使用下标(index)作为field: value形式中的 field,从而实现数组/切片初始元素值的高级构造形式。主要应用于少数场合,比如非连续(稀疏)元素构造初值,让编译器根据最大元素下标值推导数组的大小。另外,在编写单元测试时,为了更显著地体现元素对应的下标值,可能会使用index: value形式来为数组/切片进行值构造。
numbers := [256]int{'a': 8, 'b': 7, 'c': 4, 'd': 3, 'e': 2, 'y': 1, 'x': 5}
// 注:'a' == 97
fnumbers := [...]float64{-1, 4: 2, 3, 7: 4, 9: 5} // [10]float64, [-1 0 0 0 2 3 0 4 0 5]
map 复合字面值:原生的key: value构造形式,当 key 和 value 的类型为复合类型时,我们可以省去 key 或 value 中的复合字面量中的类型(Go1.5)。
type Point struct {
x float64
y float64
}
m := map[string]*Point{
"Persepolis": {29.9, 52.9},
"Uluru": {-25.4, 131.0},
"Googleplex": {37.4, -122.1},
}
关注我
参考
《Go 语言精进之路:从新手到高手的编程思想、方法和技巧》——白明