2.1 命名
1) 一个名字必须以一个字母(Unicode字母)或下划线开头,后面可以跟任意数量的字母、数字或下划线。
大写字母和小写字母是不同的。
2) 关键字有25个;关键字不能用于自定义名字,只能在特定语法结构中使用。
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
3) 还有大约30多个预定义的名字,比如int和true等,主要对应内建的常量、类型和函数。
内建常量: true false iota nil
内建类型: int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64
bool byte rune string error
内建函数: make len cap new append copy close delete
complex real imag
panic recover
这些内部预先定义的名字并不是关键字,你可以再定义中重新使用它们。
4) 惯性用法
如果一个名字是大写字母开头的,那么它将是导出的,也就是说可以被外部的包访问。
包本身的名字一般总是用小写字母。
尽量使用短小的名字。
推荐使用"驼峰式"命名。
像ASCII和HTML这样的缩略词则避免使用大小写混合的写法,它们可能被称为htmlEscape、HTMLEscape或escapeHTML,但不会是escapeHtml。
2.2 声明
1) 四种类型的声明语句:var、const、type和func,分别对应变量、常量、类型和函数实体对象的声明。
2) 每个源文件基本组成
以包的声明语句开始,说明该源文件是属于哪个包。
包声明语句之后是import语句导入依赖的其它包,然后是包一级的类型、变量、常量、函数的声明语句。
3) 包一级的各种类型、变量、常量、函数的声明语句的顺序无关紧要
2.3 变量
1) 变量声明格式
var 变量名字 类型 = 表达式
可以省略类型或表达式之一。省略类型,则根据表达式推导类型;省略表达式,则初始化为对应类型的0值。
类型的零值
数值类型 0
布尔类型 false
字符串类型 空字符串
接口或引用类型(包括slice、map、chan和函数) nil
数组或结构体等聚合类型 每个元素或字段都是对应该类型的零值。
同时声明多个变量
var i, j, k int // int, int, int
var b, f, s = true, 2.3, "four" // bool, float64, string
2) 在包级别声明的变量会在main入口函数执行前完成初始化
3) 简短变量声明
只能用于函数中
i := 100 // an int
可以初始化一组变量,一般用于提高代码可读性,如for、if的初始化条件。
i, j := 0, 1
简短变量声明语句中,左边变量必须至少要声明一个新的变量,不然无法通过编译。
对已经声明过的变量,如果是在相同词法域,则赋值;如果是上级词法域,则会生成一个新的覆盖上级变量,这可能会引起奇怪的bug
4) 指针
一个指针对应变量在内存中的存储位置。并不是每一个值都会有一个内存地址,但是对于每一个变量必然有对应的内存地址。
&x表达式(取x变量的内存地址)
任何类型的指针的零值都是nil。指针之间可以进行相等测试,只有当它们指向同一个变量或全部是nil时才相等。
5) new
表达式new(T)将创建一个T类型的匿名变量,初始化为T类型的零值,然后返回变量地址,返回的指针类型为*T。
6) 大小为0的变量
struct{} 或 [0]int64
请谨慎使用大小为0的类型,因为如果类型的大小位0好话,可能导致Go语言的自动垃圾回收器有不同的行为,具体请查看runtime.SetFinalizer函数相关文档
7) 变量生命周期
对于在包一级声明的变量来说,它们的生命周期和整个程序的运行周期是一致的。而局部变量的声明周期则是动态的:从每次创建一个新变量的声明语句开始,直到该变量不再被引用为止。
8) 变量内存分配
编译器会自动选择在栈上还是在堆上分配局部变量的存储空间,但这个选择并不是由用var还是new声明变量的方式决定的。
2.4 赋值
1) 元组赋值
允许同时更新多个变量的值。在赋值之前,赋值语句右边的所有表达式将会先进行求值,然后再统一更新左边对应变量的值。
交换变量
x, y = y, x
a[i], a[j] = a[j], a[i]
f, err = os.Open("foo.txt") // 需要确保函数返回值和左边变量数目一致
v, ok = m[key] // map lookup
v, ok = x.(T) // type assertion
v, ok = <-ch // channel receive
对于值产生一个结果的情形,map查找失败时会返回零值,类型断言失败时会发送运行时panic异常,通道接收失败时会返回零值(阻塞不算是失败)。
2.5 类型
1) 一个类型声明语句创建了一个新的类型名称,和现有类型具有相同的底层结构。
type 类型名字 底层类型
type Celsius float64 // 摄氏温度
type Fahrenheit float64 // 华氏温度
底层数据类型决定了内部结构和表达方式,也决定是否可以像底层类型一样对内置运算符的支持。Celsius和Fahrenheit类型的算术运算行为和底层的float64类型是一样的。
2) 类型转换
对于每一个类型T,都有一个对应的类型转换操作T(x)。
只有当两个类型的底层基础类型相同时,才允许转型操作,或者是两者都是指向相同底层结构的指针类型,这些转换只改变类型而不会影响值本身。
3) 对于中文汉字,Unicode标志都作为小写字母处理,因此中文的命名默认不能导出;
2.6 包
1) 按照惯例,一个包的名字和包的导入路径的最后一个字段相同
2) 包注释
在每个源文件的包声明前仅跟着的注释是包注释。
通常,包注释的第一句应该先是包的功能概要说明。
一个包通常只有一个源文件有包注释。
如果有多个包注释,目前的文档工具会根据源文件名的先后顺序将它们链接为一个包注释。
如果包注释很大,通常会放到一个独立的doc.go文件中。
3) 包的初始化
如果包中含有多个.go源文件,它们将按照发给编译器的顺序进行初始化,Go语言的构建工具首先会将.go文件根据文件名排序,然后依次调用编译器编译。
每个文件都可以包含多个init初始化函数,这样的init初始化函数除了不能被调用或引用外,其他行为和普通函数类似。
在每个文件中的init初始化函数,在程序开始执行时按照它们声明的顺序被自动调用。
每个包在解决依赖的前提下,以导入声明的顺序初始化,每个包只会被初始化一次。
初始化工作是自下而上进行的,main包最后被初始化。以这种方式,可以确保在main函数执行之前,所有依然的包都已经完成初始化工作了。
2.7 作用域
1) 声明语句的作用域是指源代码中可以有效使用这个名字的范围。
2) 控制流标号,就是break、continue或goto语句后面跟着的那种标号,则是函数级的作用域。
3) 如果一个变量或常量递归引用了自身,则会产生编译错误。函数和类型则不会。
4) Go语言的习惯是在if中处理错误然后直接返回,这样可以确保正常执行的语句不需要代码缩进。