Go 语言词法单元
词法单元
词法分析是代码编译过程第一阶段,负责将源码转换为一系列词法单元,为后续语法分析阶段提供输入。Go 语言核心词法单元分为:字面量(Literals)、标识符(Identifiers)、操作符(Operators)和分隔符(Delimiters)。
假设有以下代码:
package main
import "fmt"
func main() {
    // Prints "Hello, world!"
    fmt.Println("Hello, world!")
}
词法分析器将上述代码分解为以下词法单元:
- 关键字:
package,import,func - 标识符:
main,fmt,Println - 字符串字面量:
"fmt","Hello, world!" - 运算符和分隔符:
(,),{,},",; - 注释:
// Prints "Hello, world!" 
字面量
字面量用来表示固定值,可以出现在两个地方:一是用于常量和变量初始化,二是用于表达式或作为函数实参。Go 语言字面量包括基本字面量和复合字面量。
基本字面量
基本类型字面量仅能表达基本类型的值,分为以下几类:
- 
布尔型字面量:表示逻辑值。只能为
true或false。 - 
整型字面量:表示整数,支持多种数制表示。例如:
123、015321、0x1A3F。 - 
浮点型字面量:表示小数,可以包含小数点或指数部分。例如:
0.1、01.10、35e-2、.1234E+2。 - 
复数型字面量:表示复数,由实部和虚部组成。例如:
011i、4.1e-18i。 - 
字符型字面量:表示 Unicode 码点,用单引号括起来的单个字符。例如:
'啊'、'\t'、'\u12e3'。 - 
字符串字面量:表示普通字符串或原始字符串。例如:
"Hello World"、"你好\n,\"世界\""。 
复合字面量
复合类型字面量用于初始化数组、切片、映射、结构体等更复杂的数据结构。例如:[3]int{1, 2, 3}、map[string]int{"one": 1, "two": 2}。
标识符
高级语言通过一个标识符绑定一块特定内存,后续用标识符来替代指向内存进行操作。在 Go 语言中,标识符用于给变量、类型、函数、包等对象命名。标识符分为两类:
- 预定义标识符:Go 语言预定义了一些标识符,包括关键字和内置标识符(定义在 
builtin.go中)。 - 用户定义标识符:由用户创建的标识符,可以用来命名变量、常量、函数等。
 
创建标识符的过程也叫声明(Declaration)。
用户定义标识符
用户定义标识符基本规则:
- 必须以字母或下划线开头。
 - 可以包含任意数量字母、数字(0-9)和下划线,不支持其他字符。
 - 区分大小写。
 - 不能使用内置关键字和保留字。
 - 在同一个代码块中,标识符必须唯一。
 - 标识符可见性由其命名决定,大写字母开头表示可导出的,小写字母开头表示不可导出的。
 
标识符命名风格约定:
- 小驼峰式命名法(lowerCamelCase):命名私有(未导出的)变量和函数时,第一个单词以小写字母开始,后续单词的首字母大写。例如:
localVariable、myFunction。 - 大驼峰式命名法(UpperCamelCase):命名公共(即在包外可见,导出的)变量和函数则使用大驼峰式,即所有单词首字母都大写,也叫做 Pascal 命名法。例如:
PublicVariable、ExportedFunction。 - 短名称:Go 语言推荐使用简短但描述性强的命名,标识符作用域范围越小,名称越短。只有作用域很大时,才使用更长且更有意义的名称。例如,使用 
i作为循环索引,err表示错误。 - 避免下划线:在 Go 中,一般不使用下划线来分隔单词。
 - 避免缩写:除非该缩写是广泛认可的专有名词,例如用 
HTTP代替HyperTextTransferProtocol。其他情况下不要使用缩写。 
关键字
关键字是有特定语法含义的保留词,总共有 25 个。
声明关键字(4 个):
| 关键字 | 用途 | 
|---|---|
var | 
声明变量 | 
const | 
声明常量 | 
type | 
声明类型 | 
func | 
声明函数 | 
流程控制关键字(11 个):
| 关键字 | 用途 | 
|---|---|
if | 
条件语句 | 
else | 
条件语句中否则 | 
for | 
循环控制 | 
range | 
用于迭代数组、切片、字符串、映射或通道 | 
switch | 
选择语句 | 
case | 
选择语句中分支 | 
default | 
选择语句中默认分支 | 
fallthrough | 
选择语句中进入下一个分支 | 
break | 
中断当前循环语句 | 
continue | 
跳过本次循环 | 
goto | 
无条件跳转到标签 | 
类型控制关键字(4 个):
| 关键字 | 用途 | 
|---|---|
struct | 
定义结构体类型 | 
interface | 
定义接口类型 | 
map | 
定义映射类型 | 
chan | 
定义通道类型 | 
函数控制关键字(4 个):
| 关键字 | 用途 | 
|---|---|
return | 
从函数返回 | 
defer | 
延迟调用函数 | 
go | 
启动新协程 | 
select | 
用于通道多路复用 | 
包管理关键字(2 个):
| 关键字 | 用途 | 
|---|---|
package | 
定义包名 | 
import | 
导入包 | 
数据类型
Go 是强类型(静态编译型)语言,在定义新类型或函数时,必须显式地带上数据类型标识符。预声明数据类型标识符有 22 个。
数值类型(15 个):
| 数值类型 | 说明 | 
|---|---|
int8、int16、int32、int64 | 
有符号整数 | 
uint8、uint16、uint32、uint64 | 
无符号整数 | 
int、unit、uintptr | 
特殊整数型 | 
float32、float64 | 
浮点型 | 
complex64、complex128 | 
复数型 | 
基础类型(4 个):
| 基础类型 | 说明 | 
|---|---|
bool | 
布尔型 | 
string | 
字符串型 | 
rune | 
字符型 | 
byte | 
字节型 | 
接口型(3 个):
| 接口类型 | 说明 | 
|---|---|
error | 
错误处理接口 | 
any | 
空接口的别名 | 
comparable | 
安全比较接口 | 
内置函数
内置函数大多实现于编译器内部,具有全局可见性,不需用使用 import 引入。内置函数有 15 个:
| 内置函数 | 用途 | 
|---|---|
close | 
用于关闭通道 | 
len | 
用于计算某个类型的长度或数量 | 
cap | 
用于计算切片、数组和通道的容量 | 
make、new | 
用于初始化分配内存 | 
append、copy | 
用于修改和复制切片 | 
delete | 
用于从映射中删除键值对 | 
panic、recover | 
用于错误处理机制 | 
print、println | 
用于打印的底层函数 | 
complex、real、imag | 
用于创建和操作复数 | 
在 Go 1.18 中引入了泛型概念,内置函数新增 3 例泛型函数:
| 泛型函数 | 用途 | 
|---|---|
max | 
用于找出一组元素中最大值 | 
min | 
用于找出一组元素中最小值 | 
clear | 
用于清空指定切片或映射 | 
常量值
表达特殊含义的内置常量值。共有 4 个:
| 常量值 | 用途 | 
|---|---|
true、false | 
表示布尔类型真和假 | 
iota | 
用在枚举类型声明中 | 
nil | 
指针或引用类型默认值 | 
空白标识符
空白标识符只有 1 个,为下划线「_」,也叫匿名变量。匿名变量用于在各种情况下忽略值,例如忽略函数某个返回值、忽略导入包、范围语句中忽略索引或键,避免创建无用临时变量:
package main
import (
	"fmt"
	_ "strings" // 忽略导入
)
func main() {
	// 忽略函数返回值
	n, _ := fmt.Println(100)
	// 忽略循环索引
	for _, v := range []int{n} {
		fmt.Println(v)
	}
}
匿名变量只能被赋值,不占用命名空间,也不会分配内存空间,无法读取内容。
操作符
操作符用于进行各种运算和操作,包括算术运算符、比较运算符、逻辑运算符、位运算符、赋值操作符和其他特殊操作符。
操作符列表
在 Go 语言中,操作符和运算符经常被用来描述相同概念,这里不做区分,统一视作操作符。
算术运算符(5 个):都为二元算术运算符。
| 符号 | 说明 | 
|---|---|
+ | 
相加(求和),操作数可以是字符串 | 
- | 
相减(求差) | 
* | 
相乘(求积) | 
/ | 
相除(求商),分母不能为 0,操作数为整数结果为整数,否则为浮点数 | 
% | 
取模(求余),操作数必须为整数,结果为整数 | 
位运算符(6 个):都为二元位运算符,操作数为整数或字节型。
| 符号 | 说明 | 
|---|---|
| | | 按位或操作 | 
& | 
按位与操作 | 
^ | 
按位异或操作。同时也是一元位符,表示按位取补码操作 | 
<< | 
按位左移操作。x<<n 等于 x*(2^n) | 
>> | 
按位右移操作。x>>n 等于 x/(2^n),向下取整 | 
&^ | 
按位清除操作(AND NOT) | 
比较运算符(6 个):都为二元逻辑运算符。
| 符号 | 说明 | 
|---|---|
== | 
等于判断 | 
!= | 
不等判断 | 
< | 
小于判断 | 
<= | 
小于等于判断 | 
> | 
大于判断 | 
>= | 
大于等于判断 | 
逻辑运算符(3 个):操作元素都是布尔类型。
| 符号 | 说明 | 
|---|---|
| || | 逻辑或,二元逻辑运算符 | 
&& | 
逻辑与,二元逻辑运算符 | 
! | 
逻辑非,一元逻辑运算符,反转操作数逻辑状态 | 
赋值和赋值复核运算符(13 个):
| 符号 | 说明 | 
|---|---|
= | 
简单赋值运算符,用于给变量赋值 | 
:= | 
短变量声明操作符,用于声明并初始化局部变量 | 
+= | 
加法赋值运算符。将右侧操作数加到左侧操作数上,然后将结果赋值给左侧操作数 | 
-= | 
减法赋值运算符。从左侧操作数减去右侧操作数,然后将结果赋值给左侧操作数 | 
*= | 
乘法赋值运算符。将左侧操作数与右侧操作数相乘,然后将结果赋值给左侧操作数 | 
/= | 
除法赋值运算符。将左侧操作数除以右侧操作数,然后将结果赋值给左侧操作数 | 
%= | 
取模赋值运算符。将左侧操作数对右侧操作数取模,然后将结果赋值给左侧操作数 | 
&= | 
按位与赋值运算符。对左右两侧的操作数进行按位与操作,然后将结果赋值给左侧操作数 | 
| |= | 按位或赋值运算符。对左右两侧的操作数进行按位或操作,然后将结果赋值给左侧操作数 | 
^= | 
按位异或赋值运算符。对左右两侧的操作数进行按位异或操作,然后将结果赋值给左侧操作数 | 
&^= | 
按位清除赋值运算符。将左侧操作数的位中,对应右侧操作数位为 1 的部分设置为 0,然后将结果赋值给左侧操作数 | 
>>= | 
右移赋值运算符。将左侧操作数向右移动右侧操作数指定的位数,然后将结果赋值给左侧操作数 | 
<<= | 
左移赋值运算符。将左侧操作数向左移动右侧操作数指定的位数,然后将结果赋值给左侧操作数 | 
自增自减操作符(2 个):属于语句而非表达式,没有运算符优先级。
| 符号 | 说明 | 
|---|---|
++ | 
a++ 等同于 a = a + 1。不支持前置增量操作 ++a | 
-- | 
a-- 等同于 a = a - 1。不支持前置减量操作 --a | 
其他操作符(3 个):& 和 * 同时也是一元地址运算符。
| 符号 | 说明 | 
|---|---|
<- | 
用于通道中发送和接收值 | 
& | 
取地址操作符,用于获取变量的内存地址 | 
* | 
指针解引用操作符,用于获取指针指向变量的值 | 
运算符规则
一元运算符优先级比二元运算符高。二元运算符优先级如下:
| 运算符 | 优先级 | 
|---|---|
* / % << >> & &^ | 
最高 | 
+ - | ^ | 
较高 | 
== != < <= > >= | 
正常 | 
&& | 
较低 | 
| || | 最低 | 
其他运算符规则:
- 如果表达式中出现相同优先级的运算符,按照从左到右顺序依次操作;
 - 所有运算符都受括号影响,括号内先操作。在复杂表达式中,为避免优先级模糊,可应用括号来明确操作顺序;
 - Go 语言不支持运算符重载。例如大数类型,需要使用专用方法来执行加法运算。
 
分隔符
分隔符用于界定语句结构,帮助组织代码块、参数列表、数组元素等。Go 语言使用的分隔符可以分为两大类:
- 操作符:在 Go 语言中,操作符也充当分隔符。例如,在表达式 
sum := a + b中,:=和+同时扮演操作符和分隔符角色,将语句分割成五个独立基本元素。 - 语法符号:包括圆括号、方括号、花括号、点、逗号、分号和冒号,每个都有特定用途。
 
语句分隔符
在 Go 语言中,分号 ; 由编译器在扫描源代码过程中,作为语句结束符在每行结束时自动插入。通常仅在流程控制语句中使用分号,用来分隔初始化语句、条件表达式语句或步进表达式语句:
package main
import "fmt"
func main() {
	// 用于在一行内写多条语句,不过一般会被自动格式化掉
	a := 1; b := 2
	// 用于流程控制语句分隔子语句
	for i := 0; i < a+b; i++ {
		fmt.Println(i)
	}
}
访问分隔符
点号 . 用于访问结构体字段或调用对象方法,以及引用包中的具体函数、变量或类型:
package main
import "fmt"
type Person struct {
	Name string
}
func (p Person) SayHello() {
	// 引用包函数和结构体字段
	fmt.Println("Hello,", p.Name)
}
func main() {
	p := Person{Name: "John"}
	// 调用对象方法
	p.SayHello()
}
元素分隔符
逗号 , 用于分隔同一行中的多个声明或调用参数。此外也用于分隔数组、切片或字典的元素:
package main
import "fmt"
func main() {
	// 分隔多个元素
	data := []string{"a", "b", "c"}
	// 分隔多个声明变量
	var a, b, c int
	// 分隔多个函数参数
	fmt.Println(a, b, c, data)
}
结构分隔符
结构分隔符指 Go 语言中三种括号:
- 圆括号 
():用于分组表达式,封装函数调用、参数和返回类型,以及控制运算顺序。 - 方括号 
[]:用于指定数组和切片长度,访问数组和切片元素,以及声明映射类型。 - 花括号 
{}:用于定义复合类型的字面值,以及定义代码块。 
下面代码应用这三种括号:
package main
import "fmt"
func f(x int, y int) (int, int) {
	if x > y {
		return x + y, (x - y) * (x + y)
	}
	return x + y, (y - x) * (x + y)
}
func main() {
	var (
		a = [5]int{1, 2, 3, 4, 5}
		s = a[1:3]
		m = map[string][]int{"key": s}
	)
	fmt.Println(f(a[0], m["key"][1]))
}
冒号分隔符
冒号 : 在 Go 语言中多个不同用途:
- 短变量声明:用于局部变量声明与初始化。
 - 切片操作:用于切片时定义起始和结束索引。
 - 映射操作:用于映射类型中分隔键和值。
 - 选择语句:用于 
case语句后。 
冒号这些用法都涉及到某种形式的分隔或赋值操作:
package main
import "fmt"
func main() {
	// 映射中分隔键和值
	m := map[string][]int{"k": {1, 2, 3}}
	// 短变量声明切片中定义范围
	s := m["k"][:2]
	// 选择语句中使用
	switch s[0] {
	case 1:
		fmt.Println("First element is 1")
	}
}