Skip to content

表达式与语句

本文档详细介绍Go语言中的表达式和语句,包括运算元、运算符、控制流语句等核心语法概念。

📋 目录

表达式概述

表达式是Go语言中用于计算值的基本构造块。表达式通过对运算元使用运算符和函数调用来产生计算结果。

表达式的组成

go
// 表达式的基本形式
result = operand1 operator operand2

表达式类型:

  • 字面值表达式42, "hello", true
  • 标识符表达式:变量名、函数名
  • 复合表达式a + b, f(x), arr[i]
  • 类型转换表达式int(x), string(bytes)

运算元

运算元是表达式中的基本组成部分,代表可以参与运算的值。

运算元的语法定义

go
Operand     = Literal | OperandName | MethodExpr | "(" Expression ")" .
Literal     = BasicLit | CompositeLit | FunctionLit .
BasicLit    = int_lit | float_lit | imaginary_lit | rune_lit | string_lit .
OperandName = identifier | QualifiedIdent.

运算元类型

1. 字面值 (Literals)

go
// 基本字面值
var num int = 42              // 整数字面值
var pi float64 = 3.14159      // 浮点数字面值
var name string = "Go"        // 字符串字面值
var flag bool = true          // 布尔字面值
var char rune = 'A'           // 字符字面值

// 复合字面值
var arr = [3]int{1, 2, 3}     // 数组字面值
var slice = []string{"a", "b"} // 切片字面值
var m = map[string]int{        // 映射字面值
    "key1": 100,
    "key2": 200,
}

2. 标识符 (Identifiers)

go
// 变量标识符
var count int = 10
result := count * 2           // count是运算元

// 函数标识符
func add(a, b int) int {
    return a + b
}
sum := add(3, 4)              // add是运算元

3. 方法表达式 (Method Expressions)

go
type Rectangle struct {
    Width, Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// 方法表达式作为运算元
var areaFunc = Rectangle.Area
rect := Rectangle{Width: 5, Height: 3}
area := areaFunc(rect)        // Rectangle.Area是运算元

4. 括号表达式

go
// 使用括号改变运算优先级
result1 := 2 + 3 * 4          // 结果:14
result2 := (2 + 3) * 4        // 结果:20,括号表达式作为运算元

空标识符的使用

空标识符 _ 只能出现在赋值语句的左侧:

go
// 正确使用空标识符
_, err := os.Open("file.txt")  // 忽略第一个返回值
value, _ := strconv.Atoi("123") // 忽略错误返回值

// 错误使用(编译错误)
// result := _ + 10  // 空标识符不能作为右值使用

修饰标识符

修饰标识符是以包名作为前缀修饰的标识符。包名和标识符都不能为空。

go
QualifiedIdent = PackageName "." identifier .

修饰标识符可以用来访问不同包(需要先导入)中的标识符。标识符必须是导出的并在包级代码块声明才能够被访问。

go
math.Sin	// 表示 math 包中的 Sin 函数

复合字面值

复合字面值能为结构体、数组、切片和 map 初始化值。它每次只能创建一个值。字面值由一个字面值类型和使用括号括起来的元素列表组成。元素前也可以声明元素对应的键。

go
CompositeLit  = LiteralType LiteralValue .
LiteralType   = StructType | ArrayType | "[" "..." "]" ElementType |
                SliceType | MapType | TypeName .
LiteralValue  = "{" [ ElementList [ "," ] ] "}" .
ElementList   = KeyedElement { "," KeyedElement } .
KeyedElement  = [ Key ":" ] Element .
Key           = FieldName | Expression | LiteralValue .
FieldName     = identifier .
Element       = Expression | LiteralValue .

字面值类型的底层类型必须是一个结构体,数组,切片或 map 类型(如果没有指定类型名就会强制执行这个约束)。元素的类型和键都必须能够分配给相应的字段的元素和键类型;没有额外的类型转换。键可以表示结构体的字段名,切片和数组的索引,map 类型的键。对于 map 字面值,所有的元素都必须有键。如果相同字段名或常量值的键对应多个元素就会报错。如果 map 类型的键为非常量类型,请看求值顺序章节。

结构体字面值遵循以下规则:

  • 在结构体中,键必须是它的字段名。

  • 不包含任何键的元素列表的顺序需要与结构体字段的声明顺序相同。

  • 如果一个元素指定了键,那么所有的元素都必须指定键。

  • 包含键的元素列表不需要指定结构体的每个字字段,缺省字段会使用字段类型的零值。

  • 字面值可以不指定元素;这样的字面值等于该类型的零值。

  • 指定非本包的非导出字段会报错。

给定声明:

go
type Point3D struct { x, y, z float64 }
type Line struct { p, q Point3D }

我们可以使用这种写法:

go
origin := Point3D{}                            // Point3D 的零值
line := Line{origin, Point3D{y: -4, z: 12.3}}  // line.q.x 的零值

数组和切片遵循以下规则:

  • 每个元素都关联一个数字索引标记元素再数组中的位置。

  • 给元素指定的键会作为它的索引。键必须是能够表示非负的 int 类型值的常量;如果是指定类型的常量,那么常量必须是整型。

  • 元素没有指定键时会使用之前的索引加一。如果第一个元素没有指定键,它的索引为零。

对复合字面值取址会生成指向由字面量初始化的变量的指针。

go
var pointer *Point3D = &Point3D{y: 1000}

数组字面值需要在类型中指定数组的长度。如果提供的元素少于数组的长度,那么缺少元素的位置将会使用元素类型的零值替代。如果索引超过数组的长度会报错。 表示数组的长度等于最大元素索引加一。

go
buffer := [10]string{}             // len(buffer) == 10
intSet := [6]int{1, 2, 3, 5}       // len(intSet) == 6
days := [...]string{"Sat", "Sun"}  // len(days) == 2

切片字面值底层其实就是数组字面值。因此它的长度和容量都是元素的最大索引加一。切片字面值的格式为:

go
[]T{x1, x2, … xn}

可以在数组上进行切片操作从而获得切片:

go
tmp := [n]T{x1, x2, … xn}
tmp[0 : n]

在一个数组、切片或 map 类型 T 中。元素或者 map 的键可能有自己的字面值类型,如果字面值类型和元素或者键类型相同,那么对应的类型标识符可以省略。与之类似,如果元素或键的类型为 *T,那么它们的 &T 也可以省略。

go
[...]Point{{1.5, -3.5}, {0, 0}}     // same as [...]Point{Point{1.5, -3.5}, Point{0, 0}}
[][]int{{1, 2, 3}, {4, 5}}          // same as [][]int{[]int{1, 2, 3}, []int{4, 5}}
[][]Point{{{0, 1}, {1, 2}}}         // same as [][]Point{[]Point{Point{0, 1}, Point{1, 2}}}
map[string]Point{"orig": {0, 0}}    // same as map[string]Point{"orig": Point{0, 0}}
map[Point]string{{0, 0}: "orig"}    // same as map[Point]string{Point{0, 0}: "orig"}

type PPoint *Point
[2]*Point{{1.5, -3.5}, {}}          // same as [2]*Point{&Point{1.5, -3.5}, &Point{}}
[2]PPoint{{1.5, -3.5}, {}}          // same as [2]PPoint{PPoint(&Point{1.5, -3.5}), PPoint(&Point{})}

当复合字面值使用字面值类型的类型名格式出现在 ifforswitch 语句的关键字和括号之间并且没有使用圆括号包裹的时候,会引发语法歧义。在这种特殊的情况下字面值的括号会被认为是语句的代码块。为了避免歧义,复合字面值必须用括号括起来。

go
if x == (T{a,b,c}[i]) { … }
if (x == T{a,b,c}[i]) { … }

下面是合法的数组、切片和 map 的例子:

go
// list of prime numbers
primes := []int{2, 3, 5, 7, 9, 2147483647}

// vowels[ch] is true if ch is a vowel
vowels := [128]bool{'a': true, 'e': true, 'i': true, 'o': true, 'u': true, 'y': true}

// the array [10]float32{-1, 0, 0, 0, -0.1, -0.1, 0, 0, 0, -1}
filter := [10]float32{-1, 4: -0.1, -0.1, 9: -1}

// frequencies in Hz for equal-tempered scale (A4 = 440Hz)
noteFrequency := map[string]float32{
	"C0": 16.35, "D0": 18.35, "E0": 20.60, "F0": 21.83,
	"G0": 24.50, "A0": 27.50, "B0": 30.87,
}

函数字面值

函数字面值表示一个匿名函数。

go
FunctionLit = "func" Function .
go
func(a, b int, z float64) bool { return a*b < int(z) }

函数字面值能分配给变量或直接调用。

函数字面值是一个闭包。它可以引用包裹函数中的变量,这些变量在包裹函数和函数字面值之间是共享的。并且它会一直存在直到生命周期结束。

主要表达式

主要表达式是一元和二元表达式的运算元。

go
PrimaryExpr =
	Operand |
	Conversion |
	PrimaryExpr Selector |
	PrimaryExpr Index |
	PrimaryExpr Slice |
	PrimaryExpr TypeAssertion |
	PrimaryExpr Arguments .

Selector       = "." identifier .
Index          = "[" Expression "]" .
Slice          = "[" [ Expression ] ":" [ Expression ] "]" |
                 "[" [ Expression ] ":" Expression ":" Expression "]" .
TypeAssertion  = "." "(" Type ")" .
Arguments      = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .
go
x
2
(s + ".txt")
f(3.1415, true)
Point{1, 2}
m["foo"]
s[i : j + 1]
obj.color
f.p[i].x()

选择器

对于一个 x 不是包名的主要表达式,选择器表达式:

go
x.f

表示 x 的字段或方法 f(有时为 *x)。标识符 f 叫做(字段/方法)选择器。它不能是空标识符。选择器表达式的类型就是 f 的类型。如果 x 是包名。请参考修饰标识符。

选择器 f 可以表示类型 T 的方法或字段 f。也可以表示类型 T 的嵌入方法或字段 f。访问 f 所需穿过的嵌套层数叫做它在类型 T 中的深度。声明在 T 中的字段或方法的深度为 0。声明在 T 的嵌入字段 A 中的方法或字段的深度等于 f 在 A 中的深度加一。

选择器遵循以下原则:

  • 对于非指针/接口类型 T/*T 的值 x,x.f 表示第一层的方法/字段。如果在第一层没有对应的 f,选择器表达式就是非法的。

  • 对于接口类型 I 的值 x,x.f表示动态值 x 的方法名 f。如果接口 I 的方法集中没有 f 方法,选择器就是非法的。

  • 作为例外,如果 x 是一个指针类型并且 (*x).f 是合法的选择器表达式(只能表示字段,不能表示方法)。那么(*x).f 可以简写成 x.f。

  • 在其他情况下,x.f 都是非法的。

  • 如果x是指针类型,并且值为 nil,其中 f 为结构体字段。赋值或取值 x.f 会引起运行时恐慌。

  • 如果x是接口类型,并且值为 nil。调用 x.f 会引起运行时恐慌。

例如给定声明:

go
type T0 struct {
	x int
}

func (*T0) M0()

type T1 struct {
	y int
}

func (T1) M1()

type T2 struct {
	z int
	T1
	*T0
}

func (*T2) M2()

type Q *T2

var t T2     // with t.T0 != nil
var p *T2    // with p != nil and (*p).T0 != nil
var q Q = p

结果:

go
t.z          // t.z
t.y          // t.T1.y
t.x          // (*t.T0).x

p.z          // (*p).z
p.y          // (*p).T1.y
p.x          // (*(*p).T0).x

q.x          // (*(*q).T0).x        (*q).x is a valid field selector

p.M0()       // ((*p).T0).M0()      M0 expects *T0 receiver
p.M1()       // ((*p).T1).M1()      M1 expects T1 receiver
p.M2()       // p.M2()              M2 expects *T2 receiver
t.M2()       // (&t).M2()           M2 expects *T2 receiver, see section on Calls

但是下面这种方式是不合法的:

go
q.M0()       // (*q).M0 is valid but not a field selector

方法表达式

如果 M 在类型 T 的方法集中。那么 T.M 就是能够正常调用的函数。使用与 M 相同的参数只是在参数列表的最前面增加了接收者参数。

go
MethodExpr    = ReceiverType "." MethodName .
ReceiverType  = TypeName | "(" "*" TypeName ")" | "(" ReceiverType ")" .

假设结构体 T 有两个方法。接收者类型为 T 的 Mv 方法和接收者类型为 *T 的 Mp 方法:

go
type T struct {
	a int
}
func (tv  T) Mv(a int) int         { return 0 }  // value receiver
func (tp *T) Mp(f float32) float32 { return 1 }  // pointer receiver

var t T

表达式

go
T.Mv

将会生成一个等价于 Mv 方法只是第一个参数显式声明接受者的函数。它的签名为:

go
func(tv T, a int) int

这个函数能够通过接收者正常调用,以下5种方式是等价的:

go
t.Mv(7)
T.Mv(t, 7)
(T).Mv(t, 7)
f1 := T.Mv; f1(t, 7)
f2 := (T).Mv; f2(t, 7)

与之类似:

go
(*T).Mp

生成表示 Mp 的函数签名:

go
func(tp *T, f float32) float32

对于一个把值作为接收者的方法,我们可以显式的从指针接收者获得函数:

go
(*T).Mv

生成表示 Mv 的函数签名:

go
func(tv *T, a int) int

这样的函数会通过接收者间接的创建一个值作为接收者传入底层方法中。方法内不能修改接收者的值,因为它的地址是在函数的调用栈里面。

最后一个例子。把值作为接收者函数当做指针作为接收者的方法是非法的,因为指针接收者的方法集中不包含值类型的方法集。

通过函数调用语法从方法中获取函数的值。接收者作为调用函数的第一个参数。给定 f :=T.Mv,f 作为f(t,7) 进行调用而不是 t.f(7)。想创建一个绑定接收者的函数可以使用函数字面值或者方法值。

在接口类型中定义函数获取函数值是合法的。最终的函数调用会使用接口类型作为接收者。

方法值

如果表达式 x 拥有静态类型 T 并且 M 在类型 T 的方法集中。x.M 叫做方法值。方法值 x.M 是一个函数值,这个函数和 x.M 拥有相同的参数列表。表达式 x 在计算方法值时会被保存和计算,这个拷贝的副本会作为任何接下来调用的接收者。

类型 T 可能是接口类型也可能不是接口类型。

与方法表达式中讲过的一样,假设类型 T 有两个方法:接收者类型为 T 的 Mv 和接受者类型为 *T 的 Mp :

go
type T struct {
	a int
}
func (tv  T) Mv(a int) int         { return 0 }  // value receiver
func (tp *T) Mp(f float32) float32 { return 1 }  // pointer receiver

var t T
var pt *T
func makeT() T

表达式:

go
t.Mv

生成一个类型的函数值:

go
func(int) int

以下两种调用是等价的:

go
t.Mv(7)
f := t.Mv; f(7)

相似的,表达式:

go
pt.Mp

生成一个类型的函数值:

go
func(float32) float32

与选择器相同,使用指针调用以值作为接收者的非接口方法会自动将指针解引用:pt.Mv 等价于 (*pt).Mv

与方法调用相同,使用值调用以指针作为接收者的非接口方法会自动对值取址:pt.Mv 等价于 (&pt).Mv

go
f := t.Mv; f(7)   // like t.Mv(7)
f := pt.Mp; f(7)  // like pt.Mp(7)
f := pt.Mv; f(7)  // like (*pt).Mv(7)
f := t.Mp; f(7)   // like (&t).Mp(7)
f := makeT().Mp   // invalid: result of makeT() is not addressable

尽管上面使用的都是非接口类型的例子,不过对于接口类型同样适用。

go
var i interface { M(int) } = myVal
f := i.M; f(7)  // like i.M(7)

index表达式

主要表达式格式:

go
a[x]

可以表示数组元素、数组的指针、切片、字符串或 map 类型 a 索引 x 对应的值。x 称作索引或者 map 的键。遵循以下规则:

如果a不是 map 类型:

  • 索引 x 必须是整型或无类型常量。

  • 常量索引必须是非负数且可以使用 int 类型表示。

  • 无类型的常量索引会作为 int 型的值。

  • 索引 x 的范围在 0<=x<len(a) 内,否则就是越界。

对于数组类型 A:

  • 常量索引必须在合法范围内。

  • 如果 x 在运行时越界会引起运行时恐慌。

  • a[x] 表示数组在索引 x 处的元素。a[x] 的类型就是 A 的元素类型。

对于数组的指针类型:

  • 可以使用 a[x] 表示 (*a)[x]

对于切片类型 S:

  • 如果 x 在运行时越界会引起运行时恐慌。
  • a[x] 表示切片在索引 x 处的元素。a[x] 的类型就是 S 的元素类型。

对于字符串类型:

  • 如果字符串 a 为常量,那么常量索引必须在合法范围内。

  • 如果 x 在运行时越界会引起运行时恐慌。

  • a[x] 表示索引 x 处的非常量字节,它是byte类型。

  • 不能对 a[x] 分配值。

对于 map 类型 M:

  • 必须保证 x 的类型能够给 M 的键分配值。

  • 如果map包含键为 x 的值,a[x] 就是 map 中键 x 对应的值,它的类型就是 M 的元素类型。

  • 如果 map 值为 nil 或不包含这个实体,那么 a[x] 为 M 元素类型的零值。

否则 a[x] 就是非法的。

基于 map[K]V 类型 a 的索引表达式可以使用特殊格式的赋值和初始化语法。

go
v, ok = a[x]
v, ok := a[x]
var v, ok = a[x]

它会额外生成一个无类型的布尔值。如果 ok 是 true,那么代表在map中有该键,如果没有 ok 为 false。

给一个值为 nil 的 map 类型变量赋值会导致运行时恐慌。

切片表达式

切片表达式可以基于字符串、数组、数组指针、切片创建字符串子串或切片。它有两种变体,一种是简单的格式是指定开始和结束位置,完全格式的语法还可以指定容量。

####### 简单切片表达式

对于数组、字符串、指针数组、切片 a,主要表达式:

go
a[low:high]

可以构造字符串子串或切片。索引 lowhigh 决定结果切片中的元素。结果切片的索引从 0 开始,长度为 high - low。从数组切分出的切片 s 拥有类型 []int,长度为 3 ,容积为 4。

go
a := [5]int{1, 2, 3, 4, 5}
s := a[1:4]
go
s[0] == 2
s[1] == 3
s[2] == 4

为了方便起见,索引值都可以缺省。当 low 缺省时默认从 0 开始。当缺 high 缺省时默认的取切片的长度。

go
a[2:]  // same as a[2 : len(a)]
a[:3]  // same as a[0 : 3]
a[:]   // same as a[0 : len(a)]

如果 a 是一个数组指针,那么 a[low:high] 可以表示 (*a)[low : high]

对于数组或者字符串,索引的范围是0<=low<=high<=len(a)。对于切片,最大的索引值可以为切片的容量,而不是切片的长度。常量索引必须为非负数,且能够转换成 int 类型。对于数组或者常量字符串。常量索引值必须在合法范围内。如果2个索引都是常量。low 必须小于 high。如果索引在运行时访问了非法内存,程序会发生运行时恐慌。

除了无类型字符串,对于切片和字符串的操作结果是非常量类型的值,它的类型与运算元相同。如果运算元为无类型字符串,那么结果类型会为 string。如果把数组作为运算元,它必须是可寻址的,并且获得的切片和原数组具有同一元素类型。

如果切片运算元为 nil,那么结果也是 nil。否则结果切片会和运算元共享相同的底层无类型数组。

完全切片表达式

对于数组,数组指针或非字符串切片,主要表达式为:

go
a[low : high : max]

它会构造一个同类型切片,并具有与简单切片表达式的 a[low:high] 相同的长度和元素。另外,它还可以把切片的容量设置为 max - low。这时只有第一个索引可以为缺省值,默认为零。从数组中获得切片以后:

go
a := [5]int{1, 2, 3, 4, 5}
t := a[1:3:5]

切片 t 为 []int 类型,长度为 2,容量为 4,并且元素为:

go
t[0] == 2
t[1] == 3

和简单切片表达式一样,如果 a 是数组指针 ,那么 a[low:high:max] 可以简写为 (*a)[low:high:max]。如果切分操作元是数组,那么这个数组必须是可以寻址的。

如果索引必须在 0 <= low <= high <= max <= cap(a) 范围内。常量索引不能是负数并且能够使用 int 类型表示;对于数组,索引必须在合法范围内。如果有多个索引都是常量的,那么所有索引都需要在合法范围内。如果索引是非法的,会引起运行时恐慌。

类型断言

对于接口类型 x 和类型 T,主要表达式:

go
x.(T)

可以断言 x 不是 nil 且 x 的值是 T 类型。标记 x.(T) 叫做类型断言。

更确切的说,如果 T 不是接口类型,那么 x.(T) 将会断言动态类型 x 的类型是不是 T。

这时,T 必须实现了 x 的(接口)类型。否则断言会是非法的因为 x 不能保存 T 类型的值。如果 T 是接口类型,那么可以断言动态类型 x 是否实现了 T 接口。

如果类型断言成功,表达式的值为 x 的值,但它的类型是T。如果断言失败,将会导致运行时恐慌。换句话说,即使 x 是运行时确定的,x.(T) 也必须是编程时就确认存在的。

go
var x interface{} = 7          // x 拥有动态类型 int 值为 7
i := x.(int)                   // i 为 int 类型值为 7

type I interface { m() }

func f(y I) {
   s := y.(string)        // 非法: 字符串没有实现接口 I (缺少 m 方法)
   r := y.(io.Reader)     // r 拥有接口 io.Reader 所以 y 的动态类型必须同时实现 I 和 io.Reader

}

类型断言可以使用特定格式的赋值和初始化语句。

go
v, ok = x.(T)
v, ok := x.(T)
var v, ok = x.(T)
var v, ok T1 = x.(T)

这时将会额外生成一个无类型的布尔值。如果断言成功,ok返回 true,否则是 false。并且 v 会是 T 类型的零值。这时不会有恐慌发生。

类型转换

类型转换表达式 T(x) 其中 T 代表类型,x 代表可以转换成 T 类型的表达式。

go
Conversion = Type "(" Expression [ "," ] ")" .

如果类型是以 *<- 开头,或以关键字 func 开头并且没有返回值列表,那么它必须用括号括起来避免歧义:

go
*Point(p)        // same as *(Point(p))
(*Point)(p)      // p is converted to *Point
<-chan int(c)    // same as <-(chan int(c))
(<-chan int)(c)  // c is converted to <-chan int
func()(x)        // function signature func() x
(func())(x)      // x is converted to func()
(func() int)(x)  // x is converted to func() int
func() int(x)    // x is converted to func() int (unambiguous)

常量 x 可以在可以用类型 T 表示时自动转换。作为一个特例,整数常量 x 可以转换成字符串类型就和非常量 x 一样。

对常量的转换会生成一个指定类型的常量。

go
uint(iota)               // iota value of type uint
float32(2.718281828)     // 2.718281828 of type float32
complex128(1)            // 1.0 + 0.0i of type complex128
float32(0.49999999)      // 0.5 of type float32
float64(-1e-1000)        // 0.0 of type float64
string('x')              // "x" of type string
string(0x266c)           // "♬" of type string
MyString("foo" + "bar")  // "foobar" of type MyString
string([]byte{'a'})      // not a constant: []byte{'a'} is not a constant
(*int)(nil)              // not a constant: nil is not a constant, *int is not a boolean, numeric, or string type
int(1.2)                 // illegal: 1.2 cannot be represented as an int
string(65.0)             // illegal: 65.0 is not an integer constant

非常量 x 可以在以下情况下转换成类型 T:

  • x 可以给类型 T 赋值

  • 忽略的结构体标签,x 的类型和 T 具有相同的底层类型

  • 忽略的结构体标签,x 的类型和 T 都是指针类型,并且指针所指的类型具有相同的底层类型

  • x 的类型和 T 都是整数或者浮点数类型

  • x 的类型和 T 都是复数类型

  • x 是一个字符串而 T 时字节切片或者 rune 切片

在比较两个结构体类型的时候会忽略结构体标签:

go
type Person struct {
	Name    string
	Address *struct {
		Street string
		City   string
	}
}

var data *struct {
	Name    string `json:"name"`
	Address *struct {
		Street string `json:"street"`
		City   string `json:"city"`
	} `json:"address"`
}

var person = (*Person)(data)  // ignoring tags, the underlying types are identical

这个规则也适用于数字类型与字符串类型间的相互转换。这个转换可能会改变 x 的值并且会增加运行时消耗。包 unsafe 实现了这个功能底层的限制。

数字之间的转换

对于非常量的数字转换,需要遵守以下规则:

  • 在转换整型数字时,如果是一个有符号整型,它是继承有符号的无限精度;否则就不用继承符号。转换时会截断数字以适应类型的大小。例如:如果 v:=uint16(0x10F0),然后 ``uint32(int8(v)) == 0xFFFFFFF0 。类型转换总是生成有效值,并且永远不会溢出。

  • 如果要将浮点数转换成整型,会丢弃小数部分(截断为零)。

  • 如果要将整型或浮点型转换成浮点数类型,或或者一个复数转换成其他复数类型,结果会四舍五入成指定精度。例如: 可以使用超出IEEE-754 32位数的附加精度来存储float32类型的变量x的值,但float32(x)表示将x的值舍入为32位精度的结果。x + 0.1 会使用超过 32 位的精度,而 float32(x+0.1) 不会。

在所有浮点数和复数的非常量转换中,如果结构类型不能成功表示数据,那么结果将会依赖于具体平台实现。

字符串的类型转换
  1. 转换一个有符号或者无符号的整型值会转换成对应的 UTF-8 表示整型值。不在范围内的 Unicode 代码点会转换成 "\uFFFD"。
go
string('a')       // "a"
string(-1)        // "\ufffd" == "\xef\xbf\xbd"
string(0xf8)      // "\u00f8" == "ø" == "\xc3\xb8"
type MyString string
MyString(0x65e5)  // "\u65e5" == "日" == "\xe6\x97\xa5"
  1. 将字节切片转换成字符串类型会生成一个由切片元素组成的字符串
go
string([]byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'})   // "hellø"
string([]byte{})                                     // ""
string([]byte(nil))                                  // ""

type MyBytes []byte
string(MyBytes{'h', 'e', 'l', 'l', '\xc3', '\xb8'})  // "hellø"
  1. 将 rune 切片转换成字符串类型会生成一个由切片元素组成的字符串
go
string([]rune{0x767d, 0x9d6c, 0x7fd4})   // "\u767d\u9d6c\u7fd4" == "白鵬翔"
string([]rune{})                         // ""
string([]rune(nil))                      // ""

type MyRunes []rune
string(MyRunes{0x767d, 0x9d6c, 0x7fd4})  // "\u767d\u9d6c\u7fd4" == "白鵬翔"
  1. 将字符串转换成字节切片会生成由字符串中每个字节组成的切片
go
[]byte("hellø")   // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
[]byte("")        // []byte{}

MyBytes("hellø")  // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
  1. 将字符串转换成 rune 切片会生成由字符串中每个 Unicode 代码点组成的切片
go
[]rune(MyString("白鵬翔"))  // []rune{0x767d, 0x9d6c, 0x7fd4}
[]rune("")                 // []rune{}

MyRunes("白鵬翔")           // []rune{0x767d, 0x9d6c, 0x7fd4}
常量表达式

常量表达式只包含常量运算元并且在编译程序时就已经计算完成。

无类型布尔值,数值和字符串常量都可以当作运算元。除了位置操作符,如果二元运算符石不同类型的常量,操作元,和非布尔值,和即将在接下来出现的:整型,rune,浮点数和复数类型。例如:一个无类型整型常量减去无类型复数常量,结果为复数常量。

一个常量的比较运算会生成无类型的布尔常量。如果左移运算是一个无类型常量,结果会是一个整型常量。它会和原来常量为相同类型。其他与无类型常量的运算都会生成相同类型的结果(布尔值,整型,浮点数,复数,字符串常量)。

go
const a = 2 + 3.0          // a == 5.0   (untyped floating-point constant)
const b = 15 / 4           // b == 3     (untyped integer constant)
const c = 15 / 4.0         // c == 3.75  (untyped floating-point constant)
const Θ float64 = 3/2      // Θ == 1.0   (type float64, 3/2 is integer division)
const Π float64 = 3/2.     // Π == 1.5   (type float64, 3/2. is float division)
const d = 1 << 3.0         // d == 8     (untyped integer constant)
const e = 1.0 << 3         // e == 8     (untyped integer constant)
const f = int32(1) << 33   // illegal    (constant 8589934592 overflows int32)
const g = float64(2) >> 1  // illegal    (float64(2) is a typed floating-point constant)
const h = "foo" > "bar"    // h == true  (untyped boolean constant)
const j = true             // j == true  (untyped boolean constant)
const k = 'w' + 1          // k == 'x'   (untyped rune constant)
const l = "hi"             // l == "hi"  (untyped string constant)
const m = string(k)        // m == "x"   (type string)
const Σ = 1 - 0.707i       //            (untyped complex constant)
const Δ = Σ + 2.0e-4       //            (untyped complex constant)
const Φ = iota*1i - 1/1i   //            (untyped complex constant)

对一个无类型整数,rune,或浮点数应用内置的 complex 函数会生成无类型的复数常量。

go
const ic = complex(0, c)   // ic == 3.75i  (untyped complex constant)
const = complex(0, Θ)   // iΘ == 1i     (type complex128)

常量表达式总是一个明确的值;中间值和常量自己可以比语言所支持的精度更高,下面的声明是合法的:

go
const Huge = 1 << 100         // Huge == 1267650600228229401496703205376  (untyped integer constant)
const Four int8 = Huge >> 98  // Four == 4                                (type int8)

常量的除法的除数不能为 0:

go
3.14 / 0.0   // illegal: division by zero

定义了类型的常量的精度必须根据常量类型定义。所以下面的常量表达式是非法的:

go
uint(-1)     // -1 cannot be represented as a uint
int(3.14)    // 3.14 cannot be represented as an int
int64(Huge)  // 1267650600228229401496703205376 cannot be represented as an int64
Four * 300   // operand 300 cannot be represented as an int8 (type of Four)
Four * 100   // product 400 cannot be represented as an int8 (type of Four)

补码使用的一元操作符 ^ 对于非常量的匹配模式:补码对于无符号常量为 1,对于有符号和无类型常量为 -1。

go
^1         // untyped integer constant, equal to -2
uint8(^1)  // illegal: same as uint8(-2), -2 cannot be represented as a uint8
^uint8(1)  // typed uint8 constant, same as 0xFF ^ uint8(1) = uint8(0xFE)
int8(^1)   // same as int8(-2)
^int8(1)   // same as -1 ^ int8(1) = -2

实现限制:编译器在处理无类型浮点数和复数时会取近似值;具体请看常量章节。这个取近似值的操作在浮点数在整数上下文时会产生无效值,即使在计算过后是一个整型。

运算优先级

在包级别,初始化的依赖性由变量声明的初始化表达式顺序决定。否则,当计算表达式内的操作数时,赋值,返回语句,所有函数调用,方法调用,和通信操作都会由左向右计算。

例如,在函数作用域中的赋值:

go
y[f()], ok = g(h(), i()+x[j()], <-c), k()

函数调用和通信的发生顺序为:f()h()i()j()<-cg()k()。但是对 y 和 x 的取值操作没有指定。

go
a := 1
f := func() int { a++; return a }
x := []int{a, f()}            // x may be [1, 2] or [2, 2]: evaluation order between a and f() is not specified
m := map[int]int{a: 1, a: 2}  // m may be {2: 1} or {2: 2}: evaluation order between the two map assignments is not specified
n := map[int]int{a: f()}      // n may be {2: 3} or {3: 3}: evaluation order between the key and the value is not specified

在包级别,依赖的初始化顺序会覆盖这个从左向右的规则:

go
var a, b, c = f() + v(), g(), sqr(u()) + v()

func f() int        { return c }
func g() int        { return a }
func sqr(x int) int { return x*x }

// functions u and v are independent of all other variables and functions

语句

语句控制程序的执行。

go
Statement =
	Declaration | LabeledStmt | SimpleStmt |
	GoStmt | ReturnStmt | BreakStmt | ContinueStmt | GotoStmt |
	FallthroughStmt | Block | IfStmt | SwitchStmt | SelectStmt | ForStmt |
	DeferStmt .

SimpleStmt = EmptyStmt | ExpressionStmt | SendStmt | IncDecStmt | Assignment | ShortVarDecl .

终止语句

终止语句会阻止相同代码块中下面所有语句的执行。以下语句属于终止语句:

  1. returngoto 语句

  2. 对内置 panic 函数的调用

  3. 代码块结束

  4. if 语句中:

  5. else 分支

  6. 所有分支末尾

  7. for语句中:

  8. break 语句和循环结束

  9. switch 语句:

  10. switch 语句中没有 break 语句,

  11. 有一个默认的 case

  12. 语句列表中的每个 case 语句和有可能存在的 fallthrough 语句

  13. select 语句中:

  14. 没有 break 语句

  15. 每个 case 中的语句列表,如果包含默认 case

所有其他语句都不是中断语句。

如果语句序列不为空并且最后一个非空语句是终止语句,那么语句序列就以终结语句结尾。

空语句

空语句不做任何事情。

go
EmptyStmt = .

标签语句

标签语句可以作为 gotobreakcontinue 语句的目标。

go
LabeledStmt = Label ":" Statement .
Label       = identifier .
go
Error: log.Panic("error encountered")

表达式语句

除了特定的内置函数,一般的函数、方法和接收操作都可以出现在表达式语句的上下文中。这些语句可以使用括号括起来。

go
ExpressionStmt = Expression .

下面的内置函数不允许出现在语句的上下文中:

go
append cap complex imag len make new real
unsafe.Alignof unsafe.Offsetof unsafe.Sizeof
go
h(x+y)
f.Close()
<-ch
(<-ch)
len("foo")  // illegal if len is the built-in function

发送语句

发送语句可以向通道发送一个值。通道表达式必须是通道类型,通道方向必须允许发送操作,并且值类型是可以分配给通道元素通道类型。

go
SendStmt = Channel "<-" Expression .
Channel  = Expression .

通道类型和值表达式会在发送之前求值。发送操作会一致阻塞,直到可以进行发送操作。如果接收者已经准备好向没有缓存的通道发送值可以立即执行。如果通道内还有缓存空间,向通道内发送值也会立即执行。向关闭的通道发送数据会导致运行时恐慌。像值为 nil 的通道发送数据会一直阻塞。

go
ch <- 3  // send value 3 to channel ch

递增/递减语句

“++” 和 “--” 语句可以递增或者递减运算元一个无类型常量 1。作为一个赋值语句,运算元必须是可寻址的或者 map 的索引表达式。

go
IncDecStmt = Expression ( "++" | "--" ) .

下面的赋值语句在语义上是等价的:

go
IncDec statement    Assignment
x++                 x += 1
x--                 x -= 1

赋值

go
Assignment = ExpressionList assign_op ExpressionList .

assign_op = [ add_op | mul_op ] "=" .

所有左侧运算元都必须是可寻址的、map 索引表达式或空标识符其中之一。运算元可以用括号括起来。

go
x = 1
*p = f()
a[i] = 23
(k) = <-ch  // same as: k = <-ch

对于赋值操作 x op= y 其中 op 为二元运算符,它和 x=x op (y) 是等价的,不过它只计算一次 x。op= 是单独的一个词汇单元,在赋值操作中左侧表达式和右侧表达式必须都是单值表达式,并且左侧表达式不能是空白标识符。

go
a[i] <<= 2
i &^= 1<<n

元祖赋值语句会把运算返回的多个值分别分配给变量列表。它有两种格式,第一种:它是返回多值的表达式,例如函数调用、通道和 map 运算、类型断言。左侧运算元的数量必须等于返回值的数量。如果函数返回两个值:

go
x, y = f()

它会将第一个返回值分配给 x ,把第二个返回值分配给 y。第二种格式中,左侧运算元的数量必须等于右侧运算元的数量。每个表达式都只能返回单一值,右侧第 n 个值会赋值给左侧第 n 个变量。

go
one, two, three = '', '', ''

空标识符可以在分配时忽略一个右面位置的表达式:

go
_ = x       // evaluate x but ignore it
x, _ = f()  // evaluate f() but ignore second result value

赋值分为两个阶段。首先会计算左侧运算元的索引表达式和指针的解引用工作并以一定顺序计算右侧表达式的值。

然后依次对左侧运算元赋值。

go
a, b = b, a  // exchange a and b

x := []int{1, 2, 3}
i := 0
i, x[i] = 1, 2  // set i = 1, x[0] = 2

i = 0
x[i], i = 2, 1  // set x[0] = 2, i = 1

x[0], x[0] = 1, 2  // set x[0] = 1, then x[0] = 2 (so x[0] == 2 at end)

x[1], x[3] = 4, 5  // set x[1] = 4, then panic setting x[3] = 5.

type Point struct { x, y int }
var p *Point
x[2], p.x = 6, 7  // set x[2] = 6, then panic setting p.x = 7

i = 2
x = []int{3, 5, 7}
for i, x[i] = range x {  // set i, x[2] = 0, x[0]
	break
}
// after this loop, i == 0 and x == []int{3, 5, 3}

在赋值语句中每个值都必须能分配给左侧指定类型的值。除了以下特例:

  1. 任何类型都能分配给空标识符。

  2. 如果把无类型常量分配给接口类型或者空标识符,它会转换成默认类型。

  3. 如果无类型的布尔值分配给了接口类型或者空标识符,它会先转换成 bool 类型。

if 语句

if 语句根据布尔值表达式的值来决定执行条件分支的代码。如果表达式为真,就执行 if 分支内的代码,否则执行 else 分支的代码。

go
IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] .
go
if x > max {
	x = max
}

表达式可能先于普通语句,它会在表达式求值之前发生。

go
if x := f(); x < y {
	return x
} else if x > z {
	return z
} else {
	return y
}

switch 语句

for 语句

for 语句可以用来重复执行一段代码。它有三种格式:迭代器可以是单一条件、for 分句或者 range 语句。

go
ForStmt = "for" [ Condition | ForClause | RangeClause ] Block .
Condition = Expression .
单一条件的 for 语句

这种情况下 for 会在条件为 true 时一直重复。条件会在每次迭代时都重新计算。如果没有指定条件,默认一直为 true。

go
for a < b {
	a *= 2
}
带分句的 for 语句

带分句的 for 语句也是由条件控制,只是它有一个初始化和寄送的过程。例如赋值、递增或者递减语句。初始化语句可以是短变量声明,但是寄送语句不能。在初始化语句中声明的变量可以在迭代过程中使用。

go
ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] .
InitStmt = SimpleStmt .
PostStmt = SimpleStmt .
go
for i := 0; i < 10; i++ {
	f(i)
}

如果初始化语句非空,它会在进入迭代前执行一次;post 语句在每次循环后都会执行一次。在只有条件的情况下可以省略分号。如果缺省条件语句,默认为 true。

go
for cond { S() }    is the same as    for ; cond ; { S() }
for      { S() }    is the same as    for true     { S() }
带 range 分句的 for 语句

带 range 分句的 for 语句可以访问数组、切片、字符串、map 的所有元素,还可以从通道中接收值。迭代获得元素分配给了相应的迭代变量并执行代码块。

go
RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .

右侧的 range 分句表达式叫做 range 表达式,它可能是数组、数组的指针、切片、字符串、map 或通道接收者类型。在分配时,左侧运算元必须是可寻址的或者 map 的索引表达式;它们作为迭代变量。如果 range 表达式是一个通道类型,至少需要有一个变量,它也可以有两个变量。如果迭代变量是空标识符,就代表在分句中不存在该标识符。

go
Range expression                          1st value          2nd value

array or slice  a  [n]E, *[n]E, or []E    index    i  int    a[i]       E
string          s  string type            index    i  int    see below  rune
map             m  map[K]V                key      k  K      m[k]       V
channel         c  chan E, <-chan E       element  e  E
go
var testdata *struct {
	a *[7]int
}
for i, _ := range testdata.a {
	// testdata.a is never evaluated; len(testdata.a) is constant
	// i ranges from 0 to 6
	f(i)
}

var a [10]string
for i, s := range a {
	// type of i is int
	// type of s is string
	// s == a[i]
	g(i, s)
}

var key string
var val interface {}  // element type of m is assignable to val
m := map[string]int{"mon":0, "tue":1, "wed":2, "thu":3, "fri":4, "sat":5, "sun":6}
for key, val = range m {
	h(key, val)
}
// key == last map key encountered in iteration
// val == map[key]

var ch chan Work = producer()
for w := range ch {
	doWork(w)
}

// empty a channel
for range ch {}

Go 语句

go 语句会开始在相同地址空间中的单独 goroutine 中调用函数。

go
GoStmt = "go" Expression .

表达式必须是函数或者方法调用;它不能使用括号括起来,调用内置函数有表达式语句的限制。

函数的值和参数会按顺序在调用的 goroutine 中求值。不像普通的函数调用,程序不会等待函数调用完成,而是直接开启一个新的 goroutine 执行函数。函数退出时,goroutine 也会退出。函数的任何返回值都会被丢弃。

go
go Server()
go func(ch chan<- bool) { for { sleep(10); ch <- true }} (c)

select 语句

select 语句会在接收/发送操作集中选择一个执行。它看起来和 switch 很像,只不过是专门针对通信操作的。

go
SelectStmt = "select" "{" { CommClause } "}" .
CommClause = CommCase ":" StatementList .
CommCase   = "case" ( SendStmt | RecvStmt ) | "default" .
RecvStmt   = [ ExpressionList "=" | IdentifierList ":=" ] RecvExpr .
RecvExpr   = Expression .

接收表达式可以将接收表达式的值分配给一个或两个变量。接收表达式必须是一个接收运算元(可以使用括号括起来)。它最多允许有一个 default 语句。

select 语句执行以下几个步骤:

  1. 对于 select 语句的所有分句,接收操作的通道运算元、通道、发送语句的右侧表达式都会执行一次操作。

  2. 如果一个或多个通信同时发生,它会通过一致性随机选择一个执行。如果没有 default 语句,select 语句会一直阻塞。

  3. 除了 default 分句,其他分句只有在开始进行通信的时候才会执行。

  4. 如果 select 分句是一个接收语句,它可以给变量分配值。

  5. 执行 select 分句内的内容。

如果向 nil 通道发送信息在没有 default 分句的情况下会一直阻塞。

go
var a []int
var c, c1, c2, c3, c4 chan int
var i1, i2 int
select {
case i1 = <-c1:
	print("received ", i1, " from c1\n")
case c2 <- i2:
	print("sent ", i2, " to c2\n")
case i3, ok := (<-c3):  // same as: i3, ok := <-c3
	if ok {
		print("received ", i3, " from c3\n")
	} else {
		print("c3 is closed\n")
	}
case a[f()] = <-c4:
	// same as:
	// case t := <-c4
	//	a[f()] = t
default:
	print("no communication\n")
}

for {  // send random sequence of bits to c
	select {
	case c <- 0:  // note: no statement, no fallthrough, no folding of cases
	case c <- 1:
	}
}

select {}  // block forever

return 语句

return 语句会终止函数 F 的执行并可选的返回一个或多个返回值。所有的滞后函数都会在 F 返回到它的调用者之前执行。

go
ReturnStmt = "return" [ ExpressionList ] .

如果函数没有返回值类型,return 不能返回任何值。

go
func noResult() {
	return
}

有三种方式能够返回指定类型的值:

  1. 返回值可以直接在 return 语句中列出。每个表达式都必须返回一个值并且能够分配给相应的返回值类型。
go
func simpleF() int {
	return 2
}

func complexF1() (re float64, im float64) {
	return -7.0, -4.0
}
  1. return 语句的表达式列表可以是一个返回多值的函数调用。这时会使用临时变量来获取函数调用的返回值并直接将其作为 return 语句的表达式列表。
go
func complexF2() (re float64, im float64) {
	return complexF1()
}
  1. 如果制定了返回值的标识符那么 return 的表达式列表可以为空。返回值参数会作为普通的本地变量按需分配。return 语句会直接返回它们。
go
func complexF3() (re float64, im float64) {
	re = 7.0
	im = 4.0
	return
}

func (devnull) Write(p []byte) (n int, _ error) {
	n = len(p)
	return
}

不管如何声明,所有的返回值都会在进入函数前提前初始化成类型的零值。return 语句会在所有 defer 函数之前指定返回值。

实现限制:编译器不允许在覆盖了命名返回值的作用域中直接返回。

go
func f(n int) (res int, err error) {
	if _, err := f(n-1); err != nil {
		return  // invalid return statement: err is shadowed
	}
	return
}

break 语句

break 语句会在 forswitchselect 语句内部退出到相同函数的某个位置。

go
BreakStmt = "break" [ Label ] .

如果想指定标签,它必须出现在它所中止的 forswitchselect 语句旁。

go
OuterLoop:
	for i = 0; i < n; i++ {
		for j = 0; j < m; j++ {
			switch a[i][j] {
			case nil:
				state = Error
				break OuterLoop
			case item:
				state = Found
				break OuterLoop
			}
		}
	}

continue 语句

continue 语句会提前 for 语句的下一次迭代。for 语句必须和 continue 在相同函数中。

go
RowLoop:
	for y, row := range rows {
		for x, data := range row {
			if data == endOfRow {
				continue RowLoop
			}
			row[x] = data + bias(x, y)
		}
	}

goto 语句

goto 会将程序跳转到相同函数的指定标签处。

go
GotoStmt = "goto" Label .
go
goto Error

goto 语句不允许跳过作用域内程序变量的初始化工作。

go
goto L  // BAD
	v := 3
L:

上面的程序是错误的,因为它跳过了变量 v 的初始化过程。

go
if n%2 == 1 {
	goto L1
}
for n > 0 {
	f()
	n--
L1:
	f()
	n--
}

标签作用域外的 goto 语句不能跳转到标签处,所以上面的代码是错误的。

Fallthrough 语句

fallthrough 语句会跳转到 switch 语句中的下一个 case 分句中。它应该只在最后一个非空分句中使用。

go
FallthroughStmt = "fallthrough" .

Defer 语句

defer 语句会在包裹函数返回后触发函数调用。这里的返回泛指函数因为 return 语句终止、到达函数末尾或者当前 goroutine 触发运行时恐慌。

go
DeferStmt = "defer" Expression .

表达式必须是函数或者方法调用;它不能使用括号括起来,调用内置函数会有一些限制。

每次执行 defer 语句执行时都会计算函数的参数和值,但是并不会调用函数。相反,函数的调用是在包裹函数返回后进行,它们的执行顺序与声明顺序正好相反。如果 defer 对应的函数值为 nil,会在调用函数的时候导致运行时恐慌而不是声明 defer 语句的时候。

例如:当 defer 函数为函数字面值且包裹函数具有命名结果值,此时,我们在defer 函数中可以访问和修改命名的结果值。defer 函数的所有返回值都会被忽略。

go
lock(l)
defer unlock(l)  // unlocking happens before surrounding function returns

// prints 3 2 1 0 before surrounding function returns
for i := 0; i <= 3; i++ {
	defer fmt.Print(i)
}

// f returns 1
func f() (result int) {
	defer func() {
		result++
	}()
	return 0
}

基于 MIT 许可发布