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% 看懂):
- 函数定义
(res int):命名返回值,res是函数局部变量 res = 1:给返回值赋值为 1- 注册 defer:
res = 100(暂时不执行) return:准备返回当前res = 1return之后、函数结束前执行 defer- defer 修改
res = 100 - 最终返回修改后的 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 闭包三要素(必须同时满足)
- 函数内部定义匿名函数
- 内部函数使用外部函数的局部变量
- 外部函数返回内部函数
7.2 闭包核心原理(最难懂的点)
| 类型 | 函数结束时局部变量状态 |
|---|---|
| 普通函数 | 局部变量销毁 |
| 闭包 | 局部变量不销毁,被内部函数引用延长生命周期 |
7.3 经典计数器完整拆解
func add() func() int {
i := 0
return func() int {
i++
return i
}
}
执行流程逐步行走:
- 调用
add(),创建局部变量i = 0 - 定义内部匿名函数,引用了变量
i - 返回内部函数,
add函数执行结束 - 正常
i该销毁,但闭包留住了i - 后续每次调用返回的函数,一直使用同一个
i - 实现累加效果
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
九、进阶高频易错点汇总(独家整理)
func() int是完整返回值类型,不是两个东西- 双返回值接收:左边变量数量 = 函数定义返回值数量
- defer 执行时机:return 之后,函数结束前
- 命名返回值可以被 defer 修改,普通返回值不行
- recover 必须放在 defer 匿名函数内
- string 不可修改,只能转切片修改后转回
- 切片打印无逗号是 Go 默认格式,不是分割失败
- 闭包延长变量生命周期,局部变量不会销毁
- map / 切片未
make是nil,直接赋值 panic - nil 只能用于引用类型,基础类型无 nil
- defer 参数在注册时求值,不是在执行时
err == nil表示成功,err != nil表示失败- Go 没有 try-catch,错误靠返回值 + error
switch默认自动 break,不会顺序穿透for range遍历切片/数组时,_占位忽略不需要的索引
浙公网安备 33010602011771号