Go 语言学习进阶版

Go 进阶核心知识

文档说明

本文档汇总 Go 进阶阶段所有难点知识点,100% 无遗漏、超细节、带执行流程、带易错坑、带完整案例。包含:nil 详解、error 错误处理、defer 深度机制、panic/recover、字符串底层、strconv 转换、函数高阶、闭包、命名返回值坑、引用类型本质。

适合:永久复习、面试背诵、彻底打通 Go 进阶难点。

一、nil 超详细讲解

1.1 什么是 nil?

nil 是 Go 语言的空值/零值,代表:没有地址、没有指向、不存在

语言 对应空值
Go nil
Java null
Python None
JS null

1.2 哪些类型可以是 nil?(必考)

只有引用类型 / 特殊类型能为 nil:

  • 指针 *int
  • 切片 []int
  • map
  • 函数类型
  • 接口类型(error 属于接口)
  • channel
var p *int        // nil
var s []int       // nil
var m map[string]int  // nil
var f func()      // nil
var e error       // nil
var ch chan int   // nil

1.3 绝对不能为 nil 的类型(易错)

基础类型没有 nil,只有零值

类型 零值
int 0
string ""
bool false
float64 0.0
// 错误写法,编译报错
var a int = nil   // cannot use nil as type int

// 正确写法
var a int = 0

1.4 工作最常用写法

Go 所有函数错误统一规范:

err == nil   // 没有错误,执行成功
err != nil   // 出现错误,执行失败

二、Go 错误处理 error

2.1 Go 特色机制

Go 没有 try-catch,所有普通错误统一:返回值 + error

所有可预判错误(参数非法、除数为 0、文件不存在)全部用 error 处理。

2.2 两种创建错误方式

方式 1:errors.New(固定错误文本)

import "errors"

errors.New("除数不能为0")

方式 2:fmt.Errorf(支持动态拼接变量)

import "fmt"

fmt.Errorf("非法年龄:%d", age)

2.3 标准双返回值函数模板(必背)

func div(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("除数不能为0")
    }
    return a / b, nil
}
  • 出错:返回零值 + 错误信息
  • 成功:返回结果 + nil

2.4 调用规则(你之前疑惑的点)

res, err := div(10, 0)

重点解释

  • 不是声明两个变量,不是语法特殊
  • div 函数本身定义了两个返回值
    • 第一个返回值:计算结果 int
    • 第二个返回值:错误 error
  • 左边两个变量接收,右边只写一次函数名

三、defer 深度详解(含命名返回值坑)

3.1 核心作用

延迟执行:在当前函数即将结束、return 执行之后执行。

用途:关闭文件、关闭数据库、解锁资源、收尾日志。

3.2 执行顺序规则(必考)

多个 defer:后进先出(栈顺序)

func main() {
    defer fmt.Println("1")
    defer fmt.Println("2")
    defer fmt.Println("3")
}
// 输出:3 2 1

3.3 defer 注册时机与求值时机

  • defer 注册时,参数立即求值
  • defer 执行时,函数体已退出
func deferParams() {
    x := 1
    defer fmt.Println(x)  // 输出 1(注册时 x=1 被捕获)
    x = 2
    // return 之后打印 1
}

3.4 最难点:defer + 命名返回值

func test2() (res int) {
    res = 1
    defer func() {
        res = 100
    }()
    return
}

逐行拆解执行流程(100% 看懂)

  1. 函数定义 (res int):命名返回值,res 是函数局部变量
  2. res = 1:给返回值赋值为 1
  3. 注册 defer:res = 100(暂时不执行)
  4. return:准备返回当前 res = 1
  5. return 之后、函数结束前执行 defer
  6. defer 修改 res = 100
  7. 最终返回修改后的 100

终极结论

  • 普通返回值:defer 改不了返回结果
  • 命名返回值:defer 可以修改最终返回结果

四、panic & recover 崩溃捕获

4.1 panic

主动触发程序致命错误、程序崩溃退出。

使用场景:不可恢复的严重错误(如数组越界、nil 解引用)。

4.2 recover(救命机制)

作用:捕获 panic,阻止程序崩溃。

铁律:recover 只能在 defer 匿名函数中生效。

4.3 完整固定模板(工作必写)

func safeCall() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("捕获崩溃:", err)
        }
    }()

    panic("程序炸了")
}

4.4 error / panic 区别(必背)

类型 适用场景 程序状态
error 可处理的小错误 程序继续运行
panic 致命错误、不可恢复 程序崩溃,需要 recover 抢救

五、字符串 string 深度详解

5.1 底层本质

string不可变字节序列,一旦定义,内容不能修改。

s := "hello"
// 错误写法,编译报错
s[0] = 'a'  // cannot assign to s[0]

5.2 字符串与字节切片互转

// string 转切片(可修改)
bs := []byte("hello")
bs[0] = 'H'

// 切片转回字符串
str := string(bs)

5.3 strings 标准库常用方法

import "strings"

strings.Split("a b c", " ")       // ["a", "b", "c"]
strings.Contains("hello", "ll")   // true
strings.Join([]string{"a","b"}, "-") // "a-b"
strings.ToUpper("hello")          // "HELLO"
strings.ToLower("HELLO")          // "hello"
strings.TrimSpace("  hi  ")       // "hi"
strings.Replace("aaa", "a", "b", 2) // "bba"

5.4 strconv 类型转换

import "strconv"

// int → string
str := strconv.Itoa(123)

// string → int(有 error 返回值)
n, err := strconv.Atoi("123")

// 字符串转浮点数
f, err := strconv.ParseFloat("3.14", 64)

5.5 你之前的疑问:Split 结果为什么没有逗号?

parts := strings.Split("a b", " ")
fmt.Println(parts)  // [a b]  ← 没有逗号
  • 不是分割失败,是正确的
  • 只是 Go 切片默认打印格式用空格分隔
  • 切片内部是两个独立元素,长度为 2,完全正常

六、函数高阶(返回函数语法彻底拆解)

6.1 你看不懂的语法:func add() func() int

重点:整行是一个整体,没有两个返回值!

逐段拆解:

片段 含义
func add() 外层函数,无参数
func() int 唯一的返回值类型(一个函数)

翻译成人话add 函数,返回一个「无参数、返回 int」的函数。

6.2 为什么后面有 int?

因为 func() int完整的函数类型

  • func():代表被返回的函数无参数
  • int:代表被返回的函数返回 int

缺一不可,Go 是强类型语言,必须写全函数签名。

6.3 完整示例

func add() func() int {
    return func() int {
        return 100
    }
}

func main() {
    fn := add()
    fmt.Println(fn())  // 100
}

6.4 匿名函数

没有名字的函数,可以定义即执行或赋值给变量调用。

// 定义并立即执行
func(a, b int) {
    fmt.Println(a * b)
}(3, 5)

// 赋值给变量,重复调用
f := func(x int) {
    fmt.Println(x * x)
}
f(6)

七、闭包终极详解

7.1 闭包三要素(必须同时满足)

  1. 函数内部定义匿名函数
  2. 内部函数使用外部函数的局部变量
  3. 外部函数返回内部函数

7.2 闭包核心原理(最难懂的点)

类型 函数结束时局部变量状态
普通函数 局部变量销毁
闭包 局部变量不销毁,被内部函数引用延长生命周期

7.3 经典计数器完整拆解

func add() func() int {
    i := 0
    return func() int {
        i++
        return i
    }
}

执行流程逐步行走

  1. 调用 add(),创建局部变量 i = 0
  2. 定义内部匿名函数,引用了变量 i
  3. 返回内部函数,add 函数执行结束
  4. 正常 i 该销毁,但闭包留住了 i
  5. 后续每次调用返回的函数,一直使用同一个 i
  6. 实现累加效果
next := add()
fmt.Println(next()) // 1
fmt.Println(next()) // 2
fmt.Println(next()) // 3

next2 := add()      // 新的闭包,i 从 0 开始
fmt.Println(next2()) // 1

7.4 闭包作用

  • 保存私有状态(外部无法直接修改)
  • 实现计数器、ID 生成器
  • 中间件、回调底层核心机制
  • 函数工厂(根据参数生成不同行为的函数)

八、引用类型核心(切片 / map / channel)

8.1 三大引用类型

  • slice(切片)
  • map(映射)
  • channel(通道)

8.2 共同特点

  • 默认零值是 nil
  • 必须 make 初始化后才能使用
  • 赋值、传参是引用传递,多个变量共享同一份底层数据

8.3 经典坑(易错警告)

a := []int{1, 2, 3}
b := a       // b 和 a 指向同一底层数组
b[0] = 100

fmt.Println(a) // [100 2 3]  ← a 也会被修改!
fmt.Println(b) // [100 2 3]

8.4 未初始化直接赋值的错误

var m map[string]int
// m["a"] = 1  // panic: assignment to entry in nil map

// 正确做法:先 make
m := make(map[string]int)
m["a"] = 1

九、进阶高频易错点汇总(独家整理)

  1. func() int 是完整返回值类型,不是两个东西
  2. 双返回值接收:左边变量数量 = 函数定义返回值数量
  3. defer 执行时机:return 之后,函数结束前
  4. 命名返回值可以被 defer 修改,普通返回值不行
  5. recover 必须放在 defer 匿名函数内
  6. string 不可修改,只能转切片修改后转回
  7. 切片打印无逗号是 Go 默认格式,不是分割失败
  8. 闭包延长变量生命周期,局部变量不会销毁
  9. map / 切片未 makenil,直接赋值 panic
  10. nil 只能用于引用类型,基础类型无 nil
  11. defer 参数在注册时求值,不是在执行时
  12. err == nil 表示成功,err != nil 表示失败
  13. Go 没有 try-catch,错误靠返回值 + error
  14. switch 默认自动 break,不会顺序穿透
  15. for range 遍历切片/数组时,_ 占位忽略不需要的索引
posted @ 2026-05-26 15:54  okkk!!!  阅读(21)  评论(0)    收藏  举报