Go 的变量、常量与零值

Go 的 var、短变量声明 :=、const 与零值语义;包级与函数内声明差异,:= 重声明规则,以及 iota 枚举入口。

#type / concept #status / growing #tech / dev #resource / go

[!info] 关联笔记

Go 的变量、常量与零值

这个概念为什么会出现

写出第一个有用的程序前,必须回答:

  • 名字如何绑定到可改的存储?
  • 未赋值时内存里是什么——随机垃圾,还是确定默认值?
  • 哪些量在编译期就固定?
  • 函数里如何短促地声明并初始化?

Go 的答案是:变量 + 零值 + 常量三位一体。零值让“忘记初始化”变成可预测状态,而不是 C 系未定义行为;:= 让函数内局部变量声明紧凑;const/iota 承载编译期常量与枚举风格。

[!abstract] 一句话理解 变量用 var:= 声明,未显式赋值时取类型零值;常量用 const 在编译期固定;:= 在同一作用域可对已有变量重声明,但左右至少要有一个新名字。

最小可运行示例

先把示例放进业务场景,再看代码:

场景:订单服务启动时的计数器与配置

电商后台进程启动时要挂几份状态:

  1. 包级配置常量:服务名在编译期就固定,全进程共用
  2. 包级计数器:已处理订单数,启动时从 0 开始
  3. 请求处理函数内的局部变量:当前用户、积分;以及一次“改名 + 错误位”的短声明

零值保证“忘了赋值”时是可预期的空状态,而不是随机垃圾。

package main

import "fmt"

// ServiceName 是进程级配置:编译期固定,不会在运行时被改掉。
// 教学点:const 适合“写死且全进程共享”的名字/版本类配置。
const ServiceName = "order-api"

// processedOrders 是包级可变状态:启动时从零值开始累计。
// 教学点:包级只能用 var,不能用 :=。
var processedOrders = 0

// handleCheckout 模拟一次结账请求里的局部声明。
//
// 业务意图:
// 1. 先拿到“当前用户 / 积分 / 是否就绪”的工作变量;
// 2. 未显式赋值时,零值即可表示“还没填”;
// 3. 用 := 快速声明业务字段,并演示同一作用域的重声明。
func handleCheckout() {
	// var 声明:类型明确,初值取零值。
	// 业务上:count=待处理商品数,customer=客户名,ready=库存是否齐。
	var itemCount int   // 零值 0
	var customer string // 零值 ""
	var ready bool      // 零值 false
	fmt.Println("zero defaults:", itemCount, customer, ready)

	// := 短声明:函数内最常见写法,类型由右侧推导。
	customer = "alice" // 先赋值一次,后面演示重声明
	customer, score := "alice", 100
	fmt.Println("checkout:", customer, score, ServiceName, processedOrders)

	// := 重声明规则:左侧至少有一个“新名字”。
	// 这里 customer 已存在(重声明),err 是新变量。
	// 业务上:把客户名改成最终下单人,并带上一个可忽略的占位 error。
	customer, err := "bob", error(nil)
	fmt.Println("renamed payer:", customer, err)

	// 无类型浮点常量:先以“理想精度”存在,赋给 float64 时再定型。
	// 业务上可类比税率、折扣率这类编译期固定小数。
	const taxRate = 0.06
	var rate float64 = taxRate
	fmt.Println("tax rate:", rate)

	processedOrders++ // 包级变量在函数内可读写
}

func main() {
	handleCheckout()
}

建议运行:

go run .

期望输出:

zero defaults: 0  false
checkout: alice 100 order-api 0
renamed payer: bob <nil>
tax rate: 0.06

结合场景再看三个关注点

  1. 零值即可用
    itemCount/customer/ready 不必写 = 0 / = "" / = false,启动态可预测。

  2. := 只在函数内
    包级 ServiceNameprocessedOrdersconst/var;结账逻辑里才用 :=

  3. 重声明要有新名字
    customer, err := ... 能过编译,是因为同时引入了新的 err;单独 customer := ... 在同一作用域会报错。

核心概念与准确模型

变量声明形态

var x int
var y int = 2
var z = 3          // 推导为 int
var a, b int = 1, 2

var (
	i int
	s string
)

短变量声明:

x := 1
a, b := 1, 2
场景用什么
包级变量var(可分组)
函数内短初始化:= 最常见
需要指定类型零值或清晰类型var x T
不想要零值、立即有值:=var x = ...

规范:Var declarationsShort variable declarations

零值(zero value)

每个类型有零值;变量声明后即有效。

类型族零值
整数 / 浮点 / 复数0
boolfalse
string""
指针、slice、map、channel、func、interfacenil

重要:

  • 零值是确定语义,不是“未定义”
  • 结构体零值=字段各自零值
  • 数组零值=元素全零

nil 在不同类型上能否读/写/调用并不相同,见 nil 的多种形态

参考:The zero value

:= 与重声明(redeclaration)

短变量声明的特殊规则:

  1. 必须在函数内
  2. 左侧非空名字至少有一个在当前作用域是新的
  3. 已在当前作用域声明过的变量会被重声明(类型必须相同),相当于赋值
  4. 多变量时,新旧可以混合——这就是 v, err := f() 在循环里反复写却不炸的原因
x := 1
x, y := 2, 3 // x 重声明(实为赋值),y 新建
// x, y := 4, 5 // 若 x,y 都已存在且无新变量 → 编译错误

注意作用域:内层块的 := 可能阴影外层变量,而不是重声明。

x := 1
if true {
	x := 2 // 新变量,阴影外层
	_ = x
}
// 外层 x 仍是 1

常量 const

const Max = 100
const (
	A = 1
	B = 2
)
const Typed int = 1

要点:

  • 常量表达式在编译期求值
  • 可表示的类型有限:布尔、字符串、数字(及某些安全的表达式)
  • 不能把一般运行期结果(如函数返回的 []int)标成 const
  • 无类型常量可在赋值时更灵活地适配多种数值类型(在表示范围内)

iota(简述 + 链接)

const (
	Sunday = iota // 0
	Monday        // 1
	Tuesday       // 2
)

iota 在 const 块中逐项递增,用于枚举风格常量。完整模式、位掩码、String() 等见 iota 与枚举

赋值与声明不要混

var x int
x = 1     // 赋值
x := 1    // 若 x 已在当前作用域:非法(无新变量)

空白标识符 _ 可接住不要的值:_, err := f()

设计动机

  1. 零值减少未初始化灾难
    让 API 可以依赖“零值可用”(如空 struct、nil slice append)。
  2. 声明语法分层
    包级稳定用 var/const;局部紧凑用 :=
  3. 错误处理人体工学
    a, err := / b, err := 重声明规则降低噪音。
  4. 常量编译期化
    配置上限、枚举、位移旗标可安全折叠。

边界情况与反直觉行为

1. 包级不能 :=

// x := 1 // 包级非法
var x = 1

2. 重声明 vs 阴影

同作用域重声明要求类型相同且至少一新变量;内层 := 常是阴影,外层不变。

3. 零值 map 可读不可写

var m map[string]int
_ = m["k"] // 0
// m["k"] = 1 // panic

4. 零值 slice 可 append

var s []int
s = append(s, 1) // ok

5. 无类型常量 vs 变量

const C = 1
var i int32 = C // ok
var j int32 = i // i 是变量,类型已固定
// var k int64 = j // 需要显式转换

常见误区

[!warning] 常见误区:把零值当成未定义或随机值 错误:来自 C 的“局部变量垃圾值”直觉。
正确:Go 变量总有零值。

[!warning] 常见误区::= 到处用包括包级 错误:包级 :=
正确:包级 var/const

[!warning] 常见误区:以为 := 总是新变量 错误:忽略同作用域重声明与内层阴影。
正确:想改外层变量用 =,或在外层先 var err error

[!warning] 常见误区:nil 一视同仁 错误:所有 nil 都能当空容器用。
正确:按类型查 nil 形态

[!warning] 常见误区:运行期值塞进 const 错误:const x = time.Now()
正确:const 仅编译期常量表达式。

与相邻概念对比

概念差异
类型转换改变值的类型视图;声明是绑定名字
指针变量可取址;指针零值 nil
常量 vs 变量常量不可变、编译期;变量可寻址、可改
iota 枚举const 块技巧,不是新存储类

工程实践

  1. 尽量短作用域
    在靠近使用处声明;利用 if v := ...; ... 限制可见范围。
  2. 错误变量习惯
    函数开头可 var err error,其后 x, err = 避免阴影;或有意识使用 := 重声明规则。
  3. 零值可用设计
    类型的零值最好有意义(文档写清);避免“必须 New 否则炸”的脆弱 API(除非资源句柄)。
  4. 包级状态谨慎
    可导出包级 var 有并发与测试隔离成本;配置优先依赖注入。
  5. 命名
    _ 忽略;不要用 := 制造一堆单字母阴影。
  6. const 块分组
    相关常量放一组;枚举用自定义类型 + iota(专题)。
  7. 测试
    断言零值行为(nil slice JSON、空 string)符合 API 契约。
func parse(s string) (int, error) {
	if s == "" {
		return 0, fmt.Errorf("empty")
	}
	n, err := strconv.Atoi(s)
	if err != nil {
		return 0, fmt.Errorf("parse %q: %w", s, err)
	}
	return n, nil
}

可验证实验

实验 1:打印零值

var 一组基本类型与指针、slice、map,全部打印。

实验 2::= 重声明

a := 1
a, b := 2, 3
// a, b := 4, 5 // 试编译

实验 3:阴影

在 if 块内 x := 修改,块外打印外层 x

实验 4:包级 :=

package 级写 x := 1,阅读错误信息。

实验 5:const 与 iota

写一周枚举,打印数值;尝试 const x = len([]int{1}) 等边界是否合法。

本节总结

  • 本质:名字绑定存储(变量)或编译期值(常量);零值保证可预测默认态。
  • 关键规则var/:=/const 分工;零值表;:= 至少一新变量的重声明;iota 见专题。
  • 最易错:阴影当重声明、包级 :=、nil 一刀切、把零值当未初始化垃圾。
  • 下一步指针流程控制iota

自测题

概念题

  1. 函数内 var x intx := 0 有何相同与不同?
  2. 短变量声明重声明的“至少一个新变量”是什么意思?
  3. 为什么说零值是 Go 的设计特性而不是缺陷?

代码推理题

func main() {
	x := 1
	if x > 0 {
		x, err := 2, error(nil)
		fmt.Println(x, err)
	}
	fmt.Println(x)
}

最后打印的 x 是 1 还是 2?为什么?

工程思考题

循环中多次调用返回 (T, error) 的函数,如何避免 err 被内层阴影导致外层检查到错误的变量?

参考答案

展开
  1. 二者都得到值为 0 的 int:= 必须能推导且仅函数内,var 可显式类型、可用于包级、可不写右侧用零值。
  2. 左侧名字里至少有一个在当前作用域尚未声明,其余已存在者类型相同则重声明(赋值)。
  3. 它提供确定默认态,支持零值可用 API,避免未初始化随机行为。
    代码题:最后是 1,因为 if 内 x, err := 在内层新作用域阴影了 x
    工程题:在外层 var err error,循环内用 v, err = f();或在每次 if v, err := f(); err != nil 立刻处理,不把 err 逃逸到错误作用域。

延伸阅读与资料来源

资料类型支撑
Spec — Variable declarations规范var
Spec — Short variable declarations规范:= 与重声明
Spec — The zero value规范零值
Spec — Constant declarations规范const / iota
Tour — Variables教程声明入门
Tour — Zero values教程零值
Tour — Constants教程常量
Effective Go — Constants官方文档iota 习惯

笔记元信息

  • 建议文件名:go-variables-constants-and-zero-values.md
  • 所属阶段:语言基础起点
  • 学习顺序:基本类型之后
  • 建议下一篇:流程控制指针
  • 本篇状态:已深化(iota 仅简述并链接专题)
创建于 2026/6/20 更新于 2026/7/15