1.3 变量声明
若你习惯 Python 这类动态语言,可能对「先声明、带类型」不太熟悉:在 Go 里,变量必须有确定类型,编译器会在编译期检查,因此使用前需要声明(或短声明),并约定该标识符只能承载对应类型的值。
程序里的数据在内存中,代码通过变量名访问数据,而不是直接操作裸地址(可读性更好,也不容易写错)。Go 还要求:先声明再使用;同一作用域内不能重复声明同名变量;声明出来的变量必须被使用(否则编译报错,避免残留无效代码)。
下面先分清 包级与函数内 能写哪些语句,再归纳 五种常见写法。前两种都是 var;const 与 var 在「能写在哪」上类似,文末有一节简述。
包级与函数内
包级(函数外)指文件顶层:任意 func 的花括号外,与 package、import 同级。
包级
在包级,每条顶层语句必须以关键字开头,例如 package、import、var、const、type、func。因此:
- 声明变量只能用
var(或下面要说的const声明常量),不能使用:=。 - 不能写类似
x = 1当作「第一次出现变量」——那会被当成赋值,而x尚未声明,会报错。
package main
import "fmt"
var buildVersion = "1.0.0" // 类型由右侧推断;包级只能用 var,不能 :=
var (
Debug bool // 零值 false
maxRetries = 3
)
func main() {
fmt.Println(buildVersion, Debug, maxRetries)
}函数内
在 func { ... } 里面:
- 仍可使用
var(规则与包级相同)。 - 还可使用
:=短声明(由编译器推断类型)。 - 对已声明过的名字改值,只用
=(赋值)。
// 在某个 func 体内:
count := 0 // 短声明:仅函数内可用
var done bool // var 也可以
count, done = 1, true // 赋值:两侧标识符必须已经声明过对照
| 位置 | 常用写法 | 备注 |
|---|---|---|
| 包级 | var、var (...) | 禁止 :=;顶层须以关键字起句 |
| 函数内 | var、:=、new(T) 等 | := 左侧至少一个新名;避免无意遮蔽外层 |
遮蔽(shadowing):若包级有 var m = 100,某函数里再写 m := 200,则函数内的 m 是新局部变量,包级 m 在该块内被遮住。为可读性,尽量少内外同名。
var、= 与 :=
var name type:在作用域里声明变量,可只给类型(零值初始化),也可带初始值。=:赋值。左侧必须是已经声明过的变量;不产生新名字。:=:短变量声明,只在函数体内可用;编译器根据右侧推断类型。要求:左侧至少有一个名字对当前块而言是新声明的(同一行里可以混用「新声明 + 对已存在变量赋值」)。
典型对比:
var a, b int = 1, 2 // 声明并初始化
a, b = b, a // 仅是赋值:交换 a、b,没有新变量下面这段则是短声明:x 是新变量,b 是已有变量,在同一行合法(右侧的 b 先参与求值,再赋给左侧的 b)。
b := 1
x, b := 10, b // x 新声明;b 已存在:本行对 b 是赋值,最终 b 为 1(右侧旧值)包级不能写 :=,只能 var / const(见上文「包级与函数内」)。
五种写法速览
| 方式 | 形式要点 | 常见用途 |
|---|---|---|
| 一 | var name type 或带 = 初值 | 包级变量、需要明确类型、零值开始 |
| 二 | var ( ... ) 一组 | 多个相关变量、可读性 |
| 三 | name := expr | 函数内局部变量,类型由右侧推断 |
| 四 | a, b := v1, v2 | 函数内一次声明多个变量 |
| 五 | new(T) | 得到 *T,指向 T 的零值,常用于指针或传给 API |
var 单行
语法:
var <name> <type>var 是关键字;name 为变量名;type 为类型。只写类型时,Go 会做零值初始化:string → "",数值 → 0,bool → false,指针 → nil。
若要声明同时初始化:
var name string = "Go编程时光"完整示例:
package main
import "fmt"
func main() {
var name string = "Go编程时光"
fmt.Println(name)
}从右侧表达式能明显看出是字符串时,可省略类型,由编译器推断:
var name = "Go编程时光"注意:在 Go 里 " 表示 string,' 表示 rune(字符码点),与 Python 里单双引号混用不同,后续在字符串章节会细讲。
若右侧是带小数点的字面量且不写类型,默认推断为 float64。若你希望 float32 等,应显式写出类型,避免隐式变宽类型:
var rate float32 = 0.89与常量:定义常量时把 var 换成 const,同样适用「带类型 / 推断」的写法,例如 const s = "hello"。
var 成组
除了多行 var,可以用括号成组声明,适合一批相关变量,结构更清晰:
var (
name string
age int
gender string
)同样可以混用初始化:
var (
name = "Go"
age = 1
gender string // 仍为零值 ""
)包级变量用 var (...) 很常见;常量则对应 const (...)。
短声明 :=
:= 表示「声明 + 赋值」,类型由右侧表达式推断,等价于带推断的 var,但只能写在函数里。
name := "Go编程时光"等价于:
var name string = "Go编程时光"
// 或
var name = "Go编程时光"函数外若要「声明包级变量」,必须用 var,不能 :=(再次强调::= 仅出现在函数体内)。
多变量 :=
name, age := "wangbm", 28左侧每个标识符在该块中若是首次出现,则作为新变量;若已存在,则本行对该标识符是赋值。因此 := 常用于接收多返回值:
v, ok := m[key]不要与「多重赋值」混淆:下面交换两个已声明的变量,用的是 =,不是 :=,没有新变量产生。
var a int = 100
var b int = 200
a, b = b, a若写成 a, b := b, a,在 a、b 已在本块声明过的情况下,:= 要求至少一个新名字,通常会编译错误;若故意引入新变量(如 a, b := b, a 在嵌套块里产生新的 a),会涉及变量遮蔽(shadowing),新手建议先避免,保持可读性。
new 与指针
变量可分为值变量与指针变量:前者存数据本身,后者存指向数据的地址。取地址用 &:
package main
import "fmt"
func main() {
var age int = 28
var ptr = &age // &变量名:取得 age 的地址
fmt.Println("age: ", age)
fmt.Println("ptr: ", ptr)
}输出中 ptr 形如 0xc000010098(具体数值每次运行可能不同),表示 age 的地址。
new(T) 是内建函数:分配一块可存 T 的内存,初始化为 T 的零值,返回 *T,无需先起临时变量名:
package main
import "fmt"
func main() {
ptr := new(int)
fmt.Println("ptr address: ", ptr)
fmt.Println("ptr value: ", *ptr) // *指针:解引用取值
}此时 *ptr 为 0(int 的零值)。
与「先声明再取址」对比,下面两种思路类似(都返回 *int):
func withNew() *int {
return new(int)
}
func withVar() *int {
var dummy int
return &dummy
}在表达式里需要「某类型的指针且先零值」时,new(T) 很顺手;结构体还常用 复合字面量取址:&T{...}(后续类型章节会见到)。new 不是另一种类型系统,只是分配 + 零值 + 返回指针的语法糖。
选用建议
- 包级变量:只用
var(或const),不要:=。 - 函数内局部变量,且类型明显:优先
:=,代码更短。 - 需要特定类型(如
float32、int64)而字面量会推断成别的:用var x T = ...显式写类型。 - 一组相关零值或初始化:用
var (...)。 - 需要
*T且先零值:考虑new(T)或var x T; return &x,按可读性选。
以上任意方式,同一作用域内同一名字只能声明一次;重复声明会编译报错。
常量 const
常量在「能写在哪」上与 var 一致:包级、函数内都可 const / const (...)(值须编译期可确定)。不能改值;var 可变。
package main
import "fmt"
const defaultTimeout = 30 // 包级常量
func main() {
const localOnly = 1 // 函数内常量,仅在本函数有意义
fmt.Println(defaultTimeout, localOnly)
}iota、枚举式常量等在后面章节讲;常量也常用 const (...) 成组书写。
匿名变量 _
_ 是空白标识符,用于必须接收但不需要使用的值(例如多返回值里丢弃某一侧):
- 不占命名、不引入可引用的变量;
- 可以多次使用
_,不会像普通变量那样「重复声明」报错。
func GetData() (int, int) {
return 100, 200
}
func main() {
a, _ := GetData()
_, b := GetData()
fmt.Println(a, b)
}它解决「必须接收但不用」的场景,与上面五种声明方式不是同一类话题。