2026-06-07 11:08:10 +08:00
|
|
|
|
---
|
2026-06-07 12:14:39 +08:00
|
|
|
|
tags: [go, golang, go基础语法, 变量]
|
|
|
|
|
|
create time: 2026-06-07 15:00
|
2026-06-07 11:08:10 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
# Go 语言变量
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
## 概述
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
Go 提供三种变量声明方式:`var`、类型推断、`:=` 短声明。本文覆盖零值规则、多变量赋值、生命周期和常见陷阱。
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
## 正文
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 为什么 Go 的变量总有"默认值"?
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
在 C / C++ 中,未初始化的局部变量是**垃圾值**——读它会导致未定义行为。Go 做了一个安全选择:**所有变量都有确定的零值**。
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!note] 📝 Go 零值表
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
| 类型 | 零值 |
|
|
|
|
|
|
|------|------|
|
|
|
|
|
|
| `int`, `int32`, `int64`, `float64` 等数值类型 | `0` |
|
|
|
|
|
|
| `bool` | `false` |
|
|
|
|
|
|
| `string` | `""`(空字符串) |
|
|
|
|
|
|
| 指针、slice、map、chan、func、error | `nil` |
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
```go
|
|
|
|
|
|
var i int // i == 0
|
|
|
|
|
|
var f float64 // f == 0.0
|
|
|
|
|
|
var b bool // b == false
|
|
|
|
|
|
var s string // s == ""
|
|
|
|
|
|
var p *int // p == nil
|
|
|
|
|
|
var m map[string]int // m == nil
|
|
|
|
|
|
var sl []int // sl == nil
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!warning] ⚠️ nil 类型陷阱
|
|
|
|
|
|
> `nil` 不是万能值。不同类型有各自不同的 `nil` 类型:`*int` 的 nil 不等于 `[]int` 的 nil。比较时务必类型一致。
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 三种声明方式
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
#### 方式一:`var` + 类型 + 初始化
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
var a string = "Hello" // 显式类型 + 初始化
|
|
|
|
|
|
var b int // 不初始化 → 零值 0
|
|
|
|
|
|
var c bool // 不初始化 → 零值 false
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
#### 方式二:`var` + 类型推断(省略类型)
|
|
|
|
|
|
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
var d = true // 编译器推断为 bool
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
#### 方式三:`:=` 短声明(最常用)
|
|
|
|
|
|
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
e := 123 // 编译器推断为 int
|
|
|
|
|
|
f := "hello" // 编译器推断为 string
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!tip] 💡 首选 `:=`
|
|
|
|
|
|
> 在函数体内,`:=` 是最常用的声明方式——简洁且不易出错。**但它只能用于函数体内**,不能用于包级别的变量声明。
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!question] ❓ 思考
|
|
|
|
|
|
> 下面这段代码会编译通过吗?为什么?
|
|
|
|
|
|
> ```go
|
|
|
|
|
|
> var x int
|
|
|
|
|
|
> x := 5 // ?
|
|
|
|
|
|
> ```
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 多变量声明与并行赋值
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
Go 支持一行声明多个变量:
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
// 同类型多变量
|
|
|
|
|
|
var a, b, c int
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
// 类型推断
|
|
|
|
|
|
var d, e = 123, "hello"
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
// 短声明(仅限函数内)
|
|
|
|
|
|
f, g := 456, true
|
|
|
|
|
|
```
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
**并行赋值**是 Go 的一大亮点——右侧先全部求值,再一次性赋给左侧:
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
```go
|
|
|
|
|
|
x, y := 1, 2
|
|
|
|
|
|
x, y = y, x // 交换!x 变成 2,y 变成 1
|
|
|
|
|
|
```
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
这也被广泛用于处理多返回值函数:
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
val, err := GetValue()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
// 处理错误
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!note] 📝 并行 vs 顺序
|
|
|
|
|
|
> 在 `a[i], a[j] = a[j], a[i]` 中,Go 保证 `i` 和 `j` 的值在赋值前就被求值好,所以即使 `i == j` 也能正确工作。这是与许多语言的关键区别。
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 空白标识符 `_`
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
当你不需要某个值时,用 `_` 丢弃:
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
```go
|
|
|
|
|
|
_, b := 5, 7 // 丢弃 5,只保留 b == 7
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
_, err := GetValue()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
// 只需要错误,不需要返回值
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!warning] ⚠️ `_` 不是变量
|
|
|
|
|
|
> `_` 是一个**只写占位符**,你不能读取它的值,也不能对它做任何操作。每次使用 `_` 都会创建一个新的丢弃槽。
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 变量声明 vs 赋值
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
var x int
|
|
|
|
|
|
x := 5 // ❌ 编译错误:no new variables on left side of :=
|
|
|
|
|
|
x = 5 // ✅ 正确:给已声明的变量重新赋值
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
关键点:
|
|
|
|
|
|
- `:=` **必须**在左侧引入至少一个新变量
|
|
|
|
|
|
- `=` 只是赋值,不能用来声明新变量
|
|
|
|
|
|
- 在同一代码块中,同名变量不能被 `:=` 重复声明
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 未使用变量的陷阱
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
Go 编译器强制要求:**声明了就必须使用**。
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
```go
|
2026-06-07 11:08:10 +08:00
|
|
|
|
func main() {
|
2026-06-07 12:14:39 +08:00
|
|
|
|
var a string = "abc"
|
|
|
|
|
|
fmt.Println("hello") // ❌ 编译错误:a declared but not used
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
修复方法:使用这个变量,或者用 `_` 替代。
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!note] 📝 全局变量例外
|
|
|
|
|
|
> 包级别声明的变量允许不使用,但函数体内的局部变量不行。这是一个常见的新手坑。
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 生命周期
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
| 作用域 | 生命周期 |
|
|
|
|
|
|
|--------|---------|
|
|
|
|
|
|
| 全局变量(包级) | 程序启动到退出 |
|
|
|
|
|
|
| 局部变量(函数内) | 函数调用开始到结束(除非发生逃逸到堆) |
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
```go
|
|
|
|
|
|
var globalVar int // 程序整个生命周期存在
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
func main() {
|
|
|
|
|
|
localVar := 42 // 函数返回后销毁(可能逃逸到堆)
|
|
|
|
|
|
_ = localVar
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!info] ℹ️ 逃逸分析
|
|
|
|
|
|
> 如果局部变量的地址被返回或被闭包捕获,Go 的逃逸分析会将它分配到**堆**上而非栈上,这样函数返回后它依然有效。具体机制见「逃逸分析」专题。
|