Identifier(标识符)
标识符(identifier)用于命名包、变量、常量、类型、函数、字段、方法和标签。Go 的标识符区分大小写,其声明位置决定作用域,首字符大小写还可能决定名称能否跨包访问。
构成规则
标识符由一个或多个 Unicode 字母和 Unicode 数字组成,但第一个字符必须是字母或下划线 _:
package main
import "fmt"
func main() {
requestCount := 3
名称 := "Go"
_version2 := "v2"
fmt.Println(requestCount, 名称, _version2)
}
下面这些形式不合法:
2version // 不能以数字开头
request-id // 连字符不是标识符字符
user name // 空格不是标识符字符
Go 关键字不能作为标识符,例如 func、type、map 和 range。User 与 user 是两个不同名称,而且在包级声明、字段或方法上,这个差异还会影响导出状态。
预声明标识符
语言在宇宙块(universe block)中预声明了一组常用名称,例如:
- 类型:
bool、int、string、error、any。 - 常量:
true、false、iota。 - 零值:
nil。 - 函数:
append、cap、clear、close、copy、delete、len、make、max、min、new、panic、print、println、recover。
预声明标识符不是关键字,因此可以被内层声明遮蔽,但通常不应该这样做:
package main
import "fmt"
func main() {
len := 3
fmt.Println(len)
// len("Go") 将无法编译,因为内建函数 len 已被局部变量遮蔽。
}
空白标识符 _
单独的 _ 是空白标识符(blank identifier),表示有意忽略某个值。任何值都可以赋给 _,但之后不能再读取它:
package main
import "fmt"
func lookup() (string, bool) {
return "Go", true
}
func main() {
name, _ := lookup()
fmt.Println(name)
for _, language := range []string{"Go", "TypeScript"} {
fmt.Println(language)
}
}
_ 还可用于空白导入和编译期接口满足检查等明确场景。不要为了绕过“变量未使用”错误而随意丢弃本应处理的错误值。
作用域
标识符从声明开始在特定作用域中有效。常见作用域包括:
| 声明位置 | 典型作用域 |
|---|---|
| 预声明标识符 | 所有 Go 源码可见的宇宙块 |
| 包级声明 | 当前包 |
| 导入的包名 | 当前源文件 |
| 函数参数和返回参数 | 当前函数体 |
| 函数体或语句块中的声明 | 当前最内层块 |
内层作用域可以声明与外层相同的名称,这称为遮蔽(shadowing):
package main
import "fmt"
func main() {
status := "pending"
if ready := true; ready {
status := "ready"
fmt.Println("inner:", status)
}
fmt.Println("outer:", status)
}
输出为:
inner: ready
outer: pending
遮蔽并非语法错误,但可能让错误处理和状态更新偏离预期。看到内层 := 时,应确认它是要声明新变量,还是本应使用 = 更新外层变量。
短变量声明中的名称复用
函数内部的 := 可以在同一作用域复用已有变量,但左侧必须至少有一个非空白的新变量:
package main
import "fmt"
func load() (int, error) {
return 42, nil
}
func main() {
count := 1
count, err := load() // err 是新变量,count 是重新赋值
if err != nil {
fmt.Println("load:", err)
return
}
fmt.Println(count)
}
如果换到内层代码块,同样的写法可能创建新的 count,而不是更新外层值。判断时要同时看名称和词法作用域。
惯用命名
- 包名使用简短、全小写的单词,通常不使用下划线。
- 局部变量的作用域越小,名称可以越短;跨越较长逻辑时应表达业务含义。
- 使用驼峰形式,如
requestID,而不是request_id。 - 常见首字母缩写保持一致,例如
URL、HTTP、ID,使用userID而不是userId。 - 接收者名称通常是类型名的一两个字母,并在该类型的方法间保持一致。
- 获取器通常命名为
Owner(),而不是GetOwner();设置器可命名为SetOwner()。 - 避免包名与导出名称重复含义,例如在
http包中优先使用Client,调用时自然形成http.Client。
命名规则服务于可读性,不必把每个局部变量都扩写成完整句子。应根据作用域、领域含义和团队已有术语选择名称。
与 TypeScript 的区别
TypeScript 常用 camelCase,Go 的局部命名也较接近,但 Go 不使用 export 关键字:包级名称、字段和方法的首字符大小写直接参与可见性规则。此外,Go 的 _ 是特殊标识符,不等同于 JavaScript 社区中“以下划线开头表示未使用或私有”的命名惯例。
小结
- 标识符由 Unicode 字母和数字组成,首字符必须是字母或
_。 - 标识符区分大小写,关键字不能用作名称。
- 预声明标识符可以被遮蔽,但遮蔽内建类型和函数通常会降低可读性。
_表示有意忽略值,不能被读取,也不应掩盖必须处理的错误。- 作用域决定名称在哪里有效;使用
:=时尤其要留意意外遮蔽。 - Go 命名偏好简短、清晰、驼峰形式,并保持常见首字母缩写的大写方式。