Go 拥有强大的静态类型系统,涵盖基本类型、复合类型、指针类型、函数类型、接口类型、通道类型等。Go 语言的预定义类型(含别名与预声明接口)如下:
| 类型分类 | 类型 | 描述 |
|---|---|---|
| 基本类型 | bool | 布尔类型,取值为true或false |
int | 有符号整数类型,位宽与平台相关(32/64 位) | |
int8 | 8 位有符号整数 | |
int16 | 16 位有符号整数 | |
int32 | 32 位有符号整数 | |
int64 | 64 位有符号整数 | |
uint | 无符号整数类型,位宽与平台相关(32/64 位) | |
uint8 | 8 位无符号整数 | |
uint16 | 16 位无符号整数 | |
uint32 | 32 位无符号整数 | |
uint64 | 64 位无符号整数 | |
uintptr | 可容纳指针值的无符号整数类型(用于底层编程/unsafe 场景) | |
float32 | 32 位浮点数 | |
float64 | 64 位浮点数 | |
complex64 | 复数类型(由两个float32组成) | |
complex128 | 复数类型(由两个float64组成) | |
| 复合/其他预定义类型 | byte | uint8的别名,常用于表示字节 |
rune | int32的别名,表示 Unicode 码点 | |
string | 字符串类型(不可变字节序列) | |
error | 预声明接口类型:type error interface { Error() string } | |
array | 定长数组(如[N]T) | |
struct | 结构体类型 | |
slice | 切片类型(如[]T) | |
map | 字典(哈希表)类型(如map[K]V) | |
| 指针类型 | *T | 指向类型T的指针 |
| 函数类型 | func | 函数类型 |
| 接口类型 | interface | 接口类型 |
| 通道类型 | chan | 通道类型 |
变量声明
标准的变量声明格式如下:
var a int = 10
var:变量声明关键字a:变量名int:变量类型=:赋值操作10:初始值
Go 将类型放在变量名之后,带来两点好处:
- 避免类型前置在复杂声明中造成歧义。例如 C 中
int* a, b;实际声明了指针a与整型b,容易误读。Go 中写作var a, b *int,含义更清晰。 - 更容易表达与解析复杂类型声明。
Go 提供多种变量声明变体。
未显式赋予初值
如果不为变量指定初始值,声明方式为:
var a int
在 C 语言中,局部变量若未初始化,其值通常是未定义的(读取会导致不可预测结果)。Go 会为未显式初始化的变量赋予该类型的零值。
| 类型 | 零值 |
|---|---|
| 所有整型 | 0 |
| 浮点类型 | 0.0 |
| 布尔类型 | false |
| 字符串类型 | "" |
| 指针、接口、切片、通道、字典、函数 | nil |
此外,数组、结构体等复合类型的零值由其各元素/字段的零值递归构成。
变量声明块
Go 支持变量声明块,将多个变量集中在一个 var 关键字之下:
var (
a int = 128
b int8 = 6
s string = "hello"
c rune = 'A'
t bool = true
)
也支持在一行声明并初始化多个相同类型变量:
var a, b, c int = 5, 6, 7
同样的方式可用于声明块:
var (
a, b, c int = 5, 6, 7
d, e, f rune = 'C', 'D', 'E'
)
省略类型信息的声明
Go 允许省略类型信息:
var a = 13
编译器会根据右侧初始值推导类型,并采用对应的默认类型:整数常量默认推导为 int,浮点常量默认推导为 float64,复数常量默认推导为 complex128。如果不希望使用默认类型,可以显式转换:
var b = int32(13)
省略类型信息只适用于“声明同时初始化”,以下写法不合法:
var b
结合多变量声明,还可同时声明不同类型变量:
var a, b, c = 12, 'A', "hello"
短变量声明
Go 提供更简洁的短变量声明:
a := 12
b := 'A'
c := "hello"
也可一次声明多个:
a, b, c := 12, 'A', "hello"
短变量声明省略了 var 与类型,使用 :=,更简洁,但仅适用于函数/方法体等局部作用域,不能用于包级作用域;同时它要求“至少有一个新变量被声明”,否则会编译报错。
Go 的变量大体分为两类:
- 包级变量(package-level variable) :定义在包顶层。若标识符以大写字母开头,则为导出标识符,可被其他包引用,常被视为“全局可见”。
- 局部变量(local variable) :定义在函数或方法内部,仅在其作用域内有效。
包级变量的声明形式
包级变量只能使用
var声明,不能使用短变量声明。形式上可灵活选择是否省略类型信息。
声明并显式初始化
通常采用省略类型信息的格式;若不接受默认类型,更推荐通过显式转换控制类型:
var a = 13 // 使用默认类型 int
var b = int32(17) // 显式指定类型
var f = float32(3.14) // 显式指定类型
声明但延迟初始化
对声明时不立即初始化的包级变量,使用通用形式:
var a int32
var f float64
实践中常应用“声明聚类”与“就近原则”:
- 声明聚类:同一类变量放在一个
var块中,不同类变量分开,提升可读性。 - 就近原则:尽可能靠近第一次使用处声明(在不影响可读性与组织结构的前提下)。
示例(标准库风格):
// $GOROOT/src/net/net.go
var (
netGo bool
netCgo bool
)
var (
aLongTimeAgo = time.Unix(1, 0)
noDeadline = time.Time{}
noCancel = (chan struct{})(nil)
)
另一个“就近原则”的示例:
// $GOROOT/src/net/http/request.go
var ErrNoCookie = errors.New("http: named cookie not present")
func (r *Request) Cookie(name string) (*Cookie, error) {
for _, c := range readCookies(r.Header, name) {
return c, nil
}
return nil, ErrNoCookie
}
这里 ErrNoCookie 虽为包级变量,但与相关方法紧邻放置,便于理解;若某个包级变量在包内被广泛使用,通常更适合放在源文件头部或与其语义最相关的位置。
局部变量声明形式
延迟初始化的局部变量
省略类型信息的 var a = ... 和短变量声明 := 都不支持“只声明不初始化”。需要延迟初始化时只能使用通用形式:
var err error
声明且显式初始化的局部变量
短变量声明是最常见形式。接受默认类型时:
a := 17
f := 3.14
s := "hello, gopher!"
不接受默认类型时,可在右侧显式转换:
a := int32(17)
f := float32(3.14)
s := []byte("hello, gopher!")
短变量声明也常用于控制语句的初始化语句中:
if x, y := computeValues(); x < y {
fmt.Println("x is less than y")
}
for i, j := 0, 0; i < 10; i, j = i+1, j+2 {
fmt.Println(i, j)
}
若局部变量适合聚类声明,更推荐使用 var 声明块:
// $GOROOT/src/net/dial.go
func (r *Resolver) resolveAddrList(ctx context.Context, op, network,
addr string, hint Addr) (addrList, error) {
// ...
var (
tcp *TCPAddr
udp *UDPAddr
ip *IPAddr
wildcard bool
)
// ...
}
变量作用域
在 Go 中,代码块是由一对大括号包围的一系列声明与语句。Go 支持代码块嵌套:
func foo() { // 代码块1
{ // 代码块2
{ // 代码块3
{ // 代码块4
}
}
}
}
像上面这样由成对可见的大括号界定的称为显式代码块(explicit block) 。与之对应,Go 规范中还存在一些隐式代码块(implicit block) ,它们没有独立的大括号边界,不能仅靠观察 {} 直接识别。
- 宇宙代码块(universe block) :包含所有 Go 源码,承载预声明标识符(如
int、len、true、nil等)。 - 包代码块(package block) :每个包对应一个隐式包代码块,包含该包内所有源文件的包级声明。
- 文件代码块(file block) :每个源文件对应一个文件代码块,包含该文件的
import声明等文件级信息(导入名的可见性受文件范围影响)。 - 控制语句相关的隐式代码块:
if、for、switch等语句的某些部分可视为位于各自的隐式作用域中(例如if init; condition {}中init声明的变量作用域覆盖整个if/else if/else链)。 switch/****select子句代码块:case、default各自形成独立代码块。
标识符的作用域是编译期概念:指其声明之后在源码中可被引用的区域。通常可用“代码块层次”来理解:外层代码块声明的标识符,在其内层代码块中可见;内层可声明同名标识符遮蔽外层同名标识符。
- 宇宙代码块中的标识符是 Go 的预声明标识符,它们不是关键字,因此可在更内层作用域中被同名声明所遮蔽。
- 包代码块作用域:包顶层声明的常量、类型、变量、函数对应的标识符。
- 文件代码块作用域:
import导入名等仅在该文件内可见。 - 函数/方法体内的作用域:由语法块与嵌套块界定,例如:
func (t T) M1(x int) (err error) {
m := 13
{ // 代码块2
type bar struct{} // bar 的作用域始于此
{ // 代码块3
a := 5 // a 的作用域始于此
{ // 代码块4
// ...
}
// a 的作用域终止于此
}
// bar 的作用域终止于此
}
// m、t、x、err 的作用域终止于此
}
- 控制语句:例如
if init; cond { ... } else { ... }中在init里声明的变量,其作用域覆盖整个if/else结构;switch init; expr { ... }中init声明的变量作用域覆盖整个switch语句。