变量与零值
本教程共 80 篇 · 第 8 篇 · 更新于 2026-07-27 · 约 8 分钟阅读
8. 变量与零值
本节目标:掌握 Go 的两种变量声明方式,理解零值机制,搞清楚变量作用域和短变量声明的陷阱。
什么是变量
变量就是给一块内存起个名字,方便你在程序里存值和取值。
Go 是静态类型语言,每个变量都有确定的类型,类型决定了它能存什么值、占多大内存。一旦声明,类型就不能改了。
var 声明
var 是最基本的变量声明方式,格式是 var 变量名 类型:
var name string // 声明一个 string 类型的变量
var age int // 声明一个 int 类型的变量
var pi float64 // 声明一个 float64 类型的变量
注意 Go 的类型写在变量名后面,和 C/Java 的 int age 相反。这是 Go 刻意的设计,为了从左到右阅读更自然。
声明并赋值
var name string = "Go"
var age int = 18
省略类型
如果声明时直接赋值,可以省略类型,编译器会自动推断:
var name = "Go" // 推断为 string
var age = 18 // 推断为 int
var pi = 3.14 // 推断为 float64
var ok = true // 推断为 bool
多变量声明
同类型多个变量:
var a, b, c int
不同类型多个变量,用括号括起来:
var (
name string
age int
height float64
)
包级和函数级
var 声明可以出现在两个地方:
package main
var globalCount = 0 // 包级变量,整个包都能用
func main() {
var localName = "Go" // 函数级变量,只有 main 里能用
fmt.Println(localName)
}
短变量声明 :=
在函数内部,Go 提供了一种更简洁的写法::=,叫做短变量声明。
func main() {
name := "Go" // 等价于 var name = "Go"
age := 18 // 等价于 var age = 18
fmt.Println(name, age)
}
:= 同时做了声明和赋值,不需要写 var,也不需要写类型。这是 Go 代码里最常见的写法。
Note
:=只能在函数内部使用,不能在包级别用。包级变量必须用var。
多变量短声明
name, age := "Go", 18
x, y := 10, 20
至少一个新变量
:= 要求左边至少有一个是新变量,否则编译报错:
a := 1
a := 2 // 报错:no new variables on left side of :=
但这种情况是可以的:
a := 1
a, b := 2, 3 // b 是新变量,a 被重新赋值
这里 a 已经存在,被赋了新值 2;b 是新声明的变量。只要左边有一个新名字,:= 就合法。
Warning这是个容易踩的坑。有时候你以为在声明新变量,其实是在重新赋值旧变量。如果旧变量在另一个作用域,可能会创建一个同名的新变量,导致逻辑错误。
零值
Go 有一个很重要的设计:变量声明后如果不赋值,会自动有一个零值。
不同类型的零值不同:
| 类型 | 零值 |
|---|---|
int、float64 等数值类型 | 0 |
bool | false |
string | ""(空字符串) |
| 指针、切片、map、channel、函数、接口 | nil |
| 数组 | 每个元素的零值组成 |
func main() {
var num int
var flag bool
var text string
var ptr *int
fmt.Println(num) // 0
fmt.Println(flag) // false
fmt.Println(text) // (空字符串)
fmt.Println(ptr) // <nil>
}
零值机制的好处是:Go 里没有未初始化变量。你不会遇到 C 语言那种「变量里是随机垃圾值」的问题。
TipGo 的零值不是「空」或「无效」,很多类型的零值是可以直接用的。比如
bytes.Buffer的零值就是一个可用的空缓冲区,sync.Mutex的零值就是一个未锁的互斥锁。
变量赋值
赋值用 =:
var name string
name = "Go"
age := 18
age = 20 // 重新赋值
多重赋值
Go 支持同时给多个变量赋值:
a, b := 1, 2
a, b = b, a // 交换 a 和 b 的值
fmt.Println(a, b) // 2 1
变量交换在 Go 里特别简洁,不需要中间变量。
不同类型的变量不能混用
a := 1 // a 是 int
a = "hello" // 报错:cannot use "hello" as int
Go 是静态类型语言,变量类型一旦确定就不能改变。
变量作用域
作用域就是变量能被访问的代码范围。Go 用花括号 {} 来界定作用域。
函数作用域
func main() {
x := 10 // x 只在 main 里能用
if x > 5 {
y := 20 // y 只在这个 if 块里能用
fmt.Println(x, y) // 能访问外层的 x
}
fmt.Println(x) // 正确
// fmt.Println(y) // 报错:y 在这里不可见
}
内层可以访问外层
func main() {
count := 0
for i := 0; i < 3; i++ {
count++ // 可以访问外层的 count
fmt.Println(i, count)
}
fmt.Println(count) // 3
}
外层不能访问内层
func main() {
if true {
secret := 42
}
// secret 在这里不可见
}
包级作用域
var packageVar = "I'm global"
func main() {
fmt.Println(packageVar) // 能访问包级变量
}
func anotherFunc() {
fmt.Println(packageVar) // 也能访问
}
变量必须使用
Go 有一个严格的规定:声明的变量必须被使用,否则编译报错。
func main() {
a := 1
b := 2 // 报错:b declared but not used
fmt.Println(a)
}
这个规则也适用于导入的包。目的是保持代码整洁,避免无用代码堆积。
如果你确实需要声明但暂时不用,可以用空白标识符 _:
func main() {
a := 1
b := 2
_ = b // 「使用」了 b,实际丢弃
fmt.Println(a)
}
常见陷阱
短变量声明遮蔽
var count = 10
func main() {
count := 5 // 声明了一个新的局部 count,遮蔽了外层的
fmt.Println(count) // 5
updateCount()
fmt.Println(count) // 还是 5
}
func updateCount() {
count = 20 // 修改的是包级 count
}
这里 main 里的 count := 5 创建了一个新的局部变量,和外层的 count 完全是两个东西。这种问题很难调试,要注意。
nil 不能用于短声明
x := nil // 报错:use of untyped nil
nil 不属于任何具体类型,编译器无法推断 x 的类型。你得明确指定:
var x *int = nil
var err error = nil
学完这节你能做什么
- 用
var和:=两种方式声明变量 - 理解零值机制,知道每种类型的默认值
- 搞清楚变量作用域的层级关系
- 避开短变量声明的常见陷阱
- 用多重赋值交换变量
下一节讲常量和 iota。