Day 2|变量、控制流与值语义(Go 的核心心智模型) [Go 语言 30 天系统学习计划]

学习目标

理解 Go 的值语义模型(这是后续 80% Bug 的根源)  

掌握变量声明、作用域与生命周期  

搞清楚 forifswitchdefer 的真实行为  

识别并避免新手最常见的 Go 陷阱

一、Go 的核心前提:一切都是“值”

在 Go 中,变量 = 一段内存里的值。  

赋值、传参、返回,默认都是拷贝。

这是 Go 与很多语言(Python / PHP / Java 引用模型)的根本差异。

二、变量声明方式(你需要全认识)

1. 显式声明

var a int = 10

2. 类型推断(最常用)

b := 20

说明:

  • := 只能在函数内部使用
  • 编译期完成类型推断

3. 批量声明

var (
    x int    = 1
    y string = "go"
)

三、值拷贝:赋值的本质

示例 1:基础类型拷贝

func main() {
    a := 10
    b := a
    b = 20

    fmt.Println(a) // 10
    fmt.Println(b) // 20
}

过程说明:

  1. b := a → 把 a 的值复制一份
  2. 修改 b 不影响 a

结论:

赋值 = 拷贝,而不是引用


示例 2:函数参数也是拷贝

func change(x int) {
    x = 100
}

func main() {
    a := 10
    change(a)

    fmt.Println(a) // 10
}

结论:

Go 的函数参数永远是值传递


四、作用域与生命周期

1. 块级作用域

if true {
    x := 10
    fmt.Println(x)
}
// fmt.Println(x) // 编译错误

2. if 的“短变量声明”

if v := getValue(); v > 10 {
    fmt.Println(v)
}
  • v 只存在于 if 语句块中
  • 极常见、极推荐

五、控制流详解

1. if(无括号)

if a > 10 {
    fmt.Println("big")
}

2. for(Go 中唯一的循环)

2.1 经典 for

for i := 0; i < 3; i++ {
    fmt.Println(i)
}

2.2 while 风格

i := 0
for i < 3 {
    i++
}

2.3 无限循环

for {
    break
}

3. switch(Go 的 switch 非常强大)

3.1 普通 switch

switch v := 2; v {
case 1:
    fmt.Println("one")
case 2:
    fmt.Println("two")
default:
    fmt.Println("other")
}

特点:

  • 自动 break
  • case 可写表达式

3.2 无条件 switch(代替 if-else 链)

score := 85

switch {
case score >= 90:
    fmt.Println("A")
case score >= 80:
    fmt.Println("B")
default:
    fmt.Println("C")
}

六、defer:延迟执行(非常重要)

1. 基本用法

func main() {
    defer fmt.Println("world")
    fmt.Println("hello")
}

输出结果:

hello
world

2. defer 的执行顺序(栈)

func main() {
    defer fmt.Println(1)
    defer fmt.Println(2)
    defer fmt.Println(3)
}

输出结果:

3
2
1

结论:

defer 是“后进先出”的栈结构


3. defer + 返回值(高频考点)

func test() int {
    x := 10
    defer func() {
        x = 20
    }()
    return x
}
fmt.Println(test()) // 10

原因:

  • return 先拷贝返回值
  • defer 后执行

七、for + 闭包的经典大坑(必须理解)

错误示例(99% 新手踩过)

func main() {
    for i := 0; i < 3; i++ {
        go func() {
            fmt.Println(i)
        }()
    }
    time.Sleep(time.Second)
}

可能输出:

3
3
3

原因

  • 闭包捕获的是 同一个 i
  • goroutine 执行时,循环已结束

正确写法 1:传参

for i := 0; i < 3; i++ {
    go func(v int) {
        fmt.Println(v)
    }(i)
}

正确写法 2:重新声明变量

for i := 0; i < 3; i++ {
    i := i
    go func() {
        fmt.Println(i)
    }()
}

八、今天必须建立的 5 个认知

  1. Go 的一切默认都是值拷贝
  2. 函数参数不可能“偷偷修改外部变量”
  3. for 是唯一循环,但足够强大
  4. defer 是栈,不是延时回调
  5. 闭包捕获的是变量,不是值

九、自检问题(进入 Day 3 前必须能回答)

  • 为什么 Go 坚持值语义?
  • defer 为什么是 LIFO?
  • 闭包为什么会“取到错误的 i”?
  • switch 为什么比 if-else 更推荐?

预告:Day 3

Day 3|指针与内存模型

你将真正搞清楚:

  • 指针解决的是什么问题
  • 为什么 Go 的指针是“安全的”
  • new / & / nil 的本质区别
  • 项目中哪些地方必须用指针

发表评论