跳到主要内容

Variables(变量)

变量是带有静态类型、其值可以在运行时改变的存储位置。Go 支持使用 var 在包级或函数内声明变量,也支持在函数内部使用 := 完成短变量声明。理解声明、赋值和作用域的区别,是避免遮蔽和类型错误的关键。

使用 var 声明

var 可以显式指定类型,也可以根据初始值推断类型:

package main

import "fmt"

func main() {
var language string = "Go"
var version = 1
var requests int

fmt.Println(language, version, requests)
}

输出如下:

Go 1 0

没有初始值时,变量获得对应类型的零值。多个相关变量可以一起声明:

var width, height int = 1280, 720

var (
host = "127.0.0.1"
port = 8080
)

变量组适合组织相关的包级状态;函数内部通常优先使用靠近首次使用位置的短小声明。

短变量声明

函数内部可以使用 := 同时声明并初始化变量:

package main

import "fmt"

func main() {
name := "Ada"
count, active := 3, true
fmt.Println(name, count, active)
}

:= 不能用于包级。它也不是普通赋值:在同一作用域中,左侧至少要包含一个新变量,否则编译失败。

count := 1
count, err := loadCount() // err 是当前作用域中的新变量
if err != nil {
return
}

这里 count 被重新赋值,err 被新声明。两个变量必须处于同一作用域;外层已经存在同名变量并不代表内层短声明会复用它。

声明与赋值

声明创建变量,赋值修改已有变量:

var total int // 声明
total = 10 // 赋值

name := "Go" // 短变量声明
name = "Golang"

Go 支持多重赋值,右侧表达式会先求值,再把结果赋给左侧:

left, right := "L", "R"
left, right = right, left

函数返回多个值时,可以一次接收:

value, err := loadValue()

如果某个结果确实不需要,可以使用空白标识符 _ 丢弃,但错误返回值通常不应被无理由忽略。

作用域与生命周期

标识符可见范围由声明位置决定:

  • 包级变量可在同一个包的所有文件中访问;首字母大写时还可以被其他包引用。
  • 函数参数和返回值名称位于函数作用域内。
  • 函数体、ifforswitch 等代码块会形成更小的作用域。
  • 内层作用域可以声明与外层同名的变量,此时外层变量被遮蔽。
package main

import "fmt"

func main() {
count := 1

if true {
count := 2
fmt.Println(count) // 2:内层变量
}

fmt.Println(count) // 1:外层变量
}

变量的作用域描述名称在源码中何处可见;生命周期描述对应存储在运行时保留多久。二者相关但不等同。编译器可能把函数内变量分配到堆上,以保证被返回指针等值引用时仍然有效;这属于编译器依据逃逸分析作出的存储决定,不改变语言层面的作用域规则。

避免意外遮蔽

错误处理中的短声明容易产生遮蔽:

var result string

if result, err := loadResult(); err != nil {
return
} else {
fmt.Println(result) // 这里是 if 初始化语句声明的 result
}

fmt.Println(result) // 外层 result 仍是空字符串

如果目的是更新外层变量,应先声明错误变量,再使用普通赋值:

var result string
var err error

result, err = loadResult()
if err != nil {
return
}

缩小变量作用域通常有利于理解代码,但同名变量表达不同含义时会增加误读风险。应优先选择能区分业务含义的名称。

包级变量与初始化

包级变量在包初始化阶段求值,可以由常量、表达式或函数调用初始化:

package config

import "os"

var Environment = os.Getenv("APP_ENV")

包级可变状态会被包内多个调用方共享,可能增加测试隔离和并发同步成本。配置、连接和服务依赖通常更适合由 main 显式创建,再通过参数传入。确实需要共享变量时,应说明所有权、初始化时机和并发访问规则。

赋值会复制值

Go 的赋值会复制变量当前保存的值,但复制后的行为取决于类型内部表示:

package main

import "fmt"

func main() {
a := [2]int{1, 2}
b := a
b[0] = 9
fmt.Println(a, b) // [1 2] [9 2]

x := []int{1, 2}
y := x
y[0] = 9
fmt.Println(x, y) // [9 2] [9 2]
}

数组赋值复制所有元素;切片赋值复制切片描述符,两个切片可能仍引用同一个底层数组。映射、指针、接口和通道也有各自的复制与共享规则,不能简单归纳成“对象按引用传递”。Go 的参数传递同样始终是值传递,只是被复制的值可能包含指向共享数据的引用。

常见错误

  • 在包级使用 :=;包级声明必须使用 varconsttype 或函数声明。
  • 在同一作用域中使用 :=,但左侧没有任何新变量。
  • 在内层代码块意外声明同名变量,导致外层变量没有被更新。
  • 认为声明但未初始化的变量处于“未定义”状态;Go 会赋予零值。
  • 假设赋值总会深拷贝,或反过来认为所有值都按引用共享。
  • 使用包级可变变量保存请求数据,却没有同步和生命周期边界。

与 TypeScript 的关键差异

  • Go 的 var 与 TypeScript 的 var 没有对应关系;Go 不存在 JavaScript 式变量提升语义。
  • Go 使用词法块作用域,:= 是声明语法,不是 JavaScript/TypeScript 运算符。
  • Go 变量始终有静态类型和零值,不会自然进入 undefined 状态。
  • Go 参数始终按值传递;切片或映射可能共享底层数据,但这不等同于 JavaScript 对象模型。

涉及类型定义、可赋值性和底层类型时,继续阅读 Type(类型)

小结

  • var 可用于包级和函数内,:= 只能用于函数内部。
  • 声明创建变量,赋值更新变量;短声明左侧必须至少有一个新变量。
  • 代码块形成作用域,内层同名声明会遮蔽外层变量。
  • 包级变量会引入共享状态,应明确初始化、所有权和并发规则。
  • 赋值始终复制值,但切片等值内部可能仍引用共享数据。