跳到主要内容

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 关键字不能作为标识符,例如 functypemaprangeUseruser 是两个不同名称,而且在包级声明、字段或方法上,这个差异还会影响导出状态。

预声明标识符

语言在宇宙块(universe block)中预声明了一组常用名称,例如:

  • 类型:boolintstringerrorany
  • 常量:truefalseiota
  • 零值:nil
  • 函数:appendcapclearclosecopydeletelenmakemaxminnewpanicprintprintlnrecover

预声明标识符不是关键字,因此可以被内层声明遮蔽,但通常不应该这样做:

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
  • 常见首字母缩写保持一致,例如 URLHTTPID,使用 userID 而不是 userId
  • 接收者名称通常是类型名的一两个字母,并在该类型的方法间保持一致。
  • 获取器通常命名为 Owner(),而不是 GetOwner();设置器可命名为 SetOwner()
  • 避免包名与导出名称重复含义,例如在 http 包中优先使用 Client,调用时自然形成 http.Client

命名规则服务于可读性,不必把每个局部变量都扩写成完整句子。应根据作用域、领域含义和团队已有术语选择名称。

与 TypeScript 的区别

TypeScript 常用 camelCase,Go 的局部命名也较接近,但 Go 不使用 export 关键字:包级名称、字段和方法的首字符大小写直接参与可见性规则。此外,Go 的 _ 是特殊标识符,不等同于 JavaScript 社区中“以下划线开头表示未使用或私有”的命名惯例。

小结

  • 标识符由 Unicode 字母和数字组成,首字符必须是字母或 _
  • 标识符区分大小写,关键字不能用作名称。
  • 预声明标识符可以被遮蔽,但遮蔽内建类型和函数通常会降低可读性。
  • _ 表示有意忽略值,不能被读取,也不应掩盖必须处理的错误。
  • 作用域决定名称在哪里有效;使用 := 时尤其要留意意外遮蔽。
  • Go 命名偏好简短、清晰、驼峰形式,并保持常见首字母缩写的大写方式。