Skip to content

GO 基础语法完全教程

📖 关于本教程本教程涵盖 Go 语言从入门到进阶的核心语法知识,配合大量代码示例与注释讲解,适合有一定编程基础的开发者系统学习 Go 语言。


1. Go 代码命名习惯

Go 语言对命名有严格的约定,遵循这些约定是写出地道 Go 代码的第一步。

1.1 基本命名规则

go
// ✅ Go 命名核心原则:
// 1. 首字母大写 = 导出(public),首字母小写 = 未导出(private)
// 2. 使用 驼峰命名法(CamelCase),不使用下划线分隔

// 包名:全小写,简短,不使用下划线
package httputil // ✅ 好
package http_util // ❌ 不推荐

// 变量名:驼峰命名
var userName string    // ✅ 未导出变量
var UserName string    // ✅ 导出变量
var user_name string   // ❌ 不推荐(不使用蛇形命名)

// 常量:驼峰命名(Go 不推荐全大写)
const maxRetryCount = 3       // ✅ 未导出常量
const MaxRetryCount = 3       // ✅ 导出常量
const MAX_RETRY_COUNT = 3     // ❌ 不推荐

// 函数名:驼峰命名
func getUserByID(id int) {}   // ✅ 未导出函数
func GetUserByID(id int) {}   // ✅ 导出函数

// 接口命名:单方法接口通常以 -er 结尾
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Stringer interface {
    String() string
}

// 结构体命名:名词,驼峰
type HttpClient struct {}     // ❌ 应保留缩写词大写
type HTTPClient struct {}     // ✅ 缩写词全大写:HTTP, URL, ID, JSON, API

1.2 常见缩写词的大小写

go
// Go 中缩写词要保持统一大小写:
// ID 不是 Id,URL 不是 Url,HTTP 不是 Http

type UserID int         // ✅
type UserId int         // ❌

func ServeHTTP() {}     // ✅
func ServeHttp() {}     // ❌

var apiURL string       // ✅
var apiUrl string       // ❌

type JSONParser struct{} // ✅
type JsonParser struct{} // ❌

1.3 文件命名

text
文件名规则:
  - 全小写
  - 使用下划线分隔单词
  - 测试文件以 _test.go 结尾

示例:
  user_service.go        ✅ 源码文件
  user_service_test.go   ✅ 测试文件
  userservice.go         ✅ 也可以(简短时不加下划线)
  UserService.go         ❌ 不要大写

2. 基础数据类型及其格式化输出

2.1 基础类型一览

go
package main

import "fmt"

func main() {
    // ==================== 整数类型 ====================
    var i8  int8   = 127          // -128 ~ 127
    var i16 int16  = 32767        // -32768 ~ 32767
    var i32 int32  = 2147483647   // 约 ±21 亿
    var i64 int64  = 9223372036854775807
    var i   int    = 42           // 平台相关:32位系统=int32,64位系统=int64

    // 无符号整数
    var u8  uint8  = 255          // 0 ~ 255(等同于 byte)
    var u16 uint16 = 65535
    var u32 uint32 = 4294967295
    var u64 uint64 = 18446744073709551615
    var u   uint   = 42           // 平台相关

    // ==================== 浮点类型 ====================
    var f32 float32 = 3.14        // 单精度,约 7 位有效数字
    var f64 float64 = 3.141592653589793 // 双精度,约 15 位有效数字

    // ==================== 布尔类型 ====================
    var b bool = true  // 只有 true / false,不能用 0/1 代替

    // ==================== 字符串类型 ====================
    var s string = "Hello, Go!"

    // ==================== 字节和字符 ====================
    var by byte = 'A'   // byte 是 uint8 的别名,存 ASCII
    var r  rune = ''   // rune 是 int32 的别名,存 Unicode 码点

    // ==================== 复数类型(了解即可)====================
    var c64  complex64  = 1 + 2i
    var c128 complex128 = 1 + 2i

    fmt.Println(i8, i16, i32, i64, i, u8, u16, u32, u64, u)
    fmt.Println(f32, f64, b, s, by, r, c64, c128)
}

2.2 格式化输出详解

go
package main

import "fmt"

func main() {
    // ==================== 通用占位符 ====================
    x := 42
    fmt.Printf("%v\n", x)    // 值的默认格式: 42
    fmt.Printf("%+v\n", x)   // 对结构体会打印字段名
    fmt.Printf("%#v\n", x)   // Go 语法表示: 42
    fmt.Printf("%T\n", x)    // 类型: int
    fmt.Printf("%%\n")       // 输出百分号: %

    // ==================== 整数占位符 ====================
    n := 255
    fmt.Printf("%d\n", n)    // 十进制: 255
    fmt.Printf("%b\n", n)    // 二进制: 11111111
    fmt.Printf("%o\n", n)    // 八进制: 377
    fmt.Printf("%O\n", n)    // 带 0o 前缀的八进制: 0o377
    fmt.Printf("%x\n", n)    // 十六进制(小写): ff
    fmt.Printf("%X\n", n)    // 十六进制(大写): FF
    fmt.Printf("%#x\n", n)   // 带 0x 前缀: 0xff
    fmt.Printf("%c\n", 65)   // 对应的字符: A
    fmt.Printf("%U\n", '')  // Unicode 格式: U+4E2D

    // ==================== 浮点数占位符 ====================
    f := 3.141592653589793
    fmt.Printf("%f\n", f)     // 默认 6 位小数: 3.141593
    fmt.Printf("%.2f\n", f)   // 保留 2 位小数: 3.14
    fmt.Printf("%e\n", f)     // 科学计数法: 3.141593e+00
    fmt.Printf("%g\n", f)     // 自动选择 %e 或 %f

    // ==================== 字符串占位符 ====================
    s := "Hello"
    fmt.Printf("%s\n", s)     // 字符串: Hello
    fmt.Printf("%q\n", s)     // 带引号: "Hello"
    fmt.Printf("%x\n", s)     // 十六进制编码: 48656c6c6f

    // ==================== 宽度和对齐 ====================
    fmt.Printf("|%10d|\n", 42)   // 右对齐,宽度 10:  |        42|
    fmt.Printf("|%-10d|\n", 42)  // 左对齐,宽度 10:  |42        |
    fmt.Printf("|%010d|\n", 42)  // 前补零,宽度 10:  |0000000042|

    // ==================== 指针 ====================
    p := &x
    fmt.Printf("%p\n", p)   // 指针地址: 0xc0000b2008
}

3. 进制练习:Excel 一共多少列

💡 题目 Excel 的列名从 A 到 Z,然后是 AA、AB...AZ、BA...ZZ,再然后是 AAA...XFD。Excel 最多支持 16384 列(从 A 到 XFD)。请用 Go 验证这一点,并实现列号与列名的互相转换。

go
package main

import (
    "fmt"
    "strings"
)

// 列号转列名(1-based)
// 本质是 26 进制转换,但没有 "0",所以每次要减 1
func columnNumberToName(n int) string {
    var result []byte
    for n > 0 {
        n-- // 关键:26进制没有0,所以先减1
        remainder := n % 26
        result = append([]byte{byte('A' + remainder)}, result...)
        n /= 26
    }
    return string(result)
}

// 列名转列号
// 相当于 26 进制转 10 进制,A=1, B=2, ..., Z=26
func columnNameToNumber(name string) int {
    name = strings.ToUpper(name)
    result := 0
    for _, ch := range name {
        result = result*26 + int(ch-'A'+1)
    }
    return result
}

func main() {
    // Excel 最大列号:16384
    maxCol := 16384
    maxName := columnNumberToName(maxCol)
    fmt.Printf("Excel 最大列号: %d, 对应列名: %s\n", maxCol, maxName)
    // 输出: Excel 最大列号: 16384, 对应列名: XFD

    // 验证反向转换
    fmt.Printf("列名 XFD 对应列号: %d\n", columnNameToNumber("XFD"))
    // 输出: 列名 XFD 对应列号: 16384

    // 展示各进制表示
    fmt.Printf("16384 的二进制: %b\n", maxCol)   // 100000000000000
    fmt.Printf("16384 的八进制: %o\n", maxCol)    // 40000
    fmt.Printf("16384 的十六进制: %x\n", maxCol)  // 4000
    fmt.Printf("16384 = 2^%d\n", 14)             // 2^14

    // 打印一些示例
    examples := []int{1, 26, 27, 52, 702, 703, 16384}
    for _, num := range examples {
        name := columnNumberToName(num)
        fmt.Printf("列号 %5d -> 列名 %4s -> 反转 %5d\n",
            num, name, columnNameToNumber(name))
    }
    // 列号     1 -> 列名    A -> 反转     1
    // 列号    26 -> 列名    Z -> 反转    26
    // 列号    27 -> 列名   AA -> 反转    27
    // 列号    52 -> 列名   AZ -> 反转    52
    // 列号   702 -> 列名   ZZ -> 反转   702
    // 列号   703 -> 列名  AAA -> 反转   703
    // 列号 16384 -> 列名  XFD -> 反转 16384
}

4. Go 代码注释书写规范

go
// ==================== Go 注释规范 ====================
// Go 使用 godoc 工具自动生成文档,注释即文档。

// Package mathutil 提供常用的数学工具函数。
// 这是包注释,放在 package 声明之前。
// 对于多文件包,只需在一个文件中写包注释(通常是 doc.go)。
package mathutil

// MaxInt 返回两个整数中的较大值。
//
// 使用示例:
//
//	result := MaxInt(3, 5)
//	fmt.Println(result) // 输出: 5
//
// 注意:注释中的代码示例需要缩进一个 Tab(godoc 会识别为代码块)。
func MaxInt(a, b int) int {
    if a > b {
        return a
    }
    return b
}

// UserStatus 表示用户状态的枚举类型。
type UserStatus int

const (
    // StatusActive 表示用户处于活跃状态。
    StatusActive UserStatus = iota
    // StatusInactive 表示用户处于非活跃状态。
    StatusInactive
    // StatusBanned 表示用户已被封禁。
    StatusBanned
)

// User 代表系统中的一个用户。
//
// Name 字段不能为空,Age 字段必须大于 0。
type User struct {
    Name   string     // 用户姓名
    Age    int        // 用户年龄
    Status UserStatus // 当前状态
}

// 注释规范总结:
// 1. 导出的名称(大写开头)必须有注释
// 2. 注释第一个词应该是被注释的名称本身
// 3. 注释用完整的句子,以句号结尾
// 4. 包注释在 package 语句前,说明包的功能
// 5. TODO/FIXME/BUG 标记用法:
//    // TODO(username): 待完成的功能说明
//    // FIXME(username): 需要修复的问题
//    // BUG(username): 已知的缺陷

// Deprecated: OldFunction 已废弃,请使用 NewFunction 代替。
func OldFunction() {}

5. 常量与枚举

5.1 常量定义

go
package main

import "fmt"

// 单个常量
const Pi = 3.14159265358979323846

// 批量定义常量
const (
    StatusOK    = 200
    StatusNotFound = 404
    StatusError    = 500
)

// 常量可以是编译期确定的表达式
const (
    KB = 1024
    MB = KB * 1024     // 1048576
    GB = MB * 1024     // 1073741824
    TB = GB * 1024
)

func main() {
    fmt.Println("1 GB =", GB, "bytes")

    // 常量没有类型时,它是"无类型常量",精度非常高
    const big = 1 << 100       // 这是合法的!
    // fmt.Println(big)        // ❌ 但赋值给变量时溢出
    fmt.Println(big >> 90)     // ✅ 可以参与常量运算:1024
}

5.2 iota 枚举

go
package main

import "fmt"

// iota 是常量计数器,从 0 开始,在 const 块中每新增一行加 1

// 基本枚举
type Weekday int

const (
    Sunday    Weekday = iota // 0
    Monday                   // 1(自动 iota)
    Tuesday                  // 2
    Wednesday                // 3
    Thursday                 // 4
    Friday                   // 5
    Saturday                 // 6
)

// 跳值枚举
type LogLevel int

const (
    DEBUG LogLevel = iota       // 0
    INFO                        // 1
    _                           // 2(用空白标识符跳过)
    WARNING                     // 3
    ERROR                       // 4
)

// 位掩码枚举(常用于权限系统)
type Permission uint8

const (
    Read    Permission = 1 << iota // 1   (001)
    Write                          // 2   (010)
    Execute                        // 4   (100)
)

// iota 表达式
type ByteSize float64

const (
    _           = iota              // 跳过 0
    KiloByte ByteSize = 1 << (10 * iota) // 1 << 10 = 1024
    MegaByte                              // 1 << 20
    GigaByte                              // 1 << 30
    TeraByte                              // 1 << 40
)

func main() {
    fmt.Println("周三:", Wednesday) // 3

    // 位掩码组合权限
    var perm Permission = Read | Write // 3 (011)
    fmt.Printf("权限: %03b\n", perm)   // 011

    // 判断是否有某权限
    hasRead := perm&Read != 0
    hasExec := perm&Execute != 0
    fmt.Printf("可读: %v, 可执行: %v\n", hasRead, hasExec)
    // 可读: true, 可执行: false

    fmt.Printf("1 KB = %.0f bytes\n", float64(KiloByte)) // 1024
    fmt.Printf("1 MB = %.0f bytes\n", float64(MegaByte)) // 1048576
}

6. 结构体及其指针

6.1 结构体定义与使用

go
package main

import "fmt"

// 定义结构体
type Person struct {
    Name string
    Age  int
    City string
}

func main() {
    // ==================== 创建结构体的多种方式 ====================

    // 方式1:字段名赋值(推荐,顺序无关)
    p1 := Person{
        Name: "Alice",
        Age:  30,
        City: "Beijing",
    }

    // 方式2:按顺序赋值(不推荐,加字段会出bug)
    p2 := Person{"Bob", 25, "Shanghai"}

    // 方式3:零值初始化
    var p3 Person // Name="", Age=0, City=""

    // 方式4:使用 new 创建(返回指针)
    p4 := new(Person) // p4 是 *Person 类型
    p4.Name = "Dave"

    // 方式5:取地址创建(最常用,返回指针)
    p5 := &Person{
        Name: "Eve",
        Age:  22,
    }

    fmt.Println(p1, p2, p3, p4, p5)
}

6.2 结构体指针

go
package main

import "fmt"

type User struct {
    Name string
    Age  int
}

// 值接收:修改的是副本,不影响原始数据
func growUp(u User) {
    u.Age++
    fmt.Println("函数内:", u.Age) // 26
}

// 指针接收:修改原始数据
func growUpPtr(u *User) {
    u.Age++ // Go 自动解引用,等价于 (*u).Age++
    fmt.Println("函数内:", u.Age) // 26
}

func main() {
    u := User{Name: "Alice", Age: 25}

    growUp(u)
    fmt.Println("值传递后:", u.Age) // 25(未改变)

    growUpPtr(&u)
    fmt.Println("指针传递后:", u.Age) // 26(已改变)

    // 结构体指针访问字段
    p := &u
    // 以下两种写法等价,Go 会自动解引用
    fmt.Println(p.Name)    // Alice
    fmt.Println((*p).Name) // Alice
}

7. 继承的实现与应用

⚠️ 注意 Go 没有传统 OOP 中的继承(inheritance),而是通过 组合(composition)嵌入(embedding) 来实现代码复用。Go 的设计哲学:组合优于继承

go
package main

import "fmt"

// ==================== "父类":基础结构体 ====================
type Animal struct {
    Name   string
    Age    int
}

func (a Animal) Eat() {
    fmt.Printf("%s is eating\n", a.Name)
}

func (a Animal) Sleep() {
    fmt.Printf("%s is sleeping\n", a.Name)
}

func (a Animal) Info() string {
    return fmt.Sprintf("[%s, age: %d]", a.Name, a.Age)
}

// ==================== "子类":通过嵌入实现继承 ====================
type Dog struct {
    Animal         // 匿名嵌入(类似继承)
    Breed  string  // Dog 自己的字段
}

// Dog 可以有自己的方法
func (d Dog) Bark() {
    fmt.Printf("%s: Woof!\n", d.Name)
}

// 方法重写(Override)
func (d Dog) Info() string {
    // 调用"父类"方法
    base := d.Animal.Info()
    return fmt.Sprintf("%s, breed: %s", base, d.Breed)
}

// 更深的"继承"链
type GuideDog struct {
    Dog
    Owner string
}

func (g GuideDog) Guide() {
    fmt.Printf("%s is guiding %s\n", g.Name, g.Owner)
}

func main() {
    dog := Dog{
        Animal: Animal{Name: "Buddy", Age: 3},
        Breed:  "Golden Retriever",
    }

    // 直接调用"继承"来的方法
    dog.Eat()    // Buddy is eating
    dog.Sleep()  // Buddy is sleeping
    dog.Bark()   // Buddy: Woof!

    // 调用重写的方法
    fmt.Println(dog.Info()) // [Buddy, age: 3], breed: Golden Retriever

    // 仍然可以调用"父类"的原始方法
    fmt.Println(dog.Animal.Info()) // [Buddy, age: 3]

    // 多层"继承"
    guide := GuideDog{
        Dog:   Dog{Animal: Animal{Name: "Rex", Age: 5}, Breed: "Labrador"},
        Owner: "John",
    }
    guide.Eat()   // Rex is eating(穿透两层嵌入)
    guide.Bark()  // Rex: Woof!
    guide.Guide() // Rex is guiding John
}

8. 结构体嵌套的几种形式和场景

go
package main

import "fmt"

// ==================== 形式1:命名嵌套(has-a 关系)====================
// 场景:明确的所属关系,如"用户有一个地址"
type Address struct {
    Province string
    City     string
    Street   string
}

type Employee struct {
    Name    string
    HomeAddr Address  // 命名字段:必须通过字段名访问
    WorkAddr Address
}

// ==================== 形式2:匿名嵌入(is-a 或功能扩展)====================
// 场景:继承行为,字段和方法被"提升"到外层
type Logger struct{}

func (l Logger) Log(msg string) {
    fmt.Println("[LOG]", msg)
}

type Service struct {
    Logger  // 匿名嵌入:Service "is a" Logger
    Name string
}

// ==================== 形式3:指针嵌入 ====================
// 场景:延迟初始化、共享实例、避免拷贝大结构体
type Config struct {
    Debug   bool
    Version string
}

type App struct {
    *Config // 指针嵌入:多个 App 可以共享同一个 Config
    Name    string
}

// ==================== 形式4:接口嵌入 ====================
// 场景:装饰器模式、Mock 测试
type Writer interface {
    Write(data []byte) (int, error)
}

type BufferedWriter struct {
    Writer       // 嵌入接口(而非具体类型)
    bufSize int
}

// ==================== 形式5:自引用(链表、树)====================
type TreeNode struct {
    Value int
    Left  *TreeNode // 必须用指针,否则无限递归
    Right *TreeNode
}

func main() {
    // 形式1:命名嵌套
    emp := Employee{
        Name:     "Alice",
        HomeAddr: Address{Province: "北京", City: "北京", Street: "长安街"},
        WorkAddr: Address{Province: "北京", City: "北京", Street: "中关村"},
    }
    fmt.Println(emp.HomeAddr.City)  // 必须通过 HomeAddr 访问
    fmt.Println(emp.WorkAddr.City)

    // 形式2:匿名嵌入
    svc := Service{Name: "UserService"}
    svc.Log("started") // 直接调用 Logger 的方法:[LOG] started

    // 形式3:指针嵌入
    cfg := &Config{Debug: true, Version: "1.0"}
    app1 := App{Config: cfg, Name: "App1"}
    app2 := App{Config: cfg, Name: "App2"}
    app1.Debug = false
    fmt.Println(app2.Debug) // false(共享同一个 Config)

    // 形式5:自引用
    tree := &TreeNode{
        Value: 1,
        Left:  &TreeNode{Value: 2},
        Right: &TreeNode{Value: 3},
    }
    fmt.Println(tree.Value, tree.Left.Value, tree.Right.Value)
}

选择建议 | 形式 | 使用场景 | |------|---------| | 命名嵌套 | 明确的 has-a 关系,多个同类型字段 | | 匿名嵌入 | 继承行为(方法提升),功能组合 | | 指针嵌入 | 共享实例,延迟初始化,大结构体 | | 接口嵌入 | 装饰器,Stub/Mock,部分实现 | | 自引用 | 树、链表、图等递归数据结构 |


9. 变量作用域

go
package main

import "fmt"

// 包级变量:整个包可见
var packageVar = "I'm package-level"

func main() {
    // 函数级变量:整个函数可见
    funcVar := "I'm function-level"

    {
        // 块级变量:只在当前 {} 内可见
        blockVar := "I'm block-level"
        fmt.Println(packageVar) // ✅
        fmt.Println(funcVar)    // ✅
        fmt.Println(blockVar)   // ✅
    }
    // fmt.Println(blockVar)    // ❌ 编译错误:blockVar 不可见

    // 变量遮蔽(Shadowing)—— Go 中常见的坑!
    x := 10
    fmt.Println("外层 x:", x) // 10

    {
        x := 20 // 这是一个新变量,遮蔽了外层的 x
        fmt.Println("内层 x:", x) // 20
    }
    fmt.Println("外层 x:", x) // 10(未被修改)

    // 经典坑::= 在 if 中创建新变量
    var err error
    if true {
        // 这里 err 是新变量,不是外面那个 err!
        val, err := someFunc()
        fmt.Println(val, err)
    }
    fmt.Println("外层 err:", err) // 仍然是 nil!

    // 解决办法:先声明再赋值
    var val int
    if true {
        val, err = someFunc() // 用 = 而不是 :=
    }
    fmt.Println(val, err)

    _ = funcVar
}

func someFunc() (int, error) {
    return 42, nil
}

10. if 语句及其局部变量

go
package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    // ==================== 基本 if ====================
    age := 20
    if age >= 18 {
        fmt.Println("成年人")
    } else {
        fmt.Println("未成年")
    }

    // ==================== if 带初始化语句(Go 特色)====================
    // 变量 f 只在 if-else 块内可见
    if f, err := os.Open("test.txt"); err != nil {
        fmt.Println("打开失败:", err)
        // f 在这里也可用(虽然可能无效)
    } else {
        fmt.Println("打开成功")
        f.Close()
    }
    // fmt.Println(f)  // ❌ f 在这里不可见

    // ==================== 实际应用:类型转换检查 ====================
    input := "123"
    if num, err := strconv.Atoi(input); err != nil {
        fmt.Println("转换失败:", err)
    } else {
        fmt.Println("转换成功:", num*2) // 246
    }

    // ==================== 多条件 ====================
    score := 85
    if score >= 90 {
        fmt.Println("优秀")
    } else if score >= 80 {
        fmt.Println("良好")
    } else if score >= 60 {
        fmt.Println("及格")
    } else {
        fmt.Println("不及格")
    }

    // Go 中 if 条件不需要括号
    // if (x > 0) {}  // 能编译但不推荐
    // if x > 0 {}    // ✅ 推荐写法
}

11. for 循环的各种变体

go
package main

import "fmt"

func main() {
    // ==================== 形式1:经典三段式 ====================
    for i := 0; i < 5; i++ {
        fmt.Print(i, " ") // 0 1 2 3 4
    }
    fmt.Println()

    // ==================== 形式2:类似 while ====================
    count := 0
    for count < 5 {
        fmt.Print(count, " ") // 0 1 2 3 4
        count++
    }
    fmt.Println()

    // ==================== 形式3:无限循环 ====================
    // for {
    //     // 相当于 while(true)
    //     break // 必须有退出条件
    // }

    // ==================== 形式4:range 遍历 ====================

    // 遍历切片
    nums := []int{10, 20, 30, 40, 50}
    for index, value := range nums {
        fmt.Printf("index=%d, value=%d\n", index, value)
    }

    // 只要索引
    for i := range nums {
        fmt.Println("index:", i)
    }

    // 只要值(用 _ 忽略索引)
    for _, v := range nums {
        fmt.Println("value:", v)
    }

    // 遍历字符串(按 rune,不是按 byte)
    for i, ch := range "Hello你好" {
        fmt.Printf("byte位置=%d, 字符=%c, 码点=%d\n", i, ch, ch)
    }
    // byte位置=0, 字符=H, 码点=72
    // byte位置=5, 字符=你, 码点=20320   ← 注意索引跳了!
    // byte位置=8, 字符=好, 码点=22909

    // 遍历 map
    m := map[string]int{"a": 1, "b": 2, "c": 3}
    for key, value := range m {
        fmt.Printf("%s: %d\n", key, value) // 顺序不确定!
    }

    // 遍历 channel
    ch := make(chan int, 3)
    ch <- 1
    ch <- 2
    ch <- 3
    close(ch)
    for v := range ch {
        fmt.Println(v) // 1, 2, 3
    }

    // ==================== break 和 continue ====================
    for i := 0; i < 10; i++ {
        if i == 3 {
            continue // 跳过 3
        }
        if i == 7 {
            break // 到 7 就停
        }
        fmt.Print(i, " ") // 0 1 2 4 5 6
    }
    fmt.Println()
}

12. label 与 goto

go
package main

import "fmt"

func main() {
    // ==================== Label + break:跳出多层循环 ====================
    // 这是 label 最常见和最推荐的用法

outer:
    for i := 0; i < 5; i++ {
        for j := 0; j < 5; j++ {
            if i == 2 && j == 3 {
                fmt.Println("跳出所有循环")
                break outer // 跳出外层循环
            }
            fmt.Printf("(%d,%d) ", i, j)
        }
        fmt.Println()
    }

    fmt.Println("---")

    // ==================== Label + continue:跳到外层的下一次迭代 ====================
loop:
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            if j == 1 {
                continue loop // 跳到外层循环的下一次迭代
            }
            fmt.Printf("(%d,%d) ", i, j) // 只打印 j=0 的
        }
    }
    fmt.Println()

    // ==================== goto 语句 ====================
    // goto 可以跳转到函数内的任意标签
    // ⚠️ 不推荐使用,难以维护。只在特定场景(如错误清理)有用

    i := 0
start:
    if i >= 5 {
        goto end
    }
    fmt.Print(i, " ")
    i++
    goto start
end:
    fmt.Println("done")

    // goto 的唯一合理场景:多步资源清理(但 defer 通常更好)
    // 注意:goto 不能跳过变量声明
}

⚠️ goto 使用建议在实际项目中,goto 几乎不应该使用。Go 提供了 defer 机制来处理资源清理,配合 break labelcontinue label 已足够处理复杂的流程控制。标准库中 goto 的使用也极其稀少。


13. switch 表达式与 fallthrough

go
package main

import (
    "fmt"
    "runtime"
)

func main() {
    // ==================== 基本 switch ====================
    day := "Monday"
    switch day {
    case "Monday":
        fmt.Println("周一")
    case "Tuesday":
        fmt.Println("周二")
    case "Saturday", "Sunday": // 多值匹配
        fmt.Println("周末")
    default:
        fmt.Println("工作日")
    }

    // Go 的 switch 默认不穿透(不需要 break)

    // ==================== switch 带初始化语句 ====================
    switch os := runtime.GOOS; os {
    case "darwin":
        fmt.Println("macOS")
    case "linux":
        fmt.Println("Linux")
    default:
        fmt.Println(os)
    }

    // ==================== 无表达式 switch(替代 if-else 链)====================
    score := 85
    switch {
    case score >= 90:
        fmt.Println("A")
    case score >= 80:
        fmt.Println("B") // 命中这个
    case score >= 60:
        fmt.Println("C")
    default:
        fmt.Println("F")
    }

    // ==================== fallthrough(强制穿透到下一个 case)====================
    num := 3
    switch {
    case num > 0:
        fmt.Println("正数")
        fallthrough // 无条件执行下一个 case 的代码
    case num > -5:
        fmt.Println("大于 -5") // 也会执行
        // 不再 fallthrough,停止
    case num > -10:
        fmt.Println("大于 -10") // 不执行
    }
    // 输出:
    // 正数
    // 大于 -5

    // ==================== 类型 switch ====================
    var val interface{} = "hello"
    switch v := val.(type) {
    case int:
        fmt.Printf("整数: %d\n", v)
    case string:
        fmt.Printf("字符串: %s\n", v) // 命中
    case bool:
        fmt.Printf("布尔: %v\n", v)
    default:
        fmt.Printf("未知类型: %T\n", v)
    }
}

fallthrough 注意事项 fallthrough无条件穿透,它不会检查下一个 case 的条件是否为 true。fallthrough 不能用在 type switch 中。实际开发中很少使用 fallthrough


14. 位运算及其应用

go
package main

import "fmt"

func main() {
    // ==================== 基本位运算 ====================
    a := uint8(0b_1100_1010) // 202
    b := uint8(0b_1010_1100) // 172

    fmt.Printf("a     = %08b (%d)\n", a, a)
    fmt.Printf("b     = %08b (%d)\n", b, b)
    fmt.Printf("a & b = %08b (AND 按位与)\n", a&b)     // 10001000
    fmt.Printf("a | b = %08b (OR 按位或)\n", a|b)      // 11101110
    fmt.Printf("a ^ b = %08b (XOR 异或)\n", a^b)       // 01100110
    fmt.Printf("^a    = %08b (NOT 取反)\n", ^a)        // 00110101
    fmt.Printf("a &^ b= %08b (AND NOT 位清除)\n", a&^b) // 01000010
    fmt.Printf("a << 2= %08b (左移)\n", a<<2)          // 00101000
    fmt.Printf("a >> 2= %08b (右移)\n", a>>2)          // 00110010

    // ==================== 应用1:权限管理 ====================
    const (
        PermRead    = 1 << iota // 001 = 1
        PermWrite               // 010 = 2
        PermExecute             // 100 = 4
    )

    // 组合权限
    userPerm := PermRead | PermWrite // 011 = 3
    fmt.Printf("\n用户权限: %03b\n", userPerm)

    // 检查权限
    fmt.Println("可读?", userPerm&PermRead != 0)     // true
    fmt.Println("可执行?", userPerm&PermExecute != 0) // false

    // 添加权限
    userPerm |= PermExecute
    fmt.Printf("添加执行后: %03b\n", userPerm) // 111

    // 移除权限
    userPerm &^= PermWrite
    fmt.Printf("移除写入后: %03b\n", userPerm) // 101

    // ==================== 应用2:判断奇偶 ====================
    fmt.Println("\n7 是奇数?", 7&1 == 1) // true(最低位为1)
    fmt.Println("8 是奇数?", 8&1 == 1)   // false

    // ==================== 应用3:交换两个变量(不用临时变量)====================
    x, y := 10, 20
    x ^= y
    y ^= x
    x ^= y
    fmt.Printf("\n交换后: x=%d, y=%d\n", x, y) // x=20, y=10

    // ==================== 应用4:判断是否是 2 的幂 ====================
    isPow2 := func(n int) bool {
        return n > 0 && n&(n-1) == 0
    }
    fmt.Println("\n16 是 2 的幂?", isPow2(16)) // true
    fmt.Println("18 是 2 的幂?", isPow2(18))   // false
}

15. 函数 & Go 语言的拷贝原则

15.1 函数基础

go
package main

import (
    "errors"
    "fmt"
)

// 基本函数
func add(a, b int) int {
    return a + b
}

// 多返回值
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("除数不能为 0")
    }
    return a / b, nil
}

// 命名返回值
func swap(a, b int) (x, y int) {
    x = b
    y = a
    return // 裸 return,返回命名返回值
}

// 可变参数
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

// 函数作为值
func apply(f func(int, int) int, a, b int) int {
    return f(a, b)
}

func main() {
    fmt.Println(add(3, 5))
    fmt.Println(divide(10, 3))
    fmt.Println(swap(1, 2))
    fmt.Println(sum(1, 2, 3, 4, 5))

    // 匿名函数
    double := func(x int) int { return x * 2 }
    fmt.Println(double(5)) // 10

    // 高阶函数
    fmt.Println(apply(add, 10, 20)) // 30
}

15.2 Go 的值拷贝原则

go
package main

import "fmt"

// ==================== 核心原则 ====================
// Go 中一切赋值和传参都是 **值拷贝**
// 但不同类型"拷贝的内容"不同:
//
// 值类型(深拷贝效果):int, float, bool, string, array, struct
//   → 拷贝完整数据,修改互不影响
//
// 引用类型(浅拷贝效果):slice, map, channel, func, interface, *T
//   → 拷贝的是"头部/描述符",底层数据共享
//   → 所以看起来"像引用传递",但本质仍是值拷贝

type Point struct {
    X, Y int
}

func modifyStruct(p Point) {
    p.X = 999 // 修改的是副本
}

func modifyStructPtr(p *Point) {
    p.X = 999 // 修改原始数据
}

func modifySlice(s []int) {
    s[0] = 999 // 会影响原始切片(底层数组共享)
}

func modifyMap(m map[string]int) {
    m["key"] = 999 // 会影响原始 map(本身是引用语义)
}

func main() {
    // 结构体:值拷贝
    p := Point{1, 2}
    modifyStruct(p)
    fmt.Println("struct 值传递:", p) // {1 2}(未变)

    modifyStructPtr(&p)
    fmt.Println("struct 指针传递:", p) // {999 2}(已变)

    // 切片:拷贝 header(指针+长度+容量),底层数组共享
    s := []int{1, 2, 3}
    modifySlice(s)
    fmt.Println("slice 传递后:", s) // [999 2 3](已变)

    // map:本身就是指针语义
    m := map[string]int{"key": 1}
    modifyMap(m)
    fmt.Println("map 传递后:", m) // map[key:999](已变)
}

16. 数组 & 函数参数需要传数组指针吗

go
package main

import "fmt"

func main() {
    // ==================== 数组基础 ====================
    // 数组是值类型,长度是类型的一部分

    var arr1 [5]int                     // 零值: [0 0 0 0 0]
    arr2 := [5]int{1, 2, 3, 4, 5}      // 完整初始化
    arr3 := [5]int{1, 2}               // 部分初始化: [1 2 0 0 0]
    arr4 := [...]int{1, 2, 3}          // 编译器推断长度: [3]int
    arr5 := [5]int{1: 10, 3: 30}       // 指定索引: [0 10 0 30 0]

    fmt.Println(arr1, arr2, arr3, arr4, arr5)

    // [5]int 和 [3]int 是不同类型!
    // arr2 = arr4  // ❌ 编译错误

    // 数组是值类型,赋值会拷贝
    a := [3]int{1, 2, 3}
    b := a         // 完整拷贝
    b[0] = 999
    fmt.Println("a:", a) // [1 2 3](未变)
    fmt.Println("b:", b) // [999 2 3]
}

// ==================== 数组做函数参数 ====================

// 方式1:传值(拷贝整个数组,大数组很浪费)
func sumArray(arr [1000000]int) int {
    total := 0
    for _, v := range arr {
        total += v
    }
    return total // 每次调用拷贝 ~8MB!
}

// 方式2:传数组指针(避免拷贝,但类型耦合)
func sumArrayPtr(arr *[1000000]int) int {
    total := 0
    for _, v := range arr {
        total += v
    }
    return total // 只拷贝一个指针(8 bytes)
}

// 方式3:传切片(✅ 推荐!灵活且高效)
func sumSlice(s []int) int {
    total := 0
    for _, v := range s {
        total += v
    }
    return total // 只拷贝 slice header(24 bytes)
}

// 结论:
// ❌ 不推荐传数组(值拷贝浪费内存)
// ⚠️ 传数组指针可以但不灵活(长度绑死)
// ✅ 推荐传切片(既高效又灵活,这是 Go 的惯用写法)

17. 切片及扩容规律

17.1 切片基础与内部结构

go
package main

import (
    "fmt"
    "unsafe"
)

// 切片的内部结构等价于:
// type slice struct {
//     array unsafe.Pointer // 指向底层数组
//     len   int            // 当前长度
//     cap   int            // 容量
// }
// 所以 slice header 固定占 24 字节(64位系统)

func main() {
    // ==================== 创建切片的多种方式 ====================

    // 方式1:从数组切割
    arr := [5]int{1, 2, 3, 4, 5}
    s1 := arr[1:4] // [2, 3, 4], len=3, cap=4

    // 方式2:字面量
    s2 := []int{10, 20, 30}

    // 方式3:make
    s3 := make([]int, 5)       // len=5, cap=5, 全 0
    s4 := make([]int, 3, 10)   // len=3, cap=10

    // 方式4:var 声明(nil 切片)
    var s5 []int // nil 切片,len=0, cap=0

    fmt.Println(s1, s2, s3, s4, s5)
    fmt.Println("nil切片:", s5 == nil) // true
    fmt.Println("header 大小:", unsafe.Sizeof(s1)) // 24
}

17.2 扩容规律

go
package main

import "fmt"

func main() {
    // Go 1.18+ 切片扩容规律:
    // 1. 当 cap < 256 时:新 cap = 旧 cap * 2(翻倍)
    // 2. 当 cap >= 256 时:新 cap = 旧 cap + (旧 cap + 3*256) / 4
    //    即大约增长 25% + 192,逐渐从 2x 平滑过渡到 ~1.25x
    // 3. 最终结果会向上对齐到内存分配器的 size class

    s := make([]int, 0)
    prevCap := 0
    for i := 0; i < 2000; i++ {
        s = append(s, i)
        if cap(s) != prevCap {
            fmt.Printf("len=%-5d cap=%-5d 增长比=%.2f\n",
                len(s), cap(s), float64(cap(s))/float64(max(prevCap, 1)))
            prevCap = cap(s)
        }
    }
    // 输出示例(部分):
    // len=1     cap=1     增长比=1.00
    // len=2     cap=2     增长比=2.00
    // len=3     cap=4     增长比=2.00
    // len=5     cap=8     增长比=2.00
    // len=9     cap=16    增长比=2.00
    // ...小容量时翻倍...
    // len=257   cap=512   增长比=2.00
    // len=513   cap=848   增长比=1.66  ← 开始减缓
    // len=849   cap=1280  增长比=1.51
    // len=1281  cap=1792  增长比=1.40
}

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

18. 切片内存共享

⚠️ 这是 Go 切片最容易踩的坑多个切片可以共享同一个底层数组,修改一个会影响另一个。

go
package main

import "fmt"

func main() {
    // ==================== 内存共享演示 ====================
    original := []int{1, 2, 3, 4, 5}
    sub := original[1:3] // [2, 3]

    // sub 和 original 共享底层数组
    sub[0] = 999
    fmt.Println("original:", original) // [1 999 3 4 5] 被影响了!
    fmt.Println("sub:     ", sub)      // [999 3]

    // ==================== append 的陷阱 ====================
    a := []int{1, 2, 3, 4, 5}
    b := a[0:2] // [1, 2], len=2, cap=5

    // b 的 cap 还有空间,append 不会分配新数组
    b = append(b, 999)
    fmt.Println("a:", a) // [1 2 999 4 5] ← a[2] 被覆盖了!
    fmt.Println("b:", b) // [1 2 999]

    // ==================== 安全切割:限制容量 ====================
    c := []int{1, 2, 3, 4, 5}
    d := c[0:2:2] // [start:end:max] → len=2, cap=2(限制 cap!)

    d = append(d, 999) // cap 不够,会分配新数组
    fmt.Println("c:", c) // [1 2 3 4 5] ← 安全!未被修改
    fmt.Println("d:", d) // [1 2 999]

    // ==================== 安全复制 ====================
    e := []int{1, 2, 3, 4, 5}
    f := make([]int, len(e))
    copy(f, e) // 完整复制,完全独立

    f[0] = 999
    fmt.Println("e:", e) // [1 2 3 4 5](安全)
    fmt.Println("f:", f) // [999 2 3 4 5]
}

19. 切片相关函数

go
package main

import (
    "fmt"
    "slices" // Go 1.21+
)

func main() {
    // ==================== append ====================
    s := []int{1, 2, 3}
    s = append(s, 4)          // 追加单个元素
    s = append(s, 5, 6, 7)    // 追加多个元素
    s = append(s, []int{8, 9}...) // 追加另一个切片(展开)
    fmt.Println("append:", s)  // [1 2 3 4 5 6 7 8 9]

    // ==================== copy ====================
    src := []int{1, 2, 3, 4, 5}
    dst := make([]int, 3)
    n := copy(dst, src) // 拷贝 min(len(dst), len(src)) 个元素
    fmt.Println("copy:", dst, "copied:", n) // [1 2 3] copied: 3

    // ==================== 删除元素 ====================
    s2 := []int{1, 2, 3, 4, 5}

    // 删除索引 2(保持顺序)
    i := 2
    s2 = append(s2[:i], s2[i+1:]...)
    fmt.Println("删除后:", s2) // [1 2 4 5]

    // 删除索引 1(不保持顺序,性能更好)
    s3 := []int{1, 2, 3, 4, 5}
    j := 1
    s3[j] = s3[len(s3)-1] // 用最后一个元素覆盖
    s3 = s3[:len(s3)-1]
    fmt.Println("快速删除:", s3) // [1 5 3 4]

    // ==================== 插入元素 ====================
    s4 := []int{1, 2, 4, 5}
    // 在索引 2 处插入 3
    idx := 2
    s4 = append(s4[:idx], append([]int{3}, s4[idx:]...)...)
    fmt.Println("插入后:", s4) // [1 2 3 4 5]

    // ==================== Go 1.21+ slices 包 ====================
    s5 := []int{3, 1, 4, 1, 5, 9, 2, 6}

    // 排序
    slices.Sort(s5)
    fmt.Println("排序:", s5) // [1 1 2 3 4 5 6 9]

    // 查找
    idx2, found := slices.BinarySearch(s5, 4)
    fmt.Println("查找 4:", idx2, found) // 4 true

    // 去重(需先排序)
    s5 = slices.Compact(s5)
    fmt.Println("去重:", s5) // [1 2 3 4 5 6 9]

    // 包含
    fmt.Println("包含 5?", slices.Contains(s5, 5)) // true

    // 反转
    slices.Reverse(s5)
    fmt.Println("反转:", s5) // [9 6 5 4 3 2 1]
}

20. 字符串及常用操作

go
package main

import (
    "fmt"
    "strings"
    "unicode/utf8"
)

func main() {
    // ==================== 字符串本质 ====================
    // Go 字符串是只读的字节切片 ([]byte 的不可变版本)
    // 内部结构:struct { ptr *byte; len int }
    // 字符串默认 UTF-8 编码

    s := "Hello, 世界"

    // 长度:字节数 vs 字符数
    fmt.Println("字节长度:", len(s))                    // 13("世界"各占3字节)
    fmt.Println("字符个数:", utf8.RuneCountInString(s))  // 9

    // 遍历字节 vs 遍历字符
    for i := 0; i < len(s); i++ {
        fmt.Printf("%x ", s[i]) // 逐字节
    }
    fmt.Println()
    for _, r := range s {
        fmt.Printf("%c ", r) // 逐字符(rune)
    }
    fmt.Println()

    // ==================== strings 包常用函数 ====================

    // 查找
    fmt.Println(strings.Contains("seafood", "foo"))  // true
    fmt.Println(strings.HasPrefix("hello", "he"))     // true
    fmt.Println(strings.HasSuffix("hello", "lo"))     // true
    fmt.Println(strings.Index("hello", "ll"))          // 2
    fmt.Println(strings.Count("cheese", "e"))          // 3

    // 转换
    fmt.Println(strings.ToUpper("hello"))         // HELLO
    fmt.Println(strings.ToLower("HELLO"))         // hello
    fmt.Println(strings.Title("hello world"))     // Hello World(已废弃)

    // 修剪
    fmt.Println(strings.TrimSpace("  hello  "))   // "hello"
    fmt.Println(strings.Trim("***hello***", "*")) // "hello"
    fmt.Println(strings.TrimPrefix("foobar", "foo")) // "bar"
    fmt.Println(strings.TrimSuffix("foobar", "bar")) // "foo"

    // 分割与连接
    parts := strings.Split("a,b,c,d", ",")
    fmt.Println(parts)                           // [a b c d]
    joined := strings.Join(parts, " | ")
    fmt.Println(joined)                          // a | b | c | d

    // 替换
    fmt.Println(strings.Replace("aaa", "a", "b", 2))   // bba(替换前2个)
    fmt.Println(strings.ReplaceAll("aaa", "a", "b"))    // bbb

    // 重复
    fmt.Println(strings.Repeat("ha", 3))  // hahaha

    // ==================== strings.Builder(高效拼接)====================
    // 不要用 + 拼接大量字符串(每次产生新对象)
    var builder strings.Builder
    for i := 0; i < 100; i++ {
        builder.WriteString("hello")
    }
    result := builder.String()
    fmt.Println(len(result)) // 500
}

21. 数据类型转换

go
package main

import (
    "fmt"
    "strconv"
)

func main() {
    // ==================== 数值类型之间的转换 ====================
    // Go 不允许隐式类型转换,必须显式转换
    var i int = 42
    var f float64 = float64(i)   // int → float64
    var u uint = uint(f)          // float64 → uint

    fmt.Println(i, f, u)

    // 注意精度丢失
    var bigFloat float64 = 1.9
    var truncated int = int(bigFloat) // 直接截断,不是四舍五入!
    fmt.Println(truncated)            // 1

    // 溢出(不报错,直接截断)
    var big int64 = 256
    var small int8 = int8(big)
    fmt.Println(small) // 0(溢出)

    // ==================== 字符串 ↔ 数值 ====================

    // int → string
    s1 := strconv.Itoa(42)          // "42"
    s2 := fmt.Sprintf("%d", 42)     // "42"(更通用)

    // string → int
    n1, err := strconv.Atoi("42")
    fmt.Println(n1, err) // 42 <nil>

    n2, err := strconv.Atoi("abc")
    fmt.Println(n2, err) // 0 parsing "abc": invalid syntax

    // string → float
    f1, _ := strconv.ParseFloat("3.14", 64)
    fmt.Println(f1) // 3.14

    // float → string
    s3 := strconv.FormatFloat(3.14, 'f', 2, 64)
    fmt.Println(s3) // "3.14"

    // string → bool
    b1, _ := strconv.ParseBool("true")
    fmt.Println(b1) // true

    // ==================== 字符串 ↔ 字节切片 ====================
    str := "Hello, 世界"
    bs := []byte(str)         // string → []byte(拷贝)
    rs := []rune(str)         // string → []rune(拷贝)
    str2 := string(bs)        // []byte → string(拷贝)

    fmt.Println(bs)    // [72 101 108 108 111 44 32 228 184 150 231 149 140]
    fmt.Println(rs)    // [72 101 108 108 111 44 32 19990 30028]
    fmt.Println(str2)  // Hello, 世界

    // 单个数字转字符
    fmt.Println(string(65))     // "A"(Unicode 码点 → 字符)
    fmt.Println(string(0x4e16)) // "世"

    _ = s1
    _ = s2
}

22. HashTable 实现原理,map 扩容与遍历

22.1 map 基础用法

go
package main

import "fmt"

func main() {
    // ==================== 创建 map ====================
    m1 := map[string]int{"a": 1, "b": 2}         // 字面量
    m2 := make(map[string]int)                     // make 创建
    m3 := make(map[string]int, 100)                // 指定初始容量(建议)
    var m4 map[string]int                          // nil map

    _ = m2
    _ = m3

    // nil map 可以读,但不能写
    fmt.Println(m4["key"]) // 0(零值)
    // m4["key"] = 1       // ❌ panic!

    // ==================== CRUD 操作 ====================
    m1["c"] = 3              // 增/改
    delete(m1, "a")          // 删
    v := m1["b"]             // 查
    v2, ok := m1["x"]        // 查(带存在性检查)
    fmt.Println(v, v2, ok)   // 2 0 false

    // ==================== 遍历(顺序随机)====================
    for k, v := range m1 {
        fmt.Printf("%s: %d\n", k, v)
    }
}

22.2 底层原理

go
// ==================== Go map 底层结构(简化)====================
//
// Go 的 map 使用 HashTable 实现,核心结构:
//
// hmap {
//     count     int           // 当前元素个数
//     B         uint8         // 桶数组大小 = 2^B
//     buckets   unsafe.Pointer // 桶数组指针
//     oldbuckets unsafe.Pointer // 扩容时的旧桶
//     ...
// }
//
// 每个 bucket(bmap)最多存 8 个 key-value 对:
// bmap {
//     tophash [8]uint8  // 每个 key 的 hash 高 8 位(快速比较)
//     keys    [8]keyType
//     values  [8]valueType
//     overflow *bmap     // 溢出桶链表
// }
//
// ==================== 查找过程 ====================
// 1. 计算 key 的 hash
// 2. hash 低 B 位确定桶编号
// 3. hash 高 8 位在桶内逐个比较 tophash
// 4. tophash 匹配后再比较完整 key
// 5. 找不到就沿 overflow 链继续找
//
// ==================== 扩容机制 ====================
// 触发条件(满足其一):
// 1. 装载因子 > 6.5(count / 2^B > 6.5)→ 翻倍扩容
// 2. 溢出桶太多 → 等量扩容(整理碎片)
//
// 扩容方式:渐进式迁移
// - 不是一次性搬完,而是每次读写操作时迁移 1-2 个旧桶
// - 扩容期间 oldbuckets 和 buckets 同时存在
//
// ==================== 遍历为什么是随机的 ====================
// Go 故意在 range map 时加入随机起始位置
// 防止开发者依赖 map 的遍历顺序(因为扩容后顺序会变)
// 这是语言设计决策,不是实现副作用

23. 不定长参数和函数递归

go
package main

import "fmt"

// ==================== 不定长参数 ====================
// 不定长参数本质是一个切片

func sum(nums ...int) int {
    // nums 的类型是 []int
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

// 不定长参数必须是最后一个参数
func printInfo(prefix string, values ...interface{}) {
    fmt.Print(prefix, ": ")
    for _, v := range values {
        fmt.Printf("%v ", v)
    }
    fmt.Println()
}

// 传递不定长参数
func wrapper(nums ...int) int {
    // 用 ... 展开传递给另一个不定长参数函数
    return sum(nums...)
}

// ==================== 递归 ====================

// 阶乘
func factorial(n int) int {
    if n <= 1 { // 递归终止条件(极其重要!)
        return 1
    }
    return n * factorial(n-1)
}

// 斐波那契(朴素递归,效率很低)
func fib(n int) int {
    if n <= 1 {
        return n
    }
    return fib(n-1) + fib(n-2)
}

// 斐波那契(尾递归优化版本)
func fibTail(n, a, b int) int {
    if n == 0 {
        return a
    }
    return fibTail(n-1, b, a+b)
}

// 实用递归:遍历嵌套结构
type TreeNode struct {
    Value    int
    Children []*TreeNode
}

func traverse(node *TreeNode, depth int) {
    if node == nil {
        return
    }
    for i := 0; i < depth; i++ {
        fmt.Print("  ")
    }
    fmt.Println(node.Value)
    for _, child := range node.Children {
        traverse(child, depth+1)
    }
}

func main() {
    fmt.Println(sum(1, 2, 3, 4, 5))       // 15
    fmt.Println(sum())                      // 0

    // 切片展开传递
    nums := []int{10, 20, 30}
    fmt.Println(sum(nums...))               // 60

    printInfo("debug", "hello", 42, true)

    fmt.Println("5! =", factorial(5))       // 120
    fmt.Println("fib(10) =", fib(10))       // 55
    fmt.Println("fib(10) =", fibTail(10, 0, 1)) // 55

    // 树形遍历
    tree := &TreeNode{
        Value: 1,
        Children: []*TreeNode{
            {Value: 2, Children: []*TreeNode{
                {Value: 4}, {Value: 5},
            }},
            {Value: 3},
        },
    }
    traverse(tree, 0)
}

24. 时间类型及其格式化

go
package main

import (
    "fmt"
    "time"
)

func main() {
    // ==================== 获取当前时间 ====================
    now := time.Now()
    fmt.Println("当前时间:", now)
    fmt.Println("年:", now.Year())
    fmt.Println("月:", now.Month())
    fmt.Println("日:", now.Day())
    fmt.Println("时:", now.Hour())
    fmt.Println("分:", now.Minute())
    fmt.Println("秒:", now.Second())
    fmt.Println("纳秒:", now.Nanosecond())
    fmt.Println("星期:", now.Weekday())
    fmt.Println("时间戳(秒):", now.Unix())
    fmt.Println("时间戳(毫秒):", now.UnixMilli())

    // ==================== 时间格式化(Go 的独特设计)====================
    // Go 使用「参考时间」而不是 yyyy-MM-dd 这种符号
    // 参考时间:Mon Jan 2 15:04:05 MST 2006
    // 记忆口诀:1月2日下午3点4分5秒 2006年(1 2 3 4 5 6 7)

    fmt.Println(now.Format("2006-01-02 15:04:05"))       // 2024-01-15 14:30:45
    fmt.Println(now.Format("2006/01/02"))                 // 2024/01/15
    fmt.Println(now.Format("15:04:05"))                   // 14:30:45
    fmt.Println(now.Format("2006年01月02日"))               // 2024年01月15日
    fmt.Println(now.Format(time.RFC3339))                 // 2024-01-15T14:30:45+08:00
    fmt.Println(now.Format("Mon, 02 Jan 2006"))           // Mon, 15 Jan 2024

    // 12小时制 vs 24小时制
    fmt.Println(now.Format("03:04 PM"))  // 02:30 PM(12小时制用 03)
    fmt.Println(now.Format("15:04"))     // 14:30  (24小时制用 15)

    // ==================== 字符串解析为时间 ====================
    t, err := time.Parse("2006-01-02 15:04:05", "2024-06-15 10:30:00")
    if err != nil {
        fmt.Println("解析错误:", err)
    }
    fmt.Println("解析结果:", t)

    // 带时区的解析
    loc, _ := time.LoadLocation("Asia/Shanghai")
    t2, _ := time.ParseInLocation("2006-01-02 15:04:05", "2024-06-15 10:30:00", loc)
    fmt.Println("上海时间:", t2)

    // ==================== 时间运算 ====================
    // 加减时间
    future := now.Add(24 * time.Hour)          // 加一天
    past := now.Add(-2 * time.Hour)            // 减两小时
    nextMonth := now.AddDate(0, 1, 0)          // 加一个月

    fmt.Println("明天:", future.Format("2006-01-02"))
    fmt.Println("两小时前:", past.Format("15:04:05"))
    fmt.Println("下个月:", nextMonth.Format("2006-01-02"))

    // 计算时间差
    duration := future.Sub(now)
    fmt.Println("时间差:", duration) // 24h0m0s

    // 比较时间
    fmt.Println("future 在 now 之后?", future.After(now)) // true
    fmt.Println("past 在 now 之前?", past.Before(now))     // true
}

25. 定时执行和周期执行

go
package main

import (
    "fmt"
    "time"
)

func main() {
    // ==================== time.Sleep:最简单的等待 ====================
    fmt.Println("开始")
    time.Sleep(1 * time.Second) // 阻塞 1 秒
    fmt.Println("1秒后")

    // ==================== time.After:一次性定时器(返回 channel)====================
    fmt.Println("等待 2 秒...")
    <-time.After(2 * time.Second) // 阻塞直到时间到
    fmt.Println("2秒到了")

    // 常用于超时控制
    ch := make(chan string)
    go func() {
        time.Sleep(3 * time.Second)
        ch <- "result"
    }()

    select {
    case result := <-ch:
        fmt.Println("收到:", result)
    case <-time.After(2 * time.Second):
        fmt.Println("超时了!")
    }

    // ==================== time.NewTimer:可控的一次性定时器 ====================
    timer := time.NewTimer(2 * time.Second)

    // 可以提前停止
    go func() {
        time.Sleep(1 * time.Second)
        stopped := timer.Stop() // 停止定时器
        fmt.Println("定时器已停止:", stopped)
    }()

    // 也可以重置
    // timer.Reset(5 * time.Second)

    // ==================== time.NewTicker:周期执行 ====================
    ticker := time.NewTicker(500 * time.Millisecond)
    done := make(chan bool)

    go func() {
        for {
            select {
            case <-done:
                return
            case t := <-ticker.C:
                fmt.Println("Tick:", t.Format("15:04:05.000"))
            }
        }
    }()

    time.Sleep(2 * time.Second) // 让 ticker 运行 2 秒
    ticker.Stop()                // 停止 ticker
    done <- true
    fmt.Println("Ticker 已停止")
}

26. defer 经典案例

go
package main

import (
    "fmt"
    "os"
    "sync"
)

func main() {
    // ==================== 基本 defer ====================
    // defer 在函数返回时执行,先进后出(LIFO)
    fmt.Println("=== 执行顺序 ===")
    defer fmt.Println("第一个 defer")
    defer fmt.Println("第二个 defer")
    defer fmt.Println("第三个 defer")
    // 输出顺序:第三个 → 第二个 → 第一个

    // ==================== 案例1:资源清理 ====================
    fmt.Println("\n=== 资源清理 ===")
    file, err := os.Create("/tmp/test.txt")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer file.Close() // 确保文件一定会关闭

    file.WriteString("hello")

    // ==================== 案例2:解锁 ====================
    var mu sync.Mutex
    mu.Lock()
    defer mu.Unlock() // 确保一定会解锁

    // ==================== 案例3:defer 与返回值的关系 ====================
    fmt.Println("\n=== defer 修改返回值 ===")
    fmt.Println("结果:", deferReturn()) // 2(不是 1!)

    // ==================== 案例4:defer 参数立即求值 ====================
    fmt.Println("\n=== 参数立即求值 ===")
    x := 10
    defer fmt.Printf("defer 中 x = %d\n", x) // x 在 defer 注册时就已求值为 10
    x = 20
    fmt.Println("当前 x =", x) // 20
    // defer 输出: x = 10(不是 20)

    // ==================== 案例5:循环中的 defer(陷阱)====================
    fmt.Println("\n=== 循环中的 defer ===")
    // ❌ 错误:defer 在函数结束才执行,循环中会堆积
    // for i := 0; i < 10000; i++ {
    //     f, _ := os.Open("file")
    //     defer f.Close() // 所有 Close 在函数结束才执行!
    // }

    // ✅ 正确:用匿名函数包裹
    for i := 0; i < 3; i++ {
        func() {
            f, err := os.CreateTemp("", "test")
            if err != nil {
                return
            }
            defer f.Close() // 每次循环结束就关闭
            f.WriteString(fmt.Sprintf("file %d", i))
        }()
    }
}

// defer 可以修改命名返回值
func deferReturn() (result int) {
    result = 1
    defer func() {
        result++ // 修改命名返回值
    }()
    return result // 返回 2
    // 执行顺序:result = 1 → return(设置返回值) → defer(result++) → 实际返回
}

27. panic 与 recover

go
package main

import (
    "fmt"
    "runtime/debug"
)

// ==================== panic:程序崩溃 ====================
// panic 的触发方式:
// 1. 主动调用 panic(msg)
// 2. 运行时错误:数组越界、nil 指针、类型断言失败...

// ==================== recover:捕获 panic ====================
// recover 只能在 defer 函数中使用
// 类似于 try-catch,但 Go 不鼓励滥用

// 安全的除法
func safeDivide(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("panic recovered: %v", r)
            // 可选:打印调用栈
            debug.PrintStack()
        }
    }()

    return a / b, nil // 如果 b=0 会 panic
}

// HTTP 处理器中的 recover(实际场景)
func safeHandler(handler func()) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("[Recovery] panic: %v\n", r)
            fmt.Printf("Stack:\n%s\n", debug.Stack())
            // 实际项目中:返回 500 错误
        }
    }()
    handler()
}

func main() {
    // 基本 recover
    result, err := safeDivide(10, 0)
    if err != nil {
        fmt.Println("错误:", err)
    } else {
        fmt.Println("结果:", result)
    }

    // 正常的除法
    result2, _ := safeDivide(10, 3)
    fmt.Println("10/3 =", result2)

    // 模拟 HTTP 处理器
    safeHandler(func() {
        var s []int
        _ = s[10] // panic: index out of range
    })

    fmt.Println("程序继续运行...") // recover 后程序不会崩溃
}

// ==================== 使用原则 ====================
// 1. 绝大多数情况下,用 error 返回值处理错误,不要用 panic
// 2. panic 只用于"不可恢复的错误"(程序初始化失败等)
// 3. recover 主要用于:
//    - HTTP/RPC 框架的中间件(防止单个请求崩溃服务)
//    - goroutine 的兜底保护
//    - 库代码中将 panic 转为 error 返回给调用者
// 4. 不要用 panic/recover 做常规流程控制(这不是 try-catch)

28. channel 及引用类型的本质

go
package main

import (
    "fmt"
    "sync"
)

func main() {
    // ==================== channel 基础 ====================
    // channel 本质上是一个指向 runtime.hchan 结构的指针
    // 所以 channel 是引用类型,零值为 nil

    // 无缓冲 channel(同步通信,发送和接收必须同时就绪)
    ch1 := make(chan int)

    go func() {
        ch1 <- 42 // 发送,阻塞直到有人接收
    }()
    val := <-ch1 // 接收,阻塞直到有人发送
    fmt.Println("收到:", val)

    // 有缓冲 channel(异步通信,缓冲区满才阻塞)
    ch2 := make(chan string, 3)
    ch2 <- "a"
    ch2 <- "b"
    ch2 <- "c"
    // ch2 <- "d" // 会阻塞(缓冲区已满)
    fmt.Println(<-ch2, <-ch2, <-ch2)

    // ==================== 单向 channel ====================
    // 用于函数签名,限制 channel 的使用方向

    // 只能发送
    producer := func(ch chan<- int) {
        for i := 0; i < 5; i++ {
            ch <- i
        }
        close(ch)
    }

    // 只能接收
    consumer := func(ch <-chan int) {
        for v := range ch {
            fmt.Print(v, " ")
        }
        fmt.Println()
    }

    ch3 := make(chan int, 5)
    producer(ch3)
    consumer(ch3)

    // ==================== select:多路复用 ====================
    ch4 := make(chan string)
    ch5 := make(chan string)

    go func() { ch4 <- "from ch4" }()
    go func() { ch5 <- "from ch5" }()

    // select 随机选择一个就绪的 case
    for i := 0; i < 2; i++ {
        select {
        case msg := <-ch4:
            fmt.Println(msg)
        case msg := <-ch5:
            fmt.Println(msg)
        }
    }

    // ==================== 引用类型的本质 ====================
    // Go 中的引用类型:slice, map, channel, func, interface
    // 它们的"零值"都是 nil
    // 赋值/传参时拷贝的是"描述符/指针",底层数据共享

    // channel 传参示例
    var wg sync.WaitGroup
    ch6 := make(chan int, 10)

    wg.Add(1)
    go func(ch chan int) { // ch 是 ch6 的拷贝,但指向同一个 hchan
        defer wg.Done()
        for v := range ch {
            fmt.Print(v, " ")
        }
    }(ch6)

    for i := 0; i < 5; i++ {
        ch6 <- i
    }
    close(ch6)
    wg.Wait()
    fmt.Println()
}

29. 接口有什么用怎么用

go
package main

import (
    "fmt"
    "math"
)

// ==================== 接口定义 ====================
// 接口是一组方法签名的集合
// Go 使用隐式接口实现(不需要 implements 关键字)

type Shape interface {
    Area() float64
    Perimeter() float64
}

// 实现 Shape 的类型 1:圆
type Circle struct {
    Radius float64
}

// Circle 实现了 Shape 的所有方法,自动满足接口
func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
    return 2 * math.Pi * c.Radius
}

// 实现 Shape 的类型 2:矩形
type Rectangle struct {
    Width, Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

// ==================== 接口的作用 ====================

// 作用1:统一处理不同类型
func printShapeInfo(s Shape) {
    fmt.Printf("类型: %T, 面积: %.2f, 周长: %.2f\n",
        s, s.Area(), s.Perimeter())
}

// 作用2:依赖注入、可测试性
type Logger interface {
    Log(msg string)
}

type ConsoleLogger struct{}
func (l ConsoleLogger) Log(msg string) { fmt.Println("[Console]", msg) }

type FileLogger struct{ Path string }
func (l FileLogger) Log(msg string) { fmt.Printf("[File:%s] %s\n", l.Path, msg) }

type Service struct {
    logger Logger // 依赖接口而非具体类型
}

func NewService(logger Logger) *Service {
    return &Service{logger: logger}
}

func (s *Service) DoWork() {
    s.logger.Log("doing work...")
}

// 作用3:接口组合
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type ReadWriter interface {
    Reader
    Writer
}

func main() {
    // 多态:不同类型统一处理
    shapes := []Shape{
        Circle{Radius: 5},
        Rectangle{Width: 3, Height: 4},
    }

    for _, s := range shapes {
        printShapeInfo(s)
    }

    // 依赖注入:灵活切换实现
    svc1 := NewService(ConsoleLogger{})
    svc1.DoWork() // [Console] doing work...

    svc2 := NewService(FileLogger{Path: "/var/log/app.log"})
    svc2.DoWork() // [File:/var/log/app.log] doing work...

    // 类型断言
    var s Shape = Circle{Radius: 3}
    if c, ok := s.(Circle); ok {
        fmt.Println("是圆形,半径:", c.Radius)
    }
}

30. 面向接口编程案例——推荐系统

go
package main

import (
    "fmt"
    "math/rand"
    "sort"
)

// ==================== 接口设计 ====================

// Item 代表可推荐的内容
type Item struct {
    ID       string
    Title    string
    Category string
    Tags     []string
    Score    float64 // 推荐得分
}

// Recommender 推荐策略接口
type Recommender interface {
    Name() string
    Recommend(userTags []string, items []Item, limit int) []Item
}

// Ranker 排序策略接口
type Ranker interface {
    Rank(items []Item) []Item
}

// Filter 过滤策略接口
type Filter interface {
    Filter(items []Item) []Item
}

// ==================== 推荐策略实现 ====================

// 基于标签匹配的推荐
type TagBasedRecommender struct{}

func (r TagBasedRecommender) Name() string { return "TagBased" }

func (r TagBasedRecommender) Recommend(userTags []string, items []Item, limit int) []Item {
    tagSet := make(map[string]bool)
    for _, t := range userTags {
        tagSet[t] = true
    }

    var results []Item
    for _, item := range items {
        score := 0.0
        for _, tag := range item.Tags {
            if tagSet[tag] {
                score += 1.0
            }
        }
        if score > 0 {
            item.Score = score
            results = append(results, item)
        }
    }

    sort.Slice(results, func(i, j int) bool {
        return results[i].Score > results[j].Score
    })

    if len(results) > limit {
        results = results[:limit]
    }
    return results
}

// 随机推荐(用于冷启动)
type RandomRecommender struct{}

func (r RandomRecommender) Name() string { return "Random" }

func (r RandomRecommender) Recommend(_ []string, items []Item, limit int) []Item {
    shuffled := make([]Item, len(items))
    copy(shuffled, items)
    rand.Shuffle(len(shuffled), func(i, j int) {
        shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
    })
    if len(shuffled) > limit {
        return shuffled[:limit]
    }
    return shuffled
}

// ==================== 推荐引擎(组装各策略)====================

type RecommendEngine struct {
    primary  Recommender // 主推荐策略
    fallback Recommender // 降级策略
    filters  []Filter    // 过滤链
    ranker   Ranker      // 排序策略
}

func NewEngine(primary, fallback Recommender) *RecommendEngine {
    return &RecommendEngine{
        primary:  primary,
        fallback: fallback,
    }
}

func (e *RecommendEngine) AddFilter(f Filter) {
    e.filters = append(e.filters, f)
}

func (e *RecommendEngine) SetRanker(r Ranker) {
    e.ranker = r
}

func (e *RecommendEngine) GetRecommendations(userTags []string, items []Item, limit int) []Item {
    // 1. 过滤
    filtered := items
    for _, f := range e.filters {
        filtered = f.Filter(filtered)
    }

    // 2. 推荐
    results := e.primary.Recommend(userTags, filtered, limit)
    fmt.Printf("  [%s] 推荐了 %d 个结果\n", e.primary.Name(), len(results))

    // 3. 降级
    if len(results) < limit && e.fallback != nil {
        more := e.fallback.Recommend(userTags, filtered, limit-len(results))
        results = append(results, more...)
        fmt.Printf("  [%s] 补充了 %d 个结果\n", e.fallback.Name(), len(more))
    }

    // 4. 排序
    if e.ranker != nil {
        results = e.ranker.Rank(results)
    }

    return results
}

// ==================== 实现一个过滤器 ====================

type CategoryFilter struct {
    Allowed map[string]bool
}

func (f CategoryFilter) Filter(items []Item) []Item {
    var result []Item
    for _, item := range items {
        if f.Allowed[item.Category] {
            result = append(result, item)
        }
    }
    return result
}

func main() {
    // 准备数据
    items := []Item{
        {ID: "1", Title: "Go 入门", Category: "tech", Tags: []string{"go", "编程"}},
        {ID: "2", Title: "Python 实战", Category: "tech", Tags: []string{"python", "编程"}},
        {ID: "3", Title: "美食推荐", Category: "food", Tags: []string{"美食", "旅行"}},
        {ID: "4", Title: "Go 高级编程", Category: "tech", Tags: []string{"go", "并发"}},
        {ID: "5", Title: "旅行日记", Category: "travel", Tags: []string{"旅行", "摄影"}},
        {ID: "6", Title: "机器学习", Category: "tech", Tags: []string{"AI", "python"}},
    }

    userTags := []string{"go", "编程", "并发"}

    // 构建推荐引擎(面向接口组装,策略可随时替换)
    engine := NewEngine(
        TagBasedRecommender{}, // 主策略:标签匹配
        RandomRecommender{},   // 降级策略:随机推荐
    )

    // 添加过滤器
    engine.AddFilter(CategoryFilter{
        Allowed: map[string]bool{"tech": true},
    })

    // 获取推荐
    fmt.Println("=== 推荐结果 ===")
    results := engine.GetRecommendations(userTags, items, 5)
    for _, item := range results {
        fmt.Printf("  [%.1f] %s (%s)\n", item.Score, item.Title, item.Category)
    }
}

31. type 定义一种新类型

go
package main

import "fmt"

// ==================== type 定义新类型 ====================

// 基于基础类型定义新类型
type UserID int64
type Email string
type Celsius float64
type Fahrenheit float64

// 新类型与原类型是不同类型!
func (c Celsius) ToFahrenheit() Fahrenheit {
    return Fahrenheit(c*9/5 + 32)
}

func (f Fahrenheit) ToCelsius() Celsius {
    return Celsius((f - 32) * 5 / 9)
}

// ==================== type 别名 vs 新类型 ====================

type MyInt int    // 新类型:MyInt 和 int 是不同类型
type YourInt = int // 别名:YourInt 就是 int(完全相同的类型)

// ==================== 实际应用:增加类型安全 ====================

type Meter float64
type Kilogram float64

// 这样就不会把米和千克搞混
func calculateBMI(height Meter, weight Kilogram) float64 {
    return float64(weight) / (float64(height) * float64(height))
}

func main() {
    // 类型安全
    var uid UserID = 12345
    // var n int = uid  // ❌ 编译错误:不能直接赋值
    var n int = int(uid) // ✅ 需要显式转换
    fmt.Println(uid, n)

    // 温度转换
    boiling := Celsius(100)
    fmt.Printf("%.1f°C = %.1f°F\n", boiling, boiling.ToFahrenheit())

    // BMI 计算
    bmi := calculateBMI(Meter(1.75), Kilogram(70))
    fmt.Printf("BMI: %.1f\n", bmi)

    // 别名 vs 新类型
    var a MyInt = 10
    var b YourInt = 20
    // var c int = a   // ❌ MyInt 不是 int
    var d int = b      // ✅ YourInt 就是 int
    fmt.Println(a, d)
}

32. 函数类型

go
package main

import (
    "fmt"
    "strings"
)

// ==================== 函数类型定义 ====================
type MathFunc func(int, int) int
type Predicate func(string) bool
type Transformer func(string) string
type Middleware func(Handler) Handler
type Handler func(request string) string

// ==================== 策略模式 ====================
type Operation struct {
    Name string
    Fn   MathFunc
}

// ==================== 中间件模式 ====================
func loggingMiddleware(next Handler) Handler {
    return func(request string) string {
        fmt.Printf("[LOG] Request: %s\n", request)
        response := next(request)
        fmt.Printf("[LOG] Response: %s\n", response)
        return response
    }
}

func authMiddleware(next Handler) Handler {
    return func(request string) string {
        if !strings.HasPrefix(request, "AUTH:") {
            return "401 Unauthorized"
        }
        return next(request[5:]) // 去掉 "AUTH:" 前缀
    }
}

// ==================== 函数式选项模式(Option Pattern)====================
type Server struct {
    host    string
    port    int
    timeout int
}

type ServerOption func(*Server)

func WithHost(host string) ServerOption {
    return func(s *Server) { s.host = host }
}

func WithPort(port int) ServerOption {
    return func(s *Server) { s.port = port }
}

func WithTimeout(timeout int) ServerOption {
    return func(s *Server) { s.timeout = timeout }
}

func NewServer(opts ...ServerOption) *Server {
    s := &Server{
        host:    "localhost",
        port:    8080,
        timeout: 30,
    }
    for _, opt := range opts {
        opt(s)
    }
    return s
}

func main() {
    // 策略模式
    operations := []Operation{
        {"加法", func(a, b int) int { return a + b }},
        {"乘法", func(a, b int) int { return a * b }},
        {"最大值", func(a, b int) int {
            if a > b { return a }
            return b
        }},
    }

    for _, op := range operations {
        fmt.Printf("%s: %d\n", op.Name, op.Fn(10, 3))
    }

    // 中间件链
    handler := func(req string) string {
        return "Hello, " + req
    }

    // 从内到外包裹
    wrapped := loggingMiddleware(authMiddleware(handler))
    fmt.Println(wrapped("AUTH:World"))
    fmt.Println(wrapped("World")) // 401 Unauthorized

    // 函数式选项
    server := NewServer(
        WithHost("0.0.0.0"),
        WithPort(9090),
    )
    fmt.Printf("Server: %+v\n", server)
}

33. 用函数替代接口

go
package main

import "fmt"

// ==================== 传统接口方式 ====================
type Validator interface {
    Validate(s string) bool
}

// 为了实现接口需要创建结构体
type MinLengthValidator struct {
    Min int
}

func (v MinLengthValidator) Validate(s string) bool {
    return len(s) >= v.Min
}

func validateWithInterface(s string, v Validator) bool {
    return v.Validate(s)
}

// ==================== 函数类型替代接口 ====================
// 当接口只有一个方法时,用函数类型更简洁

type ValidateFunc func(string) bool

func validateWithFunc(s string, fn ValidateFunc) bool {
    return fn(s)
}

// Go 标准库的经典案例:http.HandlerFunc
// type Handler interface {
//     ServeHTTP(ResponseWriter, *Request)
// }
// type HandlerFunc func(ResponseWriter, *Request)
// func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
//     f(w, r)  // HandlerFunc 让函数也能满足 Handler 接口
// }

// ==================== 函数类型实现接口的桥梁 ====================
// 让函数类型也满足接口(类似 http.HandlerFunc)

type ValidatorFunc func(string) bool

// ValidatorFunc 实现了 Validator 接口
func (f ValidatorFunc) Validate(s string) bool {
    return f(s)
}

func main() {
    // 传统方式:需要定义结构体
    ok1 := validateWithInterface("hello", MinLengthValidator{Min: 3})

    // 函数方式:直接传 lambda,更简洁
    ok2 := validateWithFunc("hello", func(s string) bool {
        return len(s) >= 3
    })

    // 桥梁模式:函数也能满足接口
    var v Validator = ValidatorFunc(func(s string) bool {
        return len(s) >= 3
    })
    ok3 := v.Validate("hello")

    fmt.Println(ok1, ok2, ok3) // true true true
}

// 选择建议:
// - 接口只有 1 个方法 → 优先考虑函数类型
// - 接口有多个方法 → 必须用接口
// - 需要在接口和函数之间桥接 → 用 HandlerFunc 模式

34. 空接口本质及其使用方法

go
package main

import "fmt"

// ==================== 空接口 interface{} / any ====================
// any 是 interface{} 的别名(Go 1.18+)
// 空接口没有方法,所以所有类型都实现了空接口
//
// 内部结构(eface):
// type eface struct {
//     _type *_type  // 类型信息指针
//     data  unsafe.Pointer // 数据指针
// }
// 占 16 字节(两个指针)

func main() {
    // 空接口可以存放任何值
    var a any = 42
    var b any = "hello"
    var c any = []int{1, 2, 3}

    fmt.Println(a, b, c)

    // ==================== 类型断言 ====================
    // 从空接口中取出具体类型

    // 方式1:直接断言(失败会 panic)
    str := b.(string)
    fmt.Println("字符串:", str)

    // 方式2:安全断言(推荐)
    if num, ok := a.(int); ok {
        fmt.Println("整数:", num)
    }

    // 方式3:type switch
    printType := func(v any) {
        switch val := v.(type) {
        case int:
            fmt.Printf("int: %d\n", val)
        case string:
            fmt.Printf("string: %s\n", val)
        case []int:
            fmt.Printf("[]int: %v\n", val)
        default:
            fmt.Printf("unknown: %T\n", val)
        }
    }

    printType(a) // int: 42
    printType(b) // string: hello
    printType(c) // []int: [1 2 3]

    // ==================== 空接口的常见用途 ====================

    // 1. 通用容器
    config := map[string]any{
        "name":    "myapp",
        "port":    8080,
        "debug":   true,
        "tags":    []string{"web", "api"},
    }
    fmt.Println(config)

    // 2. 函数接受任意参数(类似 fmt.Println)
    // func Println(a ...any) (n int, err error)

    // 3. JSON 解析
    // var data any
    // json.Unmarshal(jsonBytes, &data)
}

// ⚠️ 空接口使用原则:
// 1. 能用具体类型就用具体类型
// 2. 能用有方法的接口就用有方法的接口
// 3. 空接口是最后手段(牺牲了类型安全)
// 4. Go 1.18+ 有泛型后,很多 any 场景可以用泛型替代

35. 空结构体的本质及其应用场景

go
package main

import (
    "fmt"
    "unsafe"
)

func main() {
    // ==================== 空结构体的本质 ====================
    // struct{} 不占内存(size = 0)
    // 所有空结构体变量的地址都指向同一个位置:runtime.zerobase

    var a struct{}
    var b struct{}
    fmt.Println(unsafe.Sizeof(a)) // 0
    fmt.Printf("a 地址: %p\n", &a)
    fmt.Printf("b 地址: %p\n", &b) // 和 a 相同!

    // ==================== 应用1:实现 Set ====================
    // Go 没有内置 Set,用 map[T]struct{} 模拟
    set := make(map[string]struct{})
    set["apple"] = struct{}{}
    set["banana"] = struct{}{}
    set["apple"] = struct{}{} // 重复添加无效

    // 检查是否存在
    if _, exists := set["apple"]; exists {
        fmt.Println("apple 在集合中")
    }

    fmt.Println("集合大小:", len(set)) // 2

    // 使用 map[string]bool 也可以,但每个 value 多占 1 byte
    // 大数据量时 struct{} 更省内存

    // ==================== 应用2:信号 channel ====================
    // 只传递信号,不传递数据
    done := make(chan struct{})

    go func() {
        fmt.Println("工作中...")
        close(done) // 用 close 发送信号
    }()

    <-done // 等待信号
    fmt.Println("完成")

    // ==================== 应用3:方法接收器占位 ====================
    // 不需要任何数据,只需要方法
    type Converter struct{}

    // ==================== 应用4:嵌入接口实现 ====================
    // 用于测试桩,只实现需要的方法
}

36. error 接口及自定义 Error

go
package main

import (
    "errors"
    "fmt"
    "time"
)

// ==================== error 接口 ====================
// type error interface {
//     Error() string
// }
// 任何实现了 Error() string 方法的类型都是 error

// ==================== 自定义错误类型 ====================

// 简单的自定义错误
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation failed on field '%s': %s", e.Field, e.Message)
}

// 带错误码的错误
type APIError struct {
    Code    int
    Message string
    Detail  string
}

func (e *APIError) Error() string {
    return fmt.Sprintf("[%d] %s: %s", e.Code, e.Message, e.Detail)
}

// 带重试信息的错误
type RetryableError struct {
    Err       error
    RetryAfter time.Duration
}

func (e *RetryableError) Error() string {
    return fmt.Sprintf("%s (retry after %s)", e.Err.Error(), e.RetryAfter)
}

func (e *RetryableError) Unwrap() error {
    return e.Err // 支持 errors.Is / errors.As
}

// ==================== 哨兵错误(Sentinel Error)====================
var (
    ErrNotFound     = errors.New("not found")
    ErrUnauthorized = errors.New("unauthorized")
    ErrForbidden    = errors.New("forbidden")
)

// ==================== 使用示例 ====================
func findUser(id int) (*struct{ Name string }, error) {
    if id <= 0 {
        return nil, &ValidationError{
            Field:   "id",
            Message: "must be positive",
        }
    }
    if id == 404 {
        return nil, fmt.Errorf("findUser: %w", ErrNotFound)
    }
    return &struct{ Name string }{"Alice"}, nil
}

func main() {
    // 基本错误处理
    _, err := findUser(-1)
    if err != nil {
        fmt.Println("错误:", err)

        // 判断具体错误类型
        var validErr *ValidationError
        if errors.As(err, &validErr) {
            fmt.Printf("字段: %s, 原因: %s\n", validErr.Field, validErr.Message)
        }
    }

    // 哨兵错误判断
    _, err = findUser(404)
    if errors.Is(err, ErrNotFound) {
        fmt.Println("用户不存在")
    }

    // 可重试错误
    retryErr := &RetryableError{
        Err:        ErrNotFound,
        RetryAfter: 5 * time.Second,
    }
    fmt.Println(retryErr)
    fmt.Println("是 NotFound?", errors.Is(retryErr, ErrNotFound)) // true
}

37. 何时抛出 error,如何处理 error

go
package main

import (
    "errors"
    "fmt"
    "log"
    "os"
)

// ==================== 何时返回 error ====================
//
// 返回 error 的情况:
// 1. I/O 操作(文件、网络、数据库)
// 2. 输入验证失败
// 3. 资源不足(内存、连接池)
// 4. 依赖服务调用失败
// 5. 业务规则不满足
//
// 使用 panic 的情况(极少):
// 1. 程序初始化失败(配置加载失败等)
// 2. 编程错误,不应该发生的情况(断言)

// ==================== 错误处理模式 ====================

// 模式1:直接返回
func readFile(path string) ([]byte, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, err // 直接返回
    }
    return data, nil
}

// 模式2:包装错误(添加上下文)
func loadConfig(path string) ([]byte, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("loadConfig(%s): %w", path, err)
        // %w 包装错误,保留原始错误链
    }
    return data, nil
}

// 模式3:降级处理
func getPort() int {
    portStr := os.Getenv("PORT")
    if portStr == "" {
        return 8080 // 默认值降级
    }
    // ...
    return 8080
}

// 模式4:重试
func fetchWithRetry(url string, maxRetries int) (string, error) {
    var lastErr error
    for i := 0; i < maxRetries; i++ {
        // result, err := httpGet(url)
        err := fmt.Errorf("attempt %d failed", i+1)
        if err == nil {
            return "ok", nil
        }
        lastErr = err
        // time.Sleep(time.Duration(i+1) * time.Second) // 退避
    }
    return "", fmt.Errorf("all %d retries failed: %w", maxRetries, lastErr)
}

// 模式5:错误分类处理
func handleError(err error) {
    if err == nil {
        return
    }

    // 按错误类型分类处理
    var pathErr *os.PathError
    if errors.As(err, &pathErr) {
        log.Printf("路径错误 [%s]: %v\n", pathErr.Path, pathErr.Err)
        return
    }

    if errors.Is(err, os.ErrNotExist) {
        log.Println("文件不存在,使用默认配置")
        return
    }

    if errors.Is(err, os.ErrPermission) {
        log.Fatal("权限不足,程序退出")
    }

    // 未知错误
    log.Printf("未知错误: %v\n", err)
}

func main() {
    _, err := loadConfig("nonexistent.yml")
    if err != nil {
        handleError(err)
    }

    // 不要忽略错误!
    // f, _ := os.Open("file")  // ❌ 忽略错误很危险
    // f.Read(...)              // 如果 Open 失败,f 是 nil,这里会 panic
}

// ==================== 错误处理最佳实践 ====================
// 1. 错误只处理一次(要么返回,要么处理,不要又返回又 log)
// 2. 使用 %w 包装错误,保留错误链
// 3. 在 error message 中添加有用的上下文信息
// 4. 不要忽略 error(至少记录日志)
// 5. 使用 errors.Is 和 errors.As 检查错误,不要比较字符串
// 6. 在包的边界处定义错误类型/哨兵错误

38. error 的追踪,errors.Is 和 errors.As

go
package main

import (
    "errors"
    "fmt"
)

// 定义错误
var ErrNotFound = errors.New("not found")
var ErrDatabase = errors.New("database error")

type DetailedError struct {
    Code    int
    Message string
    Cause   error
}

func (e *DetailedError) Error() string {
    return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}

// 实现 Unwrap 方法,支持错误链
func (e *DetailedError) Unwrap() error {
    return e.Cause
}

// ==================== 错误包装链 ====================
func queryDB() error {
    return ErrNotFound // 原始错误
}

func getUser(id int) error {
    err := queryDB()
    if err != nil {
        // %w 创建错误链
        return fmt.Errorf("getUser(%d): %w", id, err)
    }
    return nil
}

func handleRequest() error {
    err := getUser(42)
    if err != nil {
        return &DetailedError{
            Code:    404,
            Message: "user not found",
            Cause:   err, // 嵌套错误
        }
    }
    return nil
}

func main() {
    err := handleRequest()

    // ==================== errors.Is:判断错误链中是否包含某个错误值 ====================
    fmt.Println("=== errors.Is ===")
    fmt.Println("Is ErrNotFound?", errors.Is(err, ErrNotFound))   // true
    fmt.Println("Is ErrDatabase?", errors.Is(err, ErrDatabase))   // false

    // ==================== errors.As:从错误链中提取某个类型的错误 ====================
    fmt.Println("\n=== errors.As ===")
    var detailedErr *DetailedError
    if errors.As(err, &detailedErr) {
        fmt.Printf("错误码: %d, 消息: %s\n", detailedErr.Code, detailedErr.Message)
    }

    // ==================== 错误链展开 ====================
    fmt.Println("\n=== 错误链 ===")
    e := err
    for e != nil {
        fmt.Printf("  → %v\n", e)
        e = errors.Unwrap(e)
    }
    // → [404] user not found
    // → getUser(42): not found
    // → not found

    // ==================== 多错误合并(Go 1.20+)====================
    fmt.Println("\n=== errors.Join ===")
    errs := errors.Join(
        fmt.Errorf("error 1"),
        fmt.Errorf("error 2"),
        ErrNotFound,
    )
    fmt.Println(errs)
    fmt.Println("包含 ErrNotFound?", errors.Is(errs, ErrNotFound)) // true
}

39. 综合练习:PKCS7 数据填充算法

💡 PKCS7 Padding PKCS7 用于块加密(如 AES)前的数据填充。将数据填充到块大小的整数倍,填充字节的值等于需要填充的字节数。例如块大小为 8,数据长度为 5,需要填充 3 个字节,每个字节的值为 0x03。

go
package main

import (
    "errors"
    "fmt"
)

var (
    ErrInvalidBlockSize = errors.New("pkcs7: invalid block size (must be 1-255)")
    ErrInvalidPadding   = errors.New("pkcs7: invalid padding")
    ErrDataEmpty        = errors.New("pkcs7: data is empty")
)

// PKCS7Pad 对数据进行 PKCS7 填充
// blockSize: 块大小,取值范围 1-255
func PKCS7Pad(data []byte, blockSize int) ([]byte, error) {
    if blockSize < 1 || blockSize > 255 {
        return nil, ErrInvalidBlockSize
    }

    // 计算需要填充的字节数
    // 如果数据已经是 blockSize 的整数倍,仍需填充一个完整的块
    padding := blockSize - (len(data) % blockSize)

    // 创建填充字节切片,每个字节的值 = 填充长度
    padBytes := make([]byte, padding)
    for i := range padBytes {
        padBytes[i] = byte(padding)
    }

    // 将填充追加到原数据后面
    return append(data, padBytes...), nil
}

// PKCS7Unpad 去除 PKCS7 填充
func PKCS7Unpad(data []byte, blockSize int) ([]byte, error) {
    if blockSize < 1 || blockSize > 255 {
        return nil, ErrInvalidBlockSize
    }
    if len(data) == 0 {
        return nil, ErrDataEmpty
    }
    if len(data)%blockSize != 0 {
        return nil, ErrInvalidPadding
    }

    // 最后一个字节就是填充长度
    padding := int(data[len(data)-1])

    // 验证填充
    if padding == 0 || padding > blockSize || padding > len(data) {
        return nil, ErrInvalidPadding
    }

    // 验证所有填充字节的值是否一致
    for i := len(data) - padding; i < len(data); i++ {
        if data[i] != byte(padding) {
            return nil, ErrInvalidPadding
        }
    }

    return data[:len(data)-padding], nil
}

func main() {
    blockSize := 8

    // 测试用例
    testCases := []struct {
        name string
        data []byte
    }{
        {"空数据", []byte{}},
        {"短数据", []byte("Hi")},
        {"正好一块", []byte("12345678")},
        {"中文", []byte("你好世界")},
    }

    for _, tc := range testCases {
        fmt.Printf("\n=== %s ===\n", tc.name)
        fmt.Printf("原始数据: %v (长度: %d)\n", tc.data, len(tc.data))

        padded, err := PKCS7Pad(tc.data, blockSize)
        if err != nil {
            fmt.Println("填充错误:", err)
            continue
        }
        fmt.Printf("填充后:   %v (长度: %d)\n", padded, len(padded))

        // 展示填充的十六进制
        fmt.Printf("十六进制: %x\n", padded)

        unpadded, err := PKCS7Unpad(padded, blockSize)
        if err != nil {
            fmt.Println("去填充错误:", err)
            continue
        }
        fmt.Printf("去填充后: %v (长度: %d)\n", unpadded, len(unpadded))
        fmt.Printf("还原文本: %s\n", string(unpadded))
    }

    // 测试异常情况
    fmt.Println("\n=== 异常测试 ===")
    _, err := PKCS7Pad([]byte("test"), 0)
    fmt.Println("blockSize=0:", err)

    _, err = PKCS7Unpad([]byte{1, 2, 3}, 8)
    fmt.Println("长度不对齐:", err)

    _, err = PKCS7Unpad([]byte{1, 2, 3, 4, 5, 6, 7, 9}, 8)
    fmt.Println("填充值无效:", err)
}

40. 结构体方法接收值和接收指针的区别

go
package main

import "fmt"

type Counter struct {
    Name  string
    Count int
}

// 值接收者:方法操作的是副本
func (c Counter) GetCount() int {
    return c.Count
}

// 值接收者中修改不影响原始数据
func (c Counter) IncrementWrong() {
    c.Count++ // 修改的是副本,原始对象不变!
}

// 指针接收者:方法操作的是原始数据
func (c *Counter) Increment() {
    c.Count++ // 修改原始对象
}

func (c *Counter) Reset() {
    c.Count = 0
}

func main() {
    c := Counter{Name: "clicks", Count: 0}

    c.IncrementWrong()
    fmt.Println("错误递增后:", c.Count) // 0(未变)

    c.Increment()
    fmt.Println("正确递增后:", c.Count) // 1

    // Go 自动取地址/解引用
    p := &c
    p.GetCount()   // 指针调用值方法 → 自动解引用 (*p).GetCount()
    c.Increment()  // 值调用指针方法 → 自动取地址 (&c).Increment()
}

// ==================== 如何选择?====================
//
// 使用指针接收者 *T 的情况:
// 1. 方法需要修改接收者的状态
// 2. 结构体很大,避免值拷贝开销
// 3. 该类型的其他方法已经用了指针接收者(保持一致性)
// 4. 类型包含 sync.Mutex 等不能拷贝的字段
//
// 使用值接收者 T 的情况:
// 1. 方法不修改接收者
// 2. 结构体很小(几个基本类型字段)
// 3. 需要值语义(如 time.Time)
//
// ⚠️ 重要区别:接口实现
// - *T 的方法集包含 T 和 *T 的所有方法
// - T 的方法集只包含 T 的方法
// 这意味着:
//   如果接口有指针接收者方法,只有 *T 能满足该接口

type Stringer interface {
    String() string
}

type MyType struct{ Name string }

// 指针接收者方法
func (m *MyType) String() string {
    return m.Name
}

// var _ Stringer = MyType{}   // ❌ 编译错误!
// var _ Stringer = &MyType{}  // ✅

41. 各种数据类型的零值,以及返回零值的设计原则

go
package main

import "fmt"

func main() {
    // ==================== 各类型的零值 ====================
    var (
        b    bool       // false
        i    int        // 0
        f    float64    // 0
        s    string     // ""(空字符串)
        p    *int       // nil
        sl   []int      // nil
        m    map[string]int // nil
        ch   chan int    // nil
        fn   func()     // nil
        itf  interface{} // nil
        err  error      // nil
    )

    fmt.Printf("bool:      %v\n", b)    // false
    fmt.Printf("int:       %v\n", i)    // 0
    fmt.Printf("float64:   %v\n", f)    // 0
    fmt.Printf("string:    %q\n", s)    // ""
    fmt.Printf("*int:      %v\n", p)    // <nil>
    fmt.Printf("[]int:     %v (nil: %v)\n", sl, sl == nil) // [] true
    fmt.Printf("map:       %v (nil: %v)\n", m, m == nil)   // map[] true
    fmt.Printf("chan:       %v\n", ch)   // <nil>
    fmt.Printf("func:      %v\n", fn)   // <nil>
    fmt.Printf("interface: %v\n", itf)  // <nil>
    fmt.Printf("error:     %v\n", err)  // <nil>

    // 结构体的零值:所有字段都是零值
    type User struct {
        Name    string
        Age     int
        Friends []string
    }
    var u User
    fmt.Printf("struct:    %+v\n", u)  // {Name: Age:0 Friends:[]}

    // ==================== 零值可用的设计 ====================
    // Go 鼓励零值可用(zero value is useful)

    // sync.Mutex 零值就是未锁定的互斥锁,无需初始化
    // var mu sync.Mutex  // 直接可用

    // bytes.Buffer 零值就是空缓冲区
    // var buf bytes.Buffer
    // buf.WriteString("hello") // 直接可用

    // nil slice 可以 append
    var ns []int
    ns = append(ns, 1, 2, 3) // 无需 make
    fmt.Println(ns)            // [1 2 3]

    // nil map 可以读(返回零值),但不能写
    // m["key"]  → 0(不 panic)
    // m["key"] = 1 → panic!
}

// ==================== 返回零值的设计原则 ====================
//
// 1. 函数在错误情况下应返回零值 + error
//    func Find(id int) (User, error) {
//        if err != nil {
//            return User{}, err  // 返回零值
//        }
//    }
//
// 2. 指针类型在"不存在"的情况下返回 nil
//    func FindPtr(id int) (*User, error) {
//        if notFound {
//            return nil, ErrNotFound
//        }
//    }
//
// 3. 设计自己的类型时,让零值有意义
//    ✅ type Config struct { Timeout time.Duration }
//       → 零值 Timeout=0 可以在代码中被检测为"未设置"
//    ✅ 在方法中处理零值
//       func (c *Config) GetTimeout() time.Duration {
//           if c.Timeout == 0 {
//               return 30 * time.Second // 默认值
//           }
//           return c.Timeout
//       }

42. nil 非 nil

⚠️ Go 最著名的陷阱之一一个接口值可以不为 nil,即使它内部持有的值是 nil。

go
package main

import "fmt"

type MyError struct {
    Message string
}

func (e *MyError) Error() string {
    return e.Message
}

// 这个函数有 bug!
func doSomethingBad() error {
    var err *MyError = nil  // *MyError 类型的 nil

    // ... 一些逻辑 ...

    return err // 返回的是 interface{type: *MyError, value: nil}
    // 不是真正的 nil!
}

// 正确写法
func doSomethingGood() error {
    var err *MyError = nil

    // ... 一些逻辑 ...

    if err != nil {
        return err
    }
    return nil // 显式返回 nil interface
}

func main() {
    // ==================== nil 接口 vs 非 nil 接口 ====================
    // 接口内部结构:(type, value)
    // 只有 type 和 value 都为 nil 时,接口才 == nil

    err1 := doSomethingBad()
    fmt.Println("err1 == nil?", err1 == nil) // false!!!
    // 因为 err1 的内部是 (*MyError, nil),type 不为 nil

    err2 := doSomethingGood()
    fmt.Println("err2 == nil?", err2 == nil) // true
    // err2 的内部是 (nil, nil)

    // ==================== 可视化 ====================
    var i interface{} = nil
    fmt.Printf("nil interface:  type=%T, value=%v, isNil=%v\n", i, i, i == nil)
    // type=<nil>, value=<nil>, isNil=true

    var p *MyError = nil
    i = p
    fmt.Printf("nil pointer in interface: type=%T, value=%v, isNil=%v\n", i, i, i == nil)
    // type=*main.MyError, value=<nil>, isNil=false !!!

    // ==================== 如何安全检查 ====================
    // 方法1:在返回时确保返回 nil interface
    // 方法2:使用 reflect(不推荐,性能差)
    // import "reflect"
    // isNil := i == nil || reflect.ValueOf(i).IsNil()
}

// 记住这个规则:
// var err error = nil           → (nil, nil)    → == nil ✅
// var err error = (*MyError)(nil) → (*MyError, nil) → != nil ❌

43. 闭包

go
package main

import "fmt"

// ==================== 什么是闭包 ====================
// 闭包 = 函数 + 它引用的外部变量
// 闭包"捕获"了外部变量的引用(不是值的拷贝)

// 计数器:经典闭包示例
func makeCounter() func() int {
    count := 0 // 这个变量被闭包捕获
    return func() int {
        count++ // 修改的是外部的 count
        return count
    }
}

// 累加器
func makeAccumulator(initial int) func(int) int {
    sum := initial
    return func(n int) int {
        sum += n
        return sum
    }
}

// 缓存/记忆化
func memoize(fn func(int) int) func(int) int {
    cache := make(map[int]int) // 闭包捕获 cache
    return func(n int) int {
        if v, ok := cache[n]; ok {
            fmt.Printf("  cache hit: fib(%d) = %d\n", n, v)
            return v
        }
        result := fn(n)
        cache[n] = result
        return result
    }
}

func main() {
    // 计数器
    counter := makeCounter()
    fmt.Println(counter()) // 1
    fmt.Println(counter()) // 2
    fmt.Println(counter()) // 3

    // 两个独立的计数器
    c1 := makeCounter()
    c2 := makeCounter()
    fmt.Println(c1(), c1(), c1()) // 1 2 3
    fmt.Println(c2(), c2())       // 1 2(独立计数)

    // 累加器
    acc := makeAccumulator(100)
    fmt.Println(acc(10))  // 110
    fmt.Println(acc(20))  // 130

    // ==================== 闭包的常见陷阱 ====================

    // 陷阱:循环变量捕获
    funcs := make([]func(), 5)
    for i := 0; i < 5; i++ {
        funcs[i] = func() {
            fmt.Print(i, " ") // 捕获的是变量 i 的引用!
        }
    }
    fmt.Print("陷阱: ")
    for _, f := range funcs {
        f() // 全部输出 5 5 5 5 5(因为循环结束后 i=5)
    }
    fmt.Println()

    // 解决方法1:参数传递(创建副本)
    funcs2 := make([]func(), 5)
    for i := 0; i < 5; i++ {
        funcs2[i] = func(n int) func() {
            return func() { fmt.Print(n, " ") }
        }(i) // 立即调用,传入当前 i 的值
    }
    fmt.Print("修复1: ")
    for _, f := range funcs2 {
        f() // 0 1 2 3 4
    }
    fmt.Println()

    // 解决方法2:局部变量(Go 1.22+ 自动修复了 for 循环变量)
    funcs3 := make([]func(), 5)
    for i := 0; i < 5; i++ {
        i := i // 创建新的局部变量 i(遮蔽外层 i)
        funcs3[i] = func() { fmt.Print(i, " ") }
    }
    fmt.Print("修复2: ")
    for _, f := range funcs3 {
        f() // 0 1 2 3 4
    }
    fmt.Println()
}

44. 指针总结

go
package main

import (
    "fmt"
    "unsafe"
)

func main() {
    // ==================== 指针基础 ====================
    x := 42
    p := &x        // 取地址
    fmt.Println(*p) // 解引用:42
    *p = 100       // 通过指针修改值
    fmt.Println(x)  // 100

    // 指针的零值是 nil
    var np *int
    fmt.Println(np == nil) // true
    // fmt.Println(*np)    // panic: nil pointer dereference

    // ==================== new vs & ====================
    p1 := new(int)         // new 分配零值内存,返回指针
    p2 := &struct{ X int }{X: 10} // & 更常用

    fmt.Println(*p1) // 0
    fmt.Println(p2.X) // 10

    // ==================== 指针不能运算 ====================
    // Go 不允许指针加减运算(不像 C)
    // p++  // ❌ 编译错误

    // 但可以用 unsafe.Pointer 做底层操作(非常规手段)
    arr := [3]int{10, 20, 30}
    ptr := unsafe.Pointer(&arr[0])
    // 计算第二个元素的地址
    ptr2 := unsafe.Pointer(uintptr(ptr) + unsafe.Sizeof(arr[0]))
    fmt.Println(*(*int)(ptr2)) // 20

    // ==================== 指针与各数据类型 ====================

    // 结构体指针(最常用)
    type User struct{ Name string }
    u := &User{Name: "Alice"}
    u.Name = "Bob" // 自动解引用

    // 切片不需要指针(本身已是引用语义)
    s := []int{1, 2, 3}
    modifySlice(s) // 不需要传 *[]int
    fmt.Println(s)  // [999 2 3]

    // map 不需要指针(本身已是引用语义)
    m := map[string]int{"a": 1}
    modifyMap(m)
    fmt.Println(m) // map[a:999]

    // 数组需要指针(值类型,很大时应传指针)
    arr2 := [3]int{1, 2, 3}
    modifyArray(&arr2)
    fmt.Println(arr2) // [999 2 3]

    // ==================== 指针的指针 ====================
    a := 42
    pa := &a
    ppa := &pa // **int
    fmt.Println(**ppa) // 42
}

func modifySlice(s []int)        { s[0] = 999 }
func modifyMap(m map[string]int) { m["a"] = 999 }
func modifyArray(a *[3]int)      { a[0] = 999 }

// ==================== 何时用指针?====================
// ✅ 用指针:
//   - 需要修改接收者的方法
//   - 大结构体的函数参数(避免拷贝)
//   - 表示"可选/可空"值(nil = 不存在)
//   - sync.Mutex 等不可拷贝的类型
//
// ❌ 不用指针:
//   - slice, map, channel, func(自带引用语义)
//   - 小结构体(拷贝成本很低)
//   - 不需要修改的只读参数
//   - 并发安全的不可变值

45. 泛型

go
package main

import (
    "cmp"
    "fmt"
)

// ==================== 泛型函数 ====================

// 类型约束:comparable(可比较的类型)
func Contains[T comparable](slice []T, target T) bool {
    for _, v := range slice {
        if v == target {
            return true
        }
    }
    return false
}

// 类型约束:cmp.Ordered(可排序的类型)
func Max[T cmp.Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

// 多类型参数
func Map[T any, R any](slice []T, fn func(T) R) []R {
    result := make([]R, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

func Filter[T any](slice []T, predicate func(T) bool) []T {
    var result []T
    for _, v := range slice {
        if predicate(v) {
            result = append(result, v)
        }
    }
    return result
}

func Reduce[T any, R any](slice []T, initial R, fn func(R, T) R) R {
    result := initial
    for _, v := range slice {
        result = fn(result, v)
    }
    return result
}

// ==================== 泛型结构体 ====================

type Pair[T, U any] struct {
    First  T
    Second U
}

type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    var zero T
    if len(s.items) == 0 {
        return zero, false
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item, true
}

func (s *Stack[T]) Peek() (T, bool) {
    var zero T
    if len(s.items) == 0 {
        return zero, false
    }
    return s.items[len(s.items)-1], true
}

func (s *Stack[T]) Len() int {
    return len(s.items)
}

// ==================== 自定义类型约束 ====================

type Number interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
    ~float32 | ~float64
}

func Sum[T Number](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}

func main() {
    // 泛型函数
    fmt.Println(Contains([]int{1, 2, 3}, 2))        // true
    fmt.Println(Contains([]string{"a", "b"}, "c"))   // false
    fmt.Println(Max(3, 7))                            // 7
    fmt.Println(Max("apple", "banana"))               // banana

    // Map / Filter / Reduce
    nums := []int{1, 2, 3, 4, 5}

    doubled := Map(nums, func(n int) int { return n * 2 })
    fmt.Println("Map:", doubled) // [2 4 6 8 10]

    evens := Filter(nums, func(n int) bool { return n%2 == 0 })
    fmt.Println("Filter:", evens) // [2 4]

    sum := Reduce(nums, 0, func(acc, n int) int { return acc + n })
    fmt.Println("Reduce:", sum) // 15

    // 泛型结构体
    stack := &Stack[string]{}
    stack.Push("hello")
    stack.Push("world")
    v, _ := stack.Pop()
    fmt.Println("Pop:", v) // world

    // Pair
    p := Pair[string, int]{"age", 25}
    fmt.Println(p) // {age 25}

    // 自定义约束
    fmt.Println(Sum([]float64{1.1, 2.2, 3.3})) // 6.6
}

46. 练习:基于泛型实现 Set

go
package main

import "fmt"

// ==================== 泛型 Set 实现 ====================

type Set[T comparable] struct {
    data map[T]struct{}
}

// 创建新 Set
func NewSet[T comparable](items ...T) *Set[T] {
    s := &Set[T]{data: make(map[T]struct{})}
    for _, item := range items {
        s.Add(item)
    }
    return s
}

// 添加元素
func (s *Set[T]) Add(item T) {
    s.data[item] = struct{}{}
}

// 删除元素
func (s *Set[T]) Remove(item T) {
    delete(s.data, item)
}

// 判断是否包含
func (s *Set[T]) Contains(item T) bool {
    _, exists := s.data[item]
    return exists
}

// 集合大小
func (s *Set[T]) Len() int {
    return len(s.data)
}

// 转为切片
func (s *Set[T]) ToSlice() []T {
    result := make([]T, 0, len(s.data))
    for k := range s.data {
        result = append(result, k)
    }
    return result
}

// 遍历
func (s *Set[T]) ForEach(fn func(T)) {
    for k := range s.data {
        fn(k)
    }
}

// ==================== 集合运算 ====================

// 并集:A ∪ B
func (s *Set[T]) Union(other *Set[T]) *Set[T] {
    result := NewSet[T]()
    s.ForEach(func(item T) { result.Add(item) })
    other.ForEach(func(item T) { result.Add(item) })
    return result
}

// 交集:A ∩ B
func (s *Set[T]) Intersection(other *Set[T]) *Set[T] {
    result := NewSet[T]()
    // 遍历较小的集合以提高效率
    smaller, larger := s, other
    if s.Len() > other.Len() {
        smaller, larger = other, s
    }
    smaller.ForEach(func(item T) {
        if larger.Contains(item) {
            result.Add(item)
        }
    })
    return result
}

// 差集:A - B
func (s *Set[T]) Difference(other *Set[T]) *Set[T] {
    result := NewSet[T]()
    s.ForEach(func(item T) {
        if !other.Contains(item) {
            result.Add(item)
        }
    })
    return result
}

// 对称差集:A △ B = (A - B) ∪ (B - A)
func (s *Set[T]) SymmetricDifference(other *Set[T]) *Set[T] {
    return s.Difference(other).Union(other.Difference(s))
}

// 是否是子集
func (s *Set[T]) IsSubsetOf(other *Set[T]) bool {
    if s.Len() > other.Len() {
        return false
    }
    for k := range s.data {
        if !other.Contains(k) {
            return false
        }
    }
    return true
}

// 实现 fmt.Stringer 接口
func (s *Set[T]) String() string {
    return fmt.Sprintf("Set%v", s.ToSlice())
}

func main() {
    // 整数 Set
    s1 := NewSet(1, 2, 3, 4, 5)
    s2 := NewSet(3, 4, 5, 6, 7)

    fmt.Println("s1:", s1)
    fmt.Println("s2:", s2)
    fmt.Println("并集:", s1.Union(s2))
    fmt.Println("交集:", s1.Intersection(s2))
    fmt.Println("差集 s1-s2:", s1.Difference(s2))
    fmt.Println("对称差集:", s1.SymmetricDifference(s2))
    fmt.Println("s1 ⊂ s2?", s1.IsSubsetOf(s2))

    // 字符串 Set
    fruits := NewSet("apple", "banana", "cherry")
    fruits.Add("date")
    fruits.Remove("banana")
    fmt.Println("\n水果集合:", fruits)
    fmt.Println("包含 apple?", fruits.Contains("apple"))
    fmt.Println("包含 banana?", fruits.Contains("banana"))
}

47. 随机数种子

go
package main

import (
    "crypto/rand"
    "fmt"
    "math/big"
    mathrand "math/rand/v2" // Go 1.22+ 推荐使用 v2
)

func main() {
    // ==================== math/rand/v2(Go 1.22+)====================
    // Go 1.22+ 默认自动使用随机种子,不需要手动设置!
    // 不再需要 rand.Seed(time.Now().UnixNano())

    // 生成随机整数
    fmt.Println("随机 int:", mathrand.IntN(100))       // [0, 100)
    fmt.Println("随机 int:", mathrand.IntN(100))       // 每次不同

    // 生成随机浮点数
    fmt.Println("随机 float:", mathrand.Float64())     // [0.0, 1.0)

    // 随机打乱切片
    s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    mathrand.Shuffle(len(s), func(i, j int) {
        s[i], s[j] = s[j], s[i]
    })
    fmt.Println("打乱后:", s)

    // 如果需要可重复的随机序列(如测试),使用固定种子
    src := mathrand.NewPCG(42, 0) // 固定种子
    rng := mathrand.New(src)
    for i := 0; i < 5; i++ {
        fmt.Print(rng.IntN(100), " ") // 每次运行结果相同
    }
    fmt.Println()

    // ==================== crypto/rand(密码学安全随机数)====================
    // 用于密码、Token、加密密钥等安全场景

    // 生成安全随机整数
    n, _ := rand.Int(rand.Reader, big.NewInt(100))
    fmt.Println("安全随机数:", n)

    // 生成安全随机字节
    token := make([]byte, 32)
    rand.Read(token)
    fmt.Printf("安全 Token: %x\n", token)
}

48. 练习:生成随机字符串

go
package main

import (
    "crypto/rand"
    "fmt"
    "math/big"
    mathrand "math/rand/v2"
    "strings"
)

// ==================== 方式1:math/rand(快速,非安全)====================
func randomString(length int, charset string) string {
    var sb strings.Builder
    sb.Grow(length)
    for i := 0; i < length; i++ {
        idx := mathrand.IntN(len(charset))
        sb.WriteByte(charset[idx])
    }
    return sb.String()
}

// 预定义字符集
const (
    CharsetLower    = "abcdefghijklmnopqrstuvwxyz"
    CharsetUpper    = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    CharsetDigit    = "0123456789"
    CharsetAlpha    = CharsetLower + CharsetUpper
    CharsetAlphaNum = CharsetAlpha + CharsetDigit
    CharsetHex      = "0123456789abcdef"
    CharsetSpecial  = "!@#$%^&*()-_=+[]{}|;:,.<>?"
)

// ==================== 方式2:crypto/rand(安全随机)====================
func secureRandomString(length int, charset string) (string, error) {
    var sb strings.Builder
    sb.Grow(length)
    charsetLen := big.NewInt(int64(len(charset)))

    for i := 0; i < length; i++ {
        idx, err := rand.Int(rand.Reader, charsetLen)
        if err != nil {
            return "", err
        }
        sb.WriteByte(charset[idx.Int64()])
    }
    return sb.String(), nil
}

// ==================== 方式3:高效位掩码法 ====================
func randomStringMask(length int) string {
    const charset = CharsetAlphaNum
    const bits = 6                // 6 bits 可表示 0-63(覆盖 62 个字符)
    const mask = 1<<bits - 1      // 0b111111 = 63
    const maxPerInt = 63 / bits   // 一个 int64 能产生几个字符

    var sb strings.Builder
    sb.Grow(length)

    for i, remain := 0, 0; i < length; {
        if remain == 0 {
            remain = maxPerInt
        }
        idx := int(mathrand.Int64() & mask)
        if idx < len(charset) {
            sb.WriteByte(charset[idx])
            i++
        }
        remain--
    }
    return sb.String()
}

// ==================== 实用函数 ====================

func generatePassword(length int) string {
    // 确保至少包含各类字符
    password := make([]byte, 0, length)
    password = append(password, CharsetLower[mathrand.IntN(len(CharsetLower))])
    password = append(password, CharsetUpper[mathrand.IntN(len(CharsetUpper))])
    password = append(password, CharsetDigit[mathrand.IntN(len(CharsetDigit))])
    password = append(password, CharsetSpecial[mathrand.IntN(len(CharsetSpecial))])

    allChars := CharsetAlphaNum + CharsetSpecial
    for len(password) < length {
        password = append(password, allChars[mathrand.IntN(len(allChars))])
    }

    // 打乱顺序
    mathrand.Shuffle(len(password), func(i, j int) {
        password[i], password[j] = password[j], password[i]
    })

    return string(password)
}

func main() {
    // 基本随机字符串
    fmt.Println("字母数字:", randomString(16, CharsetAlphaNum))
    fmt.Println("纯数字:  ", randomString(6, CharsetDigit))
    fmt.Println("十六进制:", randomString(32, CharsetHex))

    // 安全随机字符串
    token, _ := secureRandomString(32, CharsetAlphaNum)
    fmt.Println("安全Token:", token)

    // 密码生成
    fmt.Println("密码:    ", generatePassword(16))

    // 高效版本
    fmt.Println("位掩码法:", randomStringMask(20))

    // 批量生成
    fmt.Println("\n批量生成 5 个邀请码:")
    for i := 0; i < 5; i++ {
        code := randomString(8, CharsetUpper+CharsetDigit)
        fmt.Printf("  %s-%s\n", code[:4], code[4:])
    }
}

49. 内存逃逸与垃圾回收

49.1 内存逃逸

go
package main

import "fmt"

// ==================== 什么是内存逃逸 ====================
// Go 编译器会决定变量分配在栈上还是堆上:
// - 栈:函数结束自动释放,速度快,无需 GC
// - 堆:需要 GC 回收,速度慢
//
// 当编译器发现变量的生命周期超出函数范围,就会"逃逸"到堆上
// 用命令查看逃逸分析:go build -gcflags="-m" main.go

// 案例1:返回局部变量指针 → 逃逸
func newUser(name string) *User {
    u := User{Name: name} // u 逃逸到堆上(因为返回了指针)
    return &u
}

type User struct {
    Name string
}

// 案例2:不返回指针 → 不逃逸
func createUser(name string) User {
    u := User{Name: name} // u 在栈上分配
    return u               // 值拷贝返回
}

// 案例3:interface{} 参数 → 逃逸
func printAnything(v interface{}) {
    fmt.Println(v) // v 逃逸(fmt.Println 参数是 interface{})
}

// 案例4:闭包引用 → 逃逸
func makeGreeter() func() string {
    name := "Alice" // name 逃逸(被闭包引用,生命周期超出函数)
    return func() string {
        return "Hello, " + name
    }
}

// 案例5:切片扩容 → 可能逃逸
func growSlice() {
    s := make([]int, 0)
    for i := 0; i < 100; i++ {
        s = append(s, i) // 扩容时可能逃逸
    }
}

// 案例6:大对象 → 逃逸
func bigObject() {
    _ = make([]byte, 1<<16) // 64KB,太大了,逃逸到堆
}

func main() {
    // 编译器会自动决定,程序员通常不需要手动管理
    // 但了解逃逸可以帮助写出更高效的代码

    u1 := newUser("Alice")   // 堆分配
    u2 := createUser("Bob")  // 栈分配(更快)

    fmt.Println(u1.Name, u2.Name)
}

// ==================== 减少逃逸的技巧 ====================
// 1. 能返回值就不返回指针(小结构体)
// 2. 预分配切片/map 容量:make([]T, 0, expectedSize)
// 3. 减少 interface{} 参数(考虑泛型替代)
// 4. 使用 sync.Pool 复用大对象
// 5. 避免在循环中创建闭包捕获外部变量

49.2 垃圾回收

go
package main

import (
    "fmt"
    "runtime"
    "runtime/debug"
)

func main() {
    // ==================== Go 的 GC:三色标记 + 并发回收 ====================
    //
    // 三色标记法:
    // 白色:未访问的对象(GC 后会被回收)
    // 灰色:已访问但子对象未扫描完
    // 黑色:已访问且子对象已扫描完(存活)
    //
    // GC 过程(简化):
    // 1. 标记开始(STW 极短)→ 开启写屏障
    // 2. 并发标记(与程序同时运行)
    // 3. 标记终止(STW 极短)→ 关闭写屏障
    // 4. 并发清扫(回收白色对象)
    //
    // GC 触发条件:
    // - 堆内存增长到上次 GC 后的 2 倍(GOGC=100 时)
    // - 手动调用 runtime.GC()
    // - 超过 2 分钟未 GC

    // 查看 GC 统计
    var stats runtime.MemStats
    runtime.ReadMemStats(&stats)
    fmt.Printf("堆内存: %d KB\n", stats.HeapAlloc/1024)
    fmt.Printf("GC 次数: %d\n", stats.NumGC)
    fmt.Printf("GC 暂停总时长: %d ms\n", stats.PauseTotalNs/1_000_000)

    // 手动触发 GC
    runtime.GC()
    runtime.ReadMemStats(&stats)
    fmt.Printf("GC 后堆内存: %d KB\n", stats.HeapAlloc/1024)

    // ==================== GC 调优 ====================

    // GOGC:控制 GC 频率(默认 100)
    // GOGC=100 → 堆增长到 2 倍时触发 GC
    // GOGC=200 → 堆增长到 3 倍时触发(GC 频率低,内存用多)
    // GOGC=50  → 堆增长到 1.5 倍时触发(GC 频率高,内存用少)
    old := debug.SetGCPercent(200)
    fmt.Println("旧 GOGC:", old)

    // GOMEMLIMIT(Go 1.19+):设置内存软上限
    // 比 GOGC 更精确地控制内存使用
    debug.SetMemoryLimit(1 << 30) // 1 GB

    // ==================== sync.Pool:对象复用 ====================
    // 减少 GC 压力的利器
    // 详见标准库 sync.Pool

    // ==================== 性能提示 ====================
    // 1. 运行时加 GODEBUG=gctrace=1 查看每次 GC 的详细信息
    // 2. 用 go tool pprof 分析内存分配热点
    // 3. 减少小对象分配(如 string 拼接改用 strings.Builder)
    // 4. 预分配容量(make([]T, 0, n))
    // 5. 复用大对象(sync.Pool)
    // 6. 注意意外的内存保持(如切片引用大数组的一小段)
}

总结

🎯 学习建议

  1. 动手实践:每个代码示例都尝试运行和修改
  2. 理解本质:Go 的核心是值语义和组合,不是 OOP
  3. 关注工程实践:error 处理、接口设计、并发模式是 Go 的核心竞争力
  4. 阅读标准库:Go 标准库是最好的学习材料(io, net/http, sync
  5. 使用工具go vet, golangci-lint, go test -race 帮你发现问题

📚 推荐学习资源

资源链接
Go 官方教程go.dev/tour
Effective Gogo.dev/doc/effective_go
Go by Examplegobyexample.com
The Go Programming Language(书)gopl.io
Go 标准库文档pkg.go.dev/std