一、:= 短变量声明的细节
1.作用域规则

2.重复声明规则

3类型推断

二、 = 赋值操作的细节
1.类型必须匹配

2.多重赋值特性

三、 零值初始化细节
1.Go为所有类型提供零值

四、指针赋值的细节
1.基本指针操作

理解这段代码的关键在于搞清楚两个概念:普通变量 、指针变量,以及它们之间的关系。
用"门牌号"和"房子"来理解
-
变量
x就像是一栋 房子 ,里面住着数字10。 -
指针变量
p就像是一张 纸条 ,上面写着这栋房子的 门牌号(内存地址)。
现在看代码的每一步:
var x int = 10
var p *int = &x
1.建房子 :x 盖好了,里面放了 10。
2.写纸条 :p 是张空白纸条,&x 就是去查 x 的门牌号,然后把门牌号 写 到 p 的纸条上。
*p = 20
3.改房子里的东西:
-
*p这个动作,叫 解引用。 -
意思是:拿起
p这张纸条,顺着上面的门牌号找到x这栋房子 ,然后把里面的东西换成20。 -
注意:纸条没变 (还是那个门牌号),但 房子里的东西变了。
fmt.Println(x) // 输出 20
4.结果 :x 这栋房子里的内容已经是 20 了。因为 p 和 x 指向的是同一栋房子 ,所以通过 p 改,x 自己也会变。
为什么 var q *int 危险?
var q *int
// *q = 30 // ❌ 这里会崩溃
-
q是一张 空白纸条 (nil指针)。 -
纸上 没有写任何门牌号。
-
如果执行
*q = 30,就相当于:"拿着一张空白纸条去找房子,强行往里面塞东西。"
你根本不知道那个房子在哪儿(内存地址无效),操作系统为了保护其他程序的安全,会直接 终止程序(Panic)。
2.结构体指针

五、特殊赋值情况
1.空白标识符 _

2.常量赋值

六、复合类型的赋值细节
1.切片赋值

2.映射赋值

七、 函数相关的赋值细节
1.命名返回值

一旦起了名字,这两个变量在函数一开始就被自动创建并初始化为零值 (
result=0,err=nil)。当你写一个裸
return(后面不带东西)时,Go 就默认:"把当前result和err的值,原样打包返回给调用方。"场景一:
b == 0(出错)
进入函数,Go 自动创建
result=0,err=nil。检测到
b == 0,执行err = errors.New(...)。执行裸
return。此时result还是0,err是新错误。返回:
(0, error)。场景二:
b != 0(正常)
进入函数,Go 自动创建
result=0,err=nil。跳过
if,执行result = a / b。执行裸
return。此时result是计算结果,err还是nil。返回:
(计算结果, nil)。
2.闭包中的变量捕获

为什么错误写法里,所有函数都输出 3?i 不是每次循环都在变吗?
闭包捕获的是变量本身 ,不是当时的值
在错误写法中,
for循环里的i是同一个变量。每次循环,只是修改了这个变量的值(0 → 1 → 2 → 3 循环结束)。
闭包函数
func(){ fmt.Println(i) }记住的是i这个变量本身(门牌号),而不是创建时的数值。等到你真正执行 这些函数时(比如
f := funcs[0]; f()),循环早就结束了,i已经变成了3。
i := i 到底干了什么?
左边的
i:创建一个全新的、局部的变量(作用域仅在本次循环内)。右边的
i:是外层for循环的变量。效果 :把外层
i当前的值 复制 给内层新的i。结果 :闭包捕获的是这个全新的内层变量,它只属于这一次循环,下次循环会再创建另一个新的。
-
funcs := make([]func(), 0)相当于:你买了一个空的 "待办事项清单" 本子。 -
funcs = append(funcs, func(){...})相当于:往这个本子里 "记下一条新任务"。
核心连接点:类型 func()
这两行代码能配合工作的唯一桥梁 就是括号里的类型 func()。
| 代码片段 | 中文直译 | 关键细节 |
|---|---|---|
make([]func(), 0) |
制作一个切片,这个切片只能用来存放 func() 类型的东西。 |
指明了仓库的"货架规格"。 |
func(){ fmt.Println(i) } |
这是一个匿名函数,它的签名正是 func()(无参数、无返回值)。 |
生产了一个符合"货架规格"的货物。 |
append(funcs, ...) |
把货物放到货架上。 | 类型必须严格匹配,否则编译报错。 |
如果把它们拆成三个时间点来看,逻辑会更清晰:
// 第一步:声明与初始化(建立空仓库) // 此时 funcs 的长度为 0,容量为 0,里面一个函数都没有。 funcs := make([]func(), 0) // 第二步:循环制造与入库(append 操作) for i := 0; i < 3; i++ { // 此时生成了一个匿名函数对象(货物) // append 函数检查:这个货物是不是 func() 类型? ✅ 是的。 // 于是 append 将 funcs 扩容,并把货物放在索引位置。 funcs = append(funcs, func() { fmt.Println(i) }) } // 第三步:此时 funcs 变成了一个有 3 个元素的切片 // 结构类似于:[ 函数0 , 函数1 , 函数2 ]
为什么要用 = 接收?
你可能会注意到写的是
funcs = append(...)而不是单纯的append(...)。这是因为 Go 的切片在扩容时,底层数组的地址可能会变 (搬仓库)。
append函数会把新仓库的地址 作为返回值返回。如果你不用funcs =接住它,你就失去新仓库的钥匙了,funcs还是指向那个旧的、没扩容的空仓库。
避免的陷阱

总结:
