目 录CONTENT

文章目录
go

【Go】Go的变量和作用域

hxuanyu
2025-12-19 / 0 评论 / 0 点赞 / 69 阅读 / 0 字

Go 拥有强大的静态类型系统,涵盖基本类型、复合类型、指针类型、函数类型、接口类型、通道类型等。Go 语言的预定义类型(含别名与预声明接口)如下:

类型分类类型描述
基本类型bool布尔类型,取值为truefalse
int有符号整数类型,位宽与平台相关(32/64 位)
int88 位有符号整数
int1616 位有符号整数
int3232 位有符号整数
int6464 位有符号整数
uint无符号整数类型,位宽与平台相关(32/64 位)
uint88 位无符号整数
uint1616 位无符号整数
uint3232 位无符号整数
uint6464 位无符号整数
uintptr可容纳指针值的无符号整数类型(用于底层编程/unsafe 场景)
float3232 位浮点数
float6464 位浮点数
complex64复数类型(由两个float32组成)
complex128复数类型(由两个float64组成)
复合/其他预定义类型byteuint8的别名,常用于表示字节
runeint32的别名,表示 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 将类型放在变量名之后,带来两点好处:

  1. 避免类型前置在复杂声明中造成歧义。例如 C 中 int* a, b; 实际声明了指针 a 与整型 b,容易误读。Go 中写作 var a, b *int,含义更清晰。
  2. 更容易表达与解析复杂类型声明。

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 源码,承载预声明标识符(如 intlentruenil 等)。
  • 包代码块(package block) :每个包对应一个隐式包代码块,包含该包内所有源文件的包级声明。
  • 文件代码块(file block) :每个源文件对应一个文件代码块,包含该文件的 import 声明等文件级信息(导入名的可见性受文件范围影响)。
  • 控制语句相关的隐式代码块ifforswitch 等语句的某些部分可视为位于各自的隐式作用域中(例如 if init; condition {}init 声明的变量作用域覆盖整个 if/else if/else 链)。
  • switch /****select 子句代码块casedefault 各自形成独立代码块。

标识符的作用域是编译期概念:指其声明之后在源码中可被引用的区域。通常可用“代码块层次”来理解:外层代码块声明的标识符,在其内层代码块中可见;内层可声明同名标识符遮蔽外层同名标识符。

  • 宇宙代码块中的标识符是 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 语句。
0
博主关闭了所有页面的评论