Effective Go 学习笔记

本文档基于 Go 官方文档 Effective Go 整理编写。 每个知识点配有可运行的示例代码,帮助理解和实践。

注意:Effective Go 最初编写于 2009 年(Go 发布时),未涵盖泛型(Generics)等后续新增的语言特性。如需了解最新变化,请参考 Go Release Notes


1. 引言(Introduction)

Effective Go 是学习编写地道 Go 代码的指南。Go 是一种注重简洁、可靠、高效的语言。要写好 Go 代码,需要理解其特性和惯用法——不能简单地把 C++ 或 Java 的写法翻译成 Go。

核心理念:Go 程序应该在结构、命名、格式等方面遵循社区约定,这样其他 Go 程序员能轻松理解你的代码。

示例:Go 的"从问题出发"思维

package main

import "fmt"

// 传统 OOP 风格(不推荐)
type Printer struct{}

func (p Printer) Print(s string) {
    fmt.Println(s)
}

// Go 风格:用函数/接口组合(推荐)
func Print(s string) {
    fmt.Println(s)
}

func main() {
    // Go 不推荐过度封装,简单的功能直接用函数
    Print("Hello, Effective Go!")
}

2. 格式(Formatting)

Go 的格式由 gofmt(或 go fmt)自动处理。格式化问题交给机器,不再需要团队争论代码风格。

关键点

  • 缩进使用 Tab(gofmt 默认)
  • 行长度无限制——太长时手动折行,用额外 Tab 缩进
  • 控制结构(ifforswitch没有括号
  • 运算符优先级更清晰

示例:gofmt 自动对齐

package main

import "fmt"

type T struct {
    name  string // 对象名称
    value int    // 它的值
}

// gofmt 会自动对齐注释
func main() {
    // 左大括号必须与 if/for 在同一行
    if true {
        fmt.Println("大括号不能另起一行")
    }

    // 运算符优先级符合直觉
    x, y := 2, 3
    // x<<8 + y<<16 含义与间距一致
    result := x<<8 + y<<16
    fmt.Printf("结果: %d\n", result) // 2*256 + 3*65536 = 512 + 196608 = 197120
}

练习:运行 gofmt

# 格式化当前目录所有 .go 文件
gofmt -w *.go

# 或使用 go fmt
go fmt ./...

3. 注释(Commentary)

Go 支持两种注释风格:

  • // 行注释(推荐,大多数情况使用)
  • /* */ 块注释(主要用于包注释或临时禁用大段代码)

文档注释:出现在顶级声明之前、没有空行分隔的注释,即为该声明的文档注释。

package main

import "fmt"

// Package 级别的文档注释
// 这个包演示了 Go 的注释风格

// Greet 返回一个问候语。
// 这是函数的文档注释,会在 godoc 中显示。
func Greet(name string) string {
    // 这是行内注释,不会被 godoc 收录
    return "Hello, " + name
}

/*
这是块注释风格,
主要用于包注释或临时禁用代码。
*/
func main() {
    msg := Greet("世界")
    fmt.Println(msg)

    /*
        块注释可以禁用大段代码
        fmt.Println("这段不会执行")
        fmt.Println("这段也不会")
    */
}

4. 命名(Names)

4.1 包命名(Package Names)

  • 小写、单个单词,不要下划线或驼峰
  • 简洁、有描述性
  • 包名是导入时的默认访问器
package main

import (
    "bufio"    // 包名是 bufio,不是 buf_io 或 bufIo
    "container/ring" // 包名是 ring,不是 container_ring
    "fmt"
)

func main() {
    // 包中的类型通过"包名.类型名"访问
    r := ring.New(5) // 不叫 ring.NewRing,因为包名 ring 已经说明了含义
    fmt.Println(r.Len())

    // bufio.Reader 而不是 bufio.BufReader
    // io.Reader 和 bufio.Reader 通过包名区分,不会冲突
    _ = bufio.NewReaderSize(nil, 1024)
}

4.2 Getter 方法

Go 不自动提供 getter/setter。如果你需要,getter 方法名不应该包含 Get

package main

import "fmt"

// Owner 是一个演示类型
type Owner struct {
    owner string // 小写、未导出
    age   int
}

// Getter: 直接叫 Owner,不叫 GetOwner
func (o *Owner) Owner() string {
    return o.owner
}

// Setter: 可以叫 SetOwner
func (o *Owner) SetOwner(name string) {
    o.owner = name
}

func main() {
    obj := &Owner{owner: "张三"}
    fmt.Println(obj.Owner()) // 不叫 GetOwner

    obj.SetOwner("李四")
    fmt.Println(obj.Owner())
}

4.3 接口命名(Interface Names)

单方法接口按惯例以方法名 + -er 后缀命名:

package main

import "fmt"

// 按照惯例定义自己的单方法接口
type Stringer interface {
    String() string
}

// Greeter 是 -er 接口的典型命名
type Greeter interface {
    Greet() string
}

type Person struct {
    Name string
}

// Person 实现了 Greeter 接口
func (p Person) Greet() string {
    return "你好,我是" + p.Name
}

func main() {
    var g Greeter = Person{Name: "小明"}
    fmt.Println(g.Greet())

    // 不要给方法起冲突的名字
    // 如果你的类型要实现"字符串转换",叫 String() 而不是 ToString()
}

4.4 MixedCaps

Go 使用 MixedCapsmixedCaps(驼峰式),不使用下划线

package main

import (
    "encoding/base64"
    "fmt"
)

// 导出的 → 大写开头
// 未导出的 → 小写开头
type UserProfile struct { // 不是 user_profile
    UserName  string // 不是 user_name
    LoginTime int64  // 不是 login_time
}

func main() {
    // 标准库也都是驼峰
    encoding := base64.StdEncoding
    fmt.Println(encoding.EncodeToString([]byte("hello")))
}

5. 分号(Semicolons)

Go 的词法分析器会自动插入分号。规则很简单:

如果换行前的最后一个 token 是 标识符字面量breakcontinuefallthroughreturn++--)},则自动插入分号。

重要推论:左大括号 { 不能放在下一行。

package main

import "fmt"

func main() {
    // ✅ 正确:左大括号在同一行
    if true {
        fmt.Println("正确写法")
    }

    // ❌ 错误:左大括号在下一行(不会编译)
    // if true
    // {
    //     fmt.Println("错误写法")
    // }

    // 分号只在 for 循环的初始/条件/递增之间有明确需要
    for i := 0; i < 3; i++ {
        fmt.Println(i)
    }

    // 一行写多条语句时需要显式分号
    a := 1; b := 2; fmt.Println(a, b)
}

6. 控制结构(Control Structures)

6.1 If

if 可以包含初始化语句;当 if 体以 returnbreakcontinuegoto 结束时,省略 else

package main

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

func main() {
    // 基本 if(无括号)
    x := 10
    if x > 5 {
        fmt.Println("x 大于 5")
    }

    // if 带初始化语句
    if err := validateAge(-1); err != nil {
        fmt.Println("验证失败:", err)
    }

    // 省略 else 的惯用风格:成功路径沿页面下行
    content, err := readFile("test.txt")
    if err != nil {
        fmt.Println("读取失败:", err)
        return
    }
    fmt.Println("内容:", content)

    // 多个错误检查链
    numStr := "42"
    num, err := strconv.Atoi(numStr)
    if err != nil {
        fmt.Println("转换失败:", err)
        return
    }
    fmt.Println("数字:", num)
}

func validateAge(age int) error {
    if age < 0 {
        return errors.New("年龄不能为负数")
    }
    return nil
}

func readFile(name string) (string, error) {
    data, err := os.ReadFile(name)
    if err != nil {
        return "", err
    }
    return string(data), nil
}

6.2 重复声明与重新赋值(Redeclaration and Reassignment)

:= 短声明中,只要至少有一个变量是新声明的,就可以重复使用已有变量(如 err)。

package main

import (
    "errors"
    "fmt"
)

func main() {
    // 首次声明 f 和 err
    f, err := openFile("test.txt")
    if err != nil {
        fmt.Println("第一次打开失败:", err)
    }
    _ = f

    // 再次使用 :=,d 是新变量,err 是重新赋值
    d, err := openFile("config.txt")
    if err != nil {
        fmt.Println("第二次打开失败:", err)
    }
    _ = d

    // 重要:err 只被重新赋值(同一作用域),d 是新声明的
    // 如果 err 在外层作用域已存在,这里会创建新的 err
    fmt.Println("成功")
}

func openFile(name string) (string, error) {
    if name == "config.txt" {
        return "", errors.New("文件不存在")
    }
    return "文件内容", nil
}

6.3 For

Go 的 for 统一了 forwhile没有 do-while

package main

import "fmt"

func main() {
    // 形式1:完整的 for 语句
    sum := 0
    for i := 0; i < 5; i++ {
        sum += i
    }
    fmt.Println("sum =", sum) // 10

    // 形式2:类似 while
    n := 1
    for n < 100 {
        n *= 2
    }
    fmt.Println("n =", n) // 128

    // 形式3:无限循环(类似 for(;;))
    count := 0
    for {
        count++
        if count >= 3 {
            break
        }
    }
    fmt.Println("count =", count)

    // range 遍历数组/切片
    fruits := []string{"苹果", "香蕉", "橙子"}
    for i, fruit := range fruits {
        fmt.Printf("%d: %s\n", i, fruit)
    }

    // 只需要索引/键
    m := map[string]int{"a": 1, "b": 2}
    for key := range m {
        fmt.Println("key:", key)
    }

    // 只需要值
    nums := []int{10, 20, 30}
    for _, v := range nums {
        fmt.Println("value:", v)
    }

    // range 遍历字符串(自动解析 UTF-8)
    for pos, char := range "世界" {
        fmt.Printf("字符 %c 在字节位置 %d\n", char, pos)
    }

    // 并行赋值反转数组
    arr := []int{1, 2, 3, 4, 5}
    for i, j := 0, len(arr)-1; i < j; i, j = i+1, j-1 {
        arr[i], arr[j] = arr[j], arr[i]
    }
    fmt.Println("反转后:", arr) // [5 4 3 2 1]
}

6.4 Switch

Go 的 switch 比 C 更灵活:

  • 表达式不必是常量或整数
  • case 从上到下匹配,匹配即停(无需 break
  • switch 后无表达式等同于 switch true
package main

import "fmt"

// switch 代替 if-else-if 链
func unhex(c byte) byte {
    switch {
    case '0' <= c && c <= '9':
        return c - '0'
    case 'a' <= c && c <= 'f':
        return c - 'a' + 10
    case 'A' <= c && c <= 'F':
        return c - 'A' + 10
    }
    return 0
}

// 逗号分隔多个匹配值
func shouldEscape(c byte) bool {
    switch c {
    case ' ', '?', '&', '=', '#', '+', '%':
        return true
    }
    return false
}

func main() {
    fmt.Println(unhex('A')) // 10
    fmt.Println(unhex('3')) // 3

    fmt.Println(shouldEscape(' '))  // true
    fmt.Println(shouldEscape('a'))  // false

    // switch + break label
    src := []byte{0x01, 0x02, 0x81, 0x82}
outer:
    for n := 0; n < len(src); n++ {
        switch {
        case src[n] < 0x80:
            fmt.Printf("单字节: %d\n", src[n])
        default:
            if n+1 >= len(src) {
                fmt.Println("数据不完整")
                break outer
            }
            fmt.Printf("多字节: %d, %d\n", src[n], src[n+1])
            n++
        }
    }

    // 用 switch 比较字节切片
    a := []byte{1, 2, 3}
    b := []byte{1, 2, 4}
    fmt.Println(compareBytes(a, b)) // -1
}

func compareBytes(a, b []byte) int {
    for i := 0; i < len(a) && i < len(b); i++ {
        switch {
        case a[i] > b[i]:
            return 1
        case a[i] < b[i]:
            return -1
        }
    }
    switch {
    case len(a) > len(b):
        return 1
    case len(a) < len(b):
        return -1
    }
    return 0
}

6.5 类型 Switch(Type Switch)

通过 switch v := x.(type) 判断接口的动态类型。

package main

import "fmt"

func typeDescriptor(v interface{}) string {
    switch t := v.(type) {
    case nil:
        return "nil"
    case bool:
        return fmt.Sprintf("布尔值: %t", t)
    case int:
        return fmt.Sprintf("整数: %d", t)
    case float64:
        return fmt.Sprintf("浮点数: %f", t)
    case string:
        return fmt.Sprintf("字符串: %q", t)
    default:
        return fmt.Sprintf("未知类型: %T", t)
    }
}

func main() {
    values := []interface{}{
        nil,
        true,
        42,
        3.14,
        "hello",
        []int{1, 2, 3},
    }

    for _, v := range values {
        fmt.Println(typeDescriptor(v))
    }
}

7. 函数(Functions)

7.1 多返回值(Multiple Return Values)

Go 的函数和方法可以返回多个值。常用于返回结果和错误。

package main

import (
    "errors"
    "fmt"
    "unicode"
)

func isDigit(c byte) bool {
    return '0' <= c && c <= '9'
}

// nextInt 从字节切片中解析下一个整数,返回数字和下一个位置
func nextInt(b []byte, i int) (int, int) {
    for ; i < len(b) && !isDigit(b[i]); i++ {
    }
    x := 0
    for ; i < len(b) && isDigit(b[i]); i++ {
        x = x*10 + int(b[i]) - '0'
    }
    return x, i
}

// 文件和错误一起返回——Go 的惯用风格
func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("除数不能为零")
    }
    return a / b, nil
}

func main() {
    // 多返回值:解析数字
    b := []byte("价格: 42 个, 数量: 10 个")
    for i := 0; i < len(b); {
        x, i := nextInt(b, i)
        if x > 0 {
            fmt.Printf("找到数字: %d (下一个位置: %d)\n", x, i)
        }
    }

    // 多返回值:结果和错误
    result, err := divide(10, 3)
    if err != nil {
        fmt.Println("错误:", err)
    } else {
        fmt.Println("结果:", result)
    }

    // 忽略错误(不推荐,仅作演示)
    result, _ = divide(10, 2)
    fmt.Println("忽略错误:", result)
}

7.2 命名返回值(Named Result Parameters)

返回值可以命名,作为文档,并允许裸 return

package main

import (
    "errors"
    "fmt"
    "io"
)

// ReadFull 精确读取 len(buf) 字节
func ReadFull(r io.Reader, buf []byte) (n int, err error) {
    for len(buf) > 0 && err == nil {
        var nr int
        nr, err = r.Read(buf)
        n += nr
        buf = buf[nr:]
    }
    return // 裸 return,返回 n 和 err 的当前值
}

// 命名返回值作为文档
func divideWithRemainder(a, b int) (quotient, remainder int, err error) {
    if b == 0 {
        err = errors.New("除数不能为零")
        return // 等价于 return 0, 0, err
    }
    quotient = a / b
    remainder = a % b
    return // 等价于 return quotient, remainder, nil
}

func main() {
    // ReadFull 示例
    r := NewStringReader("hello world")
    buf := make([]byte, 5)
    n, err := ReadFull(r, buf)
    if err != nil {
        fmt.Println("读取错误:", err)
    } else {
        fmt.Printf("读取了 %d 字节: %s\n", n, buf) // 5, "hello"
    }

    // 命名返回值示例
    q, rem, err := divideWithRemainder(17, 5)
    if err == nil {
        fmt.Printf("17 / 5 = %d 余 %d\n", q, rem) // 3 余 2
    }
}

// 辅助类型:将字符串包装为 io.Reader
type stringReader struct {
    s string
    n int
}

func NewStringReader(s string) io.Reader {
    return &stringReader{s: s}
}

func (r *stringReader) Read(b []byte) (int, error) {
    if r.n >= len(r.s) {
        return 0, io.EOF
    }
    n := copy(b, r.s[r.n:])
    r.n += n
    return n, nil
}

7.3 Defer

defer 调度一个函数调用,在包含它的函数返回前执行。用于资源清理(关闭文件、解锁等)。

关键特性

  1. 延迟调用的参数defer 执行时立即求值(不是调用时)
  2. 多个 deferLIFO(后进先出)顺序执行
  3. 常用于关闭资源和释放锁
package main

import (
    "fmt"
    "os"
)

// Contents 读取文件内容并返回
func Contents(filename string) (string, error) {
    f, err := os.Open(filename)
    if err != nil {
        return "", err
    }
    defer f.Close() // 确保函数返回时关闭文件

    var result []byte
    buf := make([]byte, 100)
    for {
        n, err := f.Read(buf)
        result = append(result, buf[:n]...)
        if err != nil {
            break
        }
    }
    return string(result), nil
}

// defer 参数立即求值
func deferArgs() {
    for i := 0; i < 5; i++ {
        defer fmt.Printf("%d ", i) // 打印: 4 3 2 1 0(LIFO且i在defer时已求值)
    }
}

// 使用 defer 跟踪函数执行
func trace(s string) string {
    fmt.Println("进入:", s)
    return s
}

func un(s string) {
    fmt.Println("离开:", s)
}

func a() {
    defer un(trace("a"))
    fmt.Println("在 a 里面")
}

func b() {
    defer un(trace("b"))
    fmt.Println("在 b 里面")
    a()
}

func main() {
    // 读取自己的源代码
    content, err := Contents("effective_go_study.md")
    if err != nil {
        fmt.Println("文件不存在,演示跳过:", err)
    } else {
        fmt.Printf("文件大小: %d 字节\n", len(content))
    }

    // defer 参数立即求值
    fmt.Println("\ndefer 参数求值演示:")
	deferArgs()

    // defer 跟踪函数
    fmt.Println("\n函数跟踪演示:")
    b()
}

8. 数据(Data)

8.1 new 分配

new(T) 分配零值内存,返回 *T。适用于所有类型

Go 1.26 新增new 现在支持 new(expr) 语法,可以直接指定初始值。

package main

import (
    "fmt"
    "sync"
)

type SyncedBuffer struct {
    lock   sync.Mutex
    buffer bytes.Buffer
}

func main() {
    // new 返回零值指针
    p := new(int)   // *int,值为 0
    fmt.Println(*p) // 0

    // Go 1.26+: new 可以指定初始值
    // p2 := new(int64(300))
    // fmt.Println(*p2) // 300(需要 Go 1.26)

    // 零值可用的设计
    var mu sync.Mutex
    mu.Lock() // 零值 Mutex 是未锁定状态,可直接使用
    mu.Unlock()

    // new 复合类型
    buf := new(bytes.Buffer) // 零值 Buffer 是空缓冲区,可直接使用
    buf.WriteString("hello")
    fmt.Println(buf.String()) // "hello"

    // SyncedBuffer 的零值也立即可用
    sb := new(SyncedBuffer)
    sb.lock.Lock()
    sb.buffer.WriteString("hello")
    sb.lock.Unlock()
}

8.2 构造函数与复合字面量(Constructors and Composite Literals)

当零值不够时,使用构造函数。复合字面量可以大大简化构造过程。

package main

import "fmt"

type File struct {
    fd      int
    name    string
    dirinfo string
    nepipe  int
}

// 构造函数:手动初始化
func NewFileV1(fd int, name string) *File {
    if fd < 0 {
        return nil
    }
    f := new(File)
    f.fd = fd
    f.name = name
    f.dirinfo = ""
    f.nepipe = 0
    return f
}

// 改进:复合字面量
func NewFileV2(fd int, name string) *File {
    if fd < 0 {
        return nil
    }
    return &File{fd: fd, name: name}
}

// 数组和 map 的复合字面量
func main() {
    // 结构体字面量
    f1 := NewFileV2(1, "test.txt")
    fmt.Printf("文件: %+v\n", f1)

    // 带字段标签的字面量(推荐)
    f2 := &File{fd: 2, name: "config.txt"}
    fmt.Printf("文件: %+v\n", f2)

    // 数组字面量
    arr := [...]int{0: 10, 2: 30, 1: 20} // 按索引指定
    fmt.Println(arr) // [10 20 30]

    // 切片字面量
    slice := []string{1: "第二", 0: "第一"}
    fmt.Println(slice) // ["第一" "第二"]

    // map 字面量
    m := map[int]string{
        200: "OK",
        404: "Not Found",
        500: "Server Error",
    }
    fmt.Printf("HTTP 状态码: %+v\n", m)
}

8.3 make 分配

make(T, args) 用于创建切片、map 和 channel。它返回初始化后的值(不是指针)。

特性 new make
适用类型 所有类型 仅 slice/map/channel
返回 *T(指针) T(值)
初始化 零值 初始化内部数据结构
package main

import "fmt"

func main() {
    // make: 创建切片(返回已初始化的值)
    v := make([]int, 10, 100) // 底层数组 100 个 int,切片长度 10,容量 100
    fmt.Printf("len=%d cap=%d\n", len(v), cap(v))

    // new: 创建切片(返回指向 nil 切片的指针,很少有用)
    p := new([]int)                // p 是 *[]int
    fmt.Printf("new切片: %v (len=%d)\n", *p, len(*p))

    // 👆 new 切片后用 make 初始化(不必要地复杂)
    *p = make([]int, 100)
    fmt.Printf("初始化后 len=%d\n", len(*p))

    // 👇 惯用写法
    v2 := make([]int, 100)
    fmt.Printf("惯用写法 len=%d\n", len(v2))

    // make map
    scores := make(map[string]int)
    scores["张三"] = 95
    fmt.Println("分数:", scores)

    // make channel
    ch := make(chan int, 10)
    ch <- 42
    fmt.Println("从 channel 接收:", <-ch)
}

8.4 数组(Arrays)

Go 中数组是值类型(赋值和传参都复制整个数组),数组大小是类型的一部分。

package main

import "fmt"

// 传递数组指针(类似 C 风格,但 Go 中不推荐)
func SumByPtr(a *[3]float64) (sum float64) {
    for _, v := range *a {
        sum += v
    }
    return
}

func main() {
    // 数组是值类型
    a := [3]int{1, 2, 3}
    b := a      // 复制整个数组
    b[0] = 100
    fmt.Println(a, b) // [1 2 3] [100 2 3]

    // 数组大小是类型的一部分
    fmt.Printf("类型: %T\n", [3]int{})   // [3]int
    fmt.Printf("类型: %T\n", [5]int{})   // [5]int(不同大小 = 不同类型)

    // 传递数组指针
    arr := [...]float64{7.0, 8.5, 9.1}
    sum := SumByPtr(&arr) // 必须显式取地址
    fmt.Println("求和:", sum)

    // 但一般情况下,用切片代替数组
    arr2 := []float64{7.0, 8.5, 9.1}
    sum2 := 0.0
    for _, v := range arr2 {
        sum2 += v
    }
    fmt.Println("切片求和:", sum2)
}

8.5 切片(Slices)

切片是对底层数组的引用(类似胖指针:ptr + len + cap)。切片传参时,函数对元素的修改对调用方可见。

package main

import "fmt"

// Append 手动实现 append 的功能
func Append(slice []byte, data []byte) []byte {
    l := len(slice)
    if l+len(data) > cap(slice) {
        // 容量不足,重新分配(通常分配 2 倍)
        newSlice := make([]byte, (l+len(data))*2)
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0 : l+len(data)]
    copy(slice[l:], data)
    return slice
}

func main() {
    // 切片持有对底层数组的引用
    arr := [5]int{1, 2, 3, 4, 5}
    s1 := arr[1:4] // 引用 arr[1], arr[2], arr[3]
    s1[0] = 99
    fmt.Println(arr) // [1 99 3 4 5] — 原数组被修改

    // 函数修改切片元素
    modifyFirst(s1)
    fmt.Println(arr) // [1 100 3 4 5]

    // 切片长度和容量
    s2 := []int{0, 1, 2, 3, 4, 5}
    s3 := s2[1:4]          // len=3, cap=5
    fmt.Printf("len=%d cap=%d\n", len(s3), cap(s3))

    // 读文件时使用切片限制读取量
    buf := make([]byte, 1024)
    _ = buf
    // f.Read(buf[0:32]) // 只读前 32 字节

    // 自定义 Append
    slice := []byte{1, 2, 3}
    slice = Append(slice, []byte{4, 5})
    fmt.Println("Append 结果:", slice)

    // append 内建函数
    s := []int{1, 2, 3}
    s = append(s, 4, 5, 6)
    fmt.Println("append 内建:", s)

    // append slice to slice
    x := []int{1, 2, 3}
    y := []int{4, 5, 6}
    x = append(x, y...) // ... 展开
    fmt.Println("展开追加:", x)
}

func modifyFirst(s []int) {
    if len(s) > 0 {
        s[0] = 100
    }
}

8.6 二维切片(Two-dimensional Slices)

package main

import "fmt"

func main() {
    // 方式1:逐行分配(每行长度可不同)
    picture1 := make([][]uint8, 4)
    for i := range picture1 {
        picture1[i] = make([]uint8, 6)
    }
    fmt.Printf("逐行分配: %d x %d\n", len(picture1), len(picture1[0]))

    // 方式2:一次分配再切片(每行长度相同,性能更优)
    picture2 := make([][]uint8, 4)      // 顶层切片
    pixels := make([]uint8, 4*6)        // 一次分配所有像素
    for i := range picture2 {
        picture2[i], pixels = pixels[:6], pixels[6:]
    }
    fmt.Printf("单次分配: %d x %d\n", len(picture2), len(picture2[0]))

    // 每行可以不同长度
    text := [][]byte{
        []byte("第一行"),
        []byte("第二行,更长一些"),
        []byte("第三行"),
    }
    for i, line := range text {
        fmt.Printf("行 %d: len=%d, 内容=%s\n", i+1, len(line), line)
    }
}

8.7 Map

Map 是 Go 的内建关联类型。slice 不能做 key(没有相等性定义)。

package main

import "fmt"

var timeZone = map[string]int{
    "UTC": 0 * 60 * 60,
    "EST": -5 * 60 * 60,
    "CST": -6 * 60 * 60,
    "MST": -7 * 60 * 60,
    "PST": -8 * 60 * 60,
}

// 使用 map 实现集合
func main() {
    // 访问 map
    offset := timeZone["EST"]
    fmt.Println("EST 偏移:", offset) // -18000

    // 不存在的 key 返回零值
    fmt.Println("未知键:", timeZone["XYZ"]) // 0

    // 逗号 ok 惯用法(区分"存在但为零"和"不存在")
    seconds, ok := timeZone["UTC"]
    fmt.Printf("UTC: seconds=%d, ok=%v\n", seconds, ok) // 0, true

    seconds, ok = timeZone["XYZ"]
    fmt.Printf("XYZ: seconds=%d, ok=%v\n", seconds, ok) // 0, false

    // 用 map 实现集合
    attended := map[string]bool{
        "张三": true,
        "李四": true,
    }

    check := []string{"张三", "王五"}
    for _, person := range check {
        if attended[person] {
            fmt.Printf("%s 参加了会议\n", person)
        } else {
            fmt.Printf("%s 未参加\n", person)
        }
    }

    // 只检查存在性(忽略值)
    _, present := timeZone["PST"]
    fmt.Println("PST 存在:", present)

    // 删除 map 条目
    delete(timeZone, "PST")
    _, present = timeZone["PST"]
    fmt.Println("删除后 PST 存在:", present)
}

// offset 函数:查找时区偏移,带友好错误
func offset(tz string) int {
    if seconds, ok := timeZone[tz]; ok {
        return seconds
    }
    fmt.Println("未知时区:", tz)
    return 0
}

9. 打印(Printing)

fmt 包的格式化打印比 C 的 printf 更强大和通用。

package main

import "fmt"

type MyString string

// 自定义类型的 String() 方法
func (m MyString) String() string {
    return fmt.Sprintf("MyString=%s", string(m)) // 转换为普通 string 避免递归
}

func (m MyString) Fun() string {
    return "fun " + string(m)
}

type T struct {
    a int
    b float64
    c string
}

func main() {
    // Printf / Println / Sprint
    fmt.Printf("Hello %d\n", 23)
    fmt.Println("Hello", 23)
    fmt.Println(fmt.Sprint("Hello ", 23))

    // %d 自动根据类型选择格式
    var x uint64 = 1<<64 - 1
    fmt.Printf("%d %x; %d %x\n", x, x, int64(x), int64(x))

    // %v 万能格式(任意类型)
    timeZone := map[string]int{
        "CST": -6 * 60 * 60,
        "EST": -5 * 60 * 60,
    }
    fmt.Printf("%v\n", timeZone) // map[键值对](按键排序)

    t := &T{7, -2.35, "abc\tdef"}
    fmt.Printf("%v\n", t)   // &{7 -2.35 abc   def}
    fmt.Printf("%+v\n", t)  // &{a:7 b:-2.35 c:abc     def}
    fmt.Printf("%#v\n", t)  // &main.T{a:7, b:-2.35, c:"abc\tdef"}

    // %T 打印类型
    fmt.Printf("%T\n", timeZone) // map[string]int

    // %q 打印带引号的字符串
    fmt.Printf("%q\n", "hello\t世界") // "hello\t世界"

    // String() 方法在自定义类型上的效果
    var ms MyString = "hello"
    fmt.Println(ms) // MyString=hello

    // ... 参数传递
    logPrintln("这是日志:", "key=", "value")

    // ...int 参数
    fmt.Println(minInt(3, 7, 2, 9)) // 2
}

func logPrintln(v ...interface{}) {
    // 把 v 当作参数列表传递给另一个可变参数函数
    fmt.Println(v...)
}

func minInt(a ...int) int {
    min := int(^uint(0) >> 1) // 最大的 int
    for _, i := range a {
        if i < min {
            min = i
        }
    }
    return min
}

10. Append

append 是内建函数,因为它的泛型特性无法用普通 Go 函数实现。

package main

import "fmt"

func main() {
    // 基本用法
    x := []int{1, 2, 3}
    x = append(x, 4, 5, 6)
    fmt.Println(x) // [1 2 3 4 5 6]

    // 追加另一个切片(使用 ... 展开)
    y := []int{7, 8, 9}
    x = append(x, y...)
    fmt.Println(x) // [1 2 3 4 5 6 7 8 9]

    // append 字符串到字节切片
    var b []byte
    b = append(b, "hello"...)
    b = append(b, " world"...)
    fmt.Println(string(b)) // "hello world"

    // nil 切片也可以 append
    var s []int
    s = append(s, 1, 2, 3)
    fmt.Println(s) // [1 2 3]

    // append 返回值必须赋值回原变量(底层数组可能改变)
    nums := make([]int, 3, 5) // len=3, cap=5
    nums2 := append(nums, 10, 20)
    nums[0] = 99
    fmt.Println(nums, nums2) // [99 0 0] [99 0 0 10 20](共享底层数组)
}

11. 初始化(Initialization)

11.1 常量与 iota

package main

import "fmt"

type ByteSize float64

const (
    _           = iota // 忽略第一个值
    KB ByteSize = 1 << (10 * iota) // 1 << (10*1)
    MB                           // 1 << (10*2)
    GB                           // 1 << (10*3)
    TB                           // 1 << (10*4)
    PB                           // 1 << (10*5)
    EB                           // 1 << (10*6)
    ZB                           // 1 << (10*7)
    YB                           // 1 << (10*8)
)

// 为 ByteSize 实现 String 方法(自动格式化打印)
func (b ByteSize) String() string {
    switch {
    case b >= YB:
        return fmt.Sprintf("%.2fYB", b/YB)
    case b >= ZB:
        return fmt.Sprintf("%.2fZB", b/ZB)
    case b >= EB:
        return fmt.Sprintf("%.2fEB", b/EB)
    case b >= PB:
        return fmt.Sprintf("%.2fPB", b/PB)
    case b >= TB:
        return fmt.Sprintf("%.2fTB", b/TB)
    case b >= GB:
        return fmt.Sprintf("%.2fGB", b/GB)
    case b >= MB:
        return fmt.Sprintf("%.2fMB", b/MB)
    case b >= KB:
        return fmt.Sprintf("%.2fKB", b/KB)
    }
    return fmt.Sprintf("%.2fB", b)
}

func main() {
    fmt.Println(YB)        // 1.00YB
    fmt.Println(ByteSize(1e13)) // 9.09TB
    fmt.Println(KB, MB, GB) // 1.00KB 1.00MB 1.00GB
}

11.2 变量初始化

package main

import (
    "fmt"
    "os"
)

var (
    home   = os.Getenv("HOME")
    user   = os.Getenv("USER")
    gopath = os.Getenv("GOPATH")
)

func main() {
    fmt.Printf("HOME=%s, USER=%s, GOPATH=%s\n", home, user, gopath)
}

11.3 init 函数

每个文件可以有多个 init 函数,在包初始化时自动执行。

package main

import "fmt"

// 预检查
var configMode string

func init() {
    fmt.Println("第一个 init 函数执行")
    if user == "" {
        fmt.Println("警告: $USER 未设置")
    }
    if home == "" {
        fmt.Println("警告: $HOME 未设置,使用默认值")
        home = "/home/" + user
    }
}

func init() {
    fmt.Println("第二个 init 函数执行")
    // 验证配置
    configMode = "production"
    fmt.Printf("配置模式: %s\n", configMode)
}

func main() {
    fmt.Println("main 函数执行")
    fmt.Printf("最终值: home=%s, user=%s\n", home, user)
}

12. 方法(Methods)

指针接收者 vs 值接收者

规则:值方法可以在指针和值上调用;指针方法只能在指针上调用(但可寻址的值会自动取地址)。

package main

import (
    "fmt"
    "io"
)

type ByteSlice []byte

// 值接收者:返回新切片(不修改原值)
func (slice ByteSlice) Append(data []byte) []byte {
    l := len(slice)
    if l+len(data) > cap(slice) {
        newSlice := make([]byte, (l+len(data))*2)
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0 : l+len(data)]
    copy(slice[l:], data)
    return slice
}

// 指针接收者:直接修改接收者
func (p *ByteSlice) AppendInPlace(data []byte) {
    slice := *p
    l := len(slice)
    if l+len(data) > cap(slice) {
        newSlice := make([]byte, (l+len(data))*2)
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0 : l+len(data)]
    copy(slice[l:], data)
    *p = slice
}

// 实现 io.Writer 接口
func (p *ByteSlice) Write(data []byte) (n int, err error) {
    slice := *p
    l := len(slice)
    if l+len(data) > cap(slice) {
        newSlice := make([]byte, (l+len(data))*2)
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0 : l+len(data)]
    copy(slice[l:], data)
    *p = slice
    return len(data), nil
}

func main() {
    // 值接收者
    var b ByteSlice
    b = b.Append([]byte("hello"))
    fmt.Println(string(b)) // "hello"

    // 指针接收者
    b.AppendInPlace([]byte(" world"))
    fmt.Println(string(b)) // "hello world"

    // 实现 io.Writer 后,可以用 fmt.Fprintf
    var buf ByteSlice
    fmt.Fprintf(&buf, "This hour has %d days\n", 7)
    fmt.Println(string(buf)) // "This hour has 7 days\n"
}

13. 接口与其他类型(Interfaces and other types)

13.1 接口基础

接口定义了行为:如果某物能做这个,那它就能用在这里

package main

import (
    "fmt"
    "sort"
)

type Sequence []int

// 实现 sort.Interface(Len, Less, Swap)
func (s Sequence) Len() int           { return len(s) }
func (s Sequence) Less(i, j int) bool { return s[i] < s[j] }
func (s Sequence) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }

// Copy 返回副本
func (s Sequence) Copy() Sequence {
    copy := make(Sequence, 0, len(s))
    return append(copy, s...)
}

// String — 排序后格式化打印
func (s Sequence) String() string {
    s = s.Copy()
    sort.Sort(s)
    return fmt.Sprint([]int(s)) // 转换为 []int 避免递归
}

func main() {
    seq := Sequence{3, 1, 4, 1, 5, 9, 2, 6}
    fmt.Println(seq) // [1 1 2 3 4 5 6 9]

    // 使用 sort.IntSlice 简化
    seq2 := Sequence{9, 8, 7, 6}
    s := seq2.Copy()
    sort.IntSlice(s).Sort() // 转换类型获取方法
    fmt.Println(s)
}

13.2 类型转换(Conversions)

Go 的类型转换可以在相关类型之间进行,用于访问不同的方法集。

package main

import (
    "fmt"
    "sort"
)

type Sequence2 []int

func (s Sequence2) String() string {
    s = s.Copy()
    sort.IntSlice(s).Sort() // 转换为 sort.IntSlice 访问 .Sort()
    return fmt.Sprint([]int(s))
}

func (s Sequence2) Copy() Sequence2 {
    copy := make(Sequence2, 0, len(s))
    return append(copy, s...)
}

func main() {
    s := Sequence2{5, 3, 1, 4, 2}
    fmt.Println(s)
}

13.3 接口转换与类型断言(Type Assertions)

package main

import "fmt"

type Stringer interface {
    String() string
}

func toString(value interface{}) string {
    // 类型断言 + 逗号 ok
    if str, ok := value.(string); ok {
        return str
    }
    if str, ok := value.(Stringer); ok {
        return str.String()
    }
    return fmt.Sprintf("未知类型: %T", value)
}

type User struct {
    Name string
}

func (u User) String() string {
    return "用户: " + u.Name
}

func main() {
    values := []interface{}{
        "直接字符串",
        User{Name: "张三"},
        42,
    }

    for _, v := range values {
        fmt.Println(toString(v))
        /*
            输出:
            直接字符串
            用户: 张三
            未知类型: int
        */
    }

    // 类型断言的 panic 风险
    var x interface{} = "hello"
    // str := x.(int) // 这会 panic!

    // 安全写法:使用逗号 ok
    str, ok := x.(string)
    if ok {
        fmt.Println("string:", str)
    }
}

13.4 接口与方法

几乎任何类型都可以拥有方法,从而几乎任何类型都能实现接口。

package main

import (
    "fmt"
    "net/http"
    "os"
)

// 整数作为 HTTP handler
type Counter int

func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    *ctr++
    fmt.Fprintf(w, "counter = %d\n", *ctr)
}

// Channel 作为 HTTP handler
type Chan chan *http.Request

func (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    ch <- req
    fmt.Fprint(w, "notification sent")
}

// 函数作为 HTTP handler(http.HandlerFunc 模式)
func ArgServer(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintln(w, os.Args)
}

// 演示:用函数类型实现接口
type GreetFunc func(name string) string

func (f GreetFunc) Greet(name string) string {
    return f(name)
}

func main() {
    // 函数类型实现接口
    var greeter GreetFunc = func(name string) string {
        return "你好, " + name
    }
    fmt.Println(greeter.Greet("世界"))
}

14. 空标识符(The blank identifier)

空标识符 _ 是一个写入即丢弃的值。用于需要变量但不需要值的地方。

14.1 多赋值中丢弃值

package main

import (
    "fmt"
    "os"
)

func main() {
    // 丢弃错误(⚠️ 不推荐,仅作示例)
    fi, _ := os.Stat("test.txt")
    if fi != nil {
        fmt.Printf("文件大小: %d\n", fi.Size())
    }

    // ✅ 正确的做法:检查错误
    if _, err := os.Stat("test.txt"); os.IsNotExist(err) {
        fmt.Println("文件不存在")
    }

    // range 中丢弃索引
    s := []string{"a", "b", "c"}
    for _, v := range s {
        fmt.Println(v)
    }

    // 检查 map 中键是否存在
    m := map[string]int{"a": 1, "b": 2}
    if _, exists := m["c"]; !exists {
        fmt.Println("键 'c' 不存在")
    }
}

14.2 抑制未使用导入和变量的编译错误

package main

import (
    "fmt"    // 使用中
    "io"     // 临时抑制,开发完成后删除
    "log"
    "os"
)

// 临时抑制未使用导入
var _ = io.Reader // 调试使用,完成后删除

func main() {
    fd, err := os.Open("test.go")
    if err != nil {
        log.Fatal(err)
    }
    // 临时抑制未使用变量
    _ = fd // TODO: 后续使用 fd

    fmt.Println("编译通过")
}

14.3 为副作用导入

// 导入 net/http/pprof 仅为其副作用(注册 HTTP handlers)
// import _ "net/http/pprof"

14.4 接口检查

package main

import (
    "encoding/json"
    "fmt"
)

// 检查类型是否实现接口
// 编译时验证:确保 *MyType 实现了 json.Marshaler
type MyType struct {
    Value string
}

func (m *MyType) MarshalJSON() ([]byte, error) {
    return json.Marshal(struct {
        Value string `json:"value"`
    }{m.Value})
}

// 编译时接口检查(全局声明,仅用于类型检查)
var _ json.Marshaler = (*MyType)(nil)

// 运行时接口检查
func checkMarshaler(v interface{}) {
    if _, ok := v.(json.Marshaler); ok {
        fmt.Printf("%T 实现了 json.Marshaler\n", v)
    } else {
        fmt.Printf("%T 未实现 json.Marshaler\n", v)
    }
}

func main() {
    jsonData := json.RawMessage(`{"key": "value"}`)
    checkMarshaler(&jsonData)
    checkMarshaler("plain string")
}

15. 嵌入(Embedding)

Go 不提供子类化,但通过嵌入实现"借用"实现。

15.1 接口嵌入

package main

import (
    "fmt"
    "io"
    "strings"
)

// 嵌入 Reader 和 Writer 组合成 ReadWriter
type ReadWriter interface {
    io.Reader
    io.Writer
}

func main() {
    // 一个 strings.Reader 实现了 io.Reader
    var r io.Reader = strings.NewReader("hello")

    // 但不能同时满足 ReadWriter(需要同时有 Read 和 Write)
    // 这里只是演示接口嵌入的概念
    var rw ReadWriter
    fmt.Printf("ReadWriter type: %T\n", rw)
    _ = r
}

15.2 结构体嵌入

package main

import (
    "fmt"
    "io"
    "log"
    "os"
    "strings"
)

// 嵌入 bufio.Reader 和 bufio.Writer
// type ReadWriter struct {
//     *bufio.Reader
//     *bufio.Writer
// }

// Job 类型嵌入了 *log.Logger
type Job struct {
    Command     string
    *log.Logger // 嵌入 Logger
}

// 自定义 Printf 覆盖默认行为
func (job *Job) Printf(format string, args ...interface{}) {
    job.Logger.Printf("%q: %s", job.Command, fmt.Sprintf(format, args...))
}

func main() {
    // 创建嵌入了 Logger 的 Job
    job := &Job{
        Command: "deploy.sh",
        Logger:  log.New(os.Stdout, "JOB: ", log.Ldate|log.Ltime),
    }

    // 直接调用嵌入类型的方法
    job.Println("开始执行...")
    job.Print("工作进行中...\n")

    // 调用覆盖后的方法
    job.Printf("完成 (code=%d)", 0)

    // 如果需要访问原始嵌入字段
    job.Logger.Println("这是通过原始 Logger 打印的")

    // 嵌入带方法的类型:满足接口
    // 写一个 ReadWriter
    var buf strings.Builder
    _ = buf

    // 嵌入的 ReadWriter 示例
    // 使用组合
    type MyReadWriter struct {
        reader io.Reader
        writer io.Writer
    }
    _ = MyReadWriter{
        reader: strings.NewReader("read"),
        writer: os.Stdout,
    }
}

15.3 嵌入的冲突规则

package main

import "fmt"

type Base struct {
    Name string
}

func (b Base) SayHello() string {
    return "你好, " + b.Name
}

type Derived struct {
    Base         // 嵌入
    Extra string
    Name  string // 覆盖 Base.Name
}

func main() {
    d := Derived{
        Base:  Base{Name: "内部"},
        Extra: "额外",
        Name:  "外部",
    }

    // 外部 Name 覆盖了 Base.Name
    fmt.Println("Name:", d.Name)  // "外部"

    // 可以通过类型名访问内部字段
    fmt.Println("Base.Name:", d.Base.Name) // "内部"

    // 嵌入类型的方法被提升
    fmt.Println(d.SayHello()) // "你好, 外部"(使用外部 Name)
}

16. 并发(Concurrency)

核心理念

不要通过共享内存来通信;而应该通过通信来共享内存。

16.1 Goroutines

Goroutine 是轻量级线程,由 Go 运行时管理。

package main

import (
    "fmt"
    "time"
)

func Announce(message string, delay time.Duration) {
    go func() {
        time.Sleep(delay)
        fmt.Println(message)
    }()
}

func main() {
    // 启动 goroutine
    go fmt.Println("这是并发执行的")
    go func() {
        fmt.Println("匿名函数 goroutine")
    }()

    // 用 goroutine 执行排序(模拟)
    list := []int{3, 1, 4, 1, 5}
    c := make(chan int)

    go func() {
        // 模拟排序
        time.Sleep(100 * time.Millisecond)
        c <- 1 // 发送信号
    }()

    fmt.Println("等待排序完成...")
    <-c // 等待信号
    fmt.Println("排序完成", list)

    // 延迟打印
    Announce("延迟消息", 50*time.Millisecond)
    time.Sleep(200 * time.Millisecond) // 等待 goroutine 完成
}

16.2 Channels

Channel 是 goroutine 之间的通信机制。可以带缓冲或无缓冲。

package main

import (
    "fmt"
    "time"
)

func main() {
    // 无缓冲 channel
    ci := make(chan int)
    go func() {
        ci <- 42
    }()
    fmt.Println("从 channel 接收:", <-ci)

    // 带缓冲 channel
    ch := make(chan string, 3)
    ch <- "a"
    ch <- "b"
    ch <- "c"
    fmt.Println("缓冲 channel:", <-ch, <-ch, <-ch)

    // channel 用作信号
    done := make(chan bool)
    go func() {
        time.Sleep(100 * time.Millisecond)
        fmt.Println("工作完成")
        done <- true
    }()
    <-done // 等待

    // 用 channel 做信号量控制并发
    const MaxOutstanding = 3
    sem := make(chan int, MaxOutstanding)

    workItems := []string{"任务1", "任务2", "任务3", "任务4", "任务5"}
    for _, item := range workItems {
        sem <- 1 // 获取信号量;满时阻塞
        go func(task string) {
            fmt.Println("处理:", task)
            time.Sleep(50 * time.Millisecond) // 模拟工作
            <-sem                             // 释放信号量
        }(item)
    }

    time.Sleep(300 * time.Millisecond)
    fmt.Println("所有任务完成")
}

16.3 Channel 的 Channel

Channel 是一等公民,可以像其他值一样传递。

package main

import (
    "fmt"
    "time"
)

type Request struct {
    args       []int
    f          func([]int) int
    resultChan chan int
}

func sum(a []int) int {
    s := 0
    for _, v := range a {
        s += v
    }
    return s
}

func handle(queue chan *Request) {
    for req := range queue {
        req.resultChan <- req.f(req.args)
    }
}

func main() {
    // 创建客户端请求 channel
    clientRequests := make(chan *Request)

    // 启动服务器 handler
    go handle(clientRequests)

    // 客户端创建请求
    resultChan := make(chan int)
    req := &Request{
        args:       []int{3, 4, 5},
        f:          sum,
        resultChan: resultChan,
    }

    // 发送请求
    clientRequests <- req

    // 等待结果
    result := <-resultChan
    fmt.Println("计算结果:", result) // 12

    // 更多测试
    go handle(clientRequests)
    for i := 0; i < 3; i++ {
        req := &Request{
            args:       []int{i, i*2, i*3},
            f:          sum,
            resultChan: make(chan int),
        }
        clientRequests <- req
        fmt.Printf("请求 %d 结果: %d\n", i, <-req.resultChan)
    }

    time.Sleep(10 * time.Millisecond)
}

16.4 并行计算(Parallelization)

package main

import (
    "fmt"
    "runtime"
    "time"
)

type Vector []float64

// Op 应用一个操作
func (v Vector) Op(x float64) float64 {
    return x * 2
}

// DoSome 对向量的一部分执行操作
func (v Vector) DoSome(i, n int, u Vector, c chan int) {
    for ; i < n; i++ {
        v[i] += u.Op(v[i])
    }
    c <- 1 // 信号:这部分完成
}

// DoAll 并行执行操作
func (v Vector) DoAll(u Vector) {
    numCPU := runtime.GOMAXPROCS(0) // 使用用户指定的 CPU 数
    c := make(chan int, numCPU)

    for i := 0; i < numCPU; i++ {
        start := i * len(v) / numCPU
        end := (i + 1) * len(v) / numCPU
        go v.DoSome(start, end, u, c)
    }

    // 等待所有部分完成
    for i := 0; i < numCPU; i++ {
        <-c
    }
}

func main() {
    // 创建向量
    v := Vector{1, 2, 3, 4, 5, 6, 7, 8}
    u := Vector{10, 10, 10, 10, 10, 10, 10, 10}

    fmt.Println("原始:", v)
    v.DoAll(u)
    fmt.Println("处理后:", v) // 每个元素: x + (x*2) = x*3

    // runtime.NumCPU 和 GOMAXPROCS
    fmt.Printf("CPU 核心数: %d\n", runtime.NumCPU())
    fmt.Printf("GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0))

    time.Sleep(10 * time.Millisecond)
}

16.5 泄漏桶缓冲区(Leaky Buffer)

package main

import (
    "fmt"
    "time"
)

type Buffer struct {
    data []byte
}

func (b *Buffer) String() string {
    return string(b.data)
}

var freeList = make(chan *Buffer, 5)
var serverChan = make(chan *Buffer, 10)

// client 模拟从网络读取数据并发送到服务端
func client() {
    counter := 0
    for {
        var b *Buffer
        select {
        case b = <-freeList:
            // 有空闲缓冲区
        default:
            // 没有空闲,分配新的
            b = &Buffer{}
        }

        // 模拟加载数据
        data := fmt.Sprintf("消息 #%d", counter)
        b.data = []byte(data)
        counter++

        serverChan <- b
        time.Sleep(20 * time.Millisecond)
    }
}

// server 处理消息并回收缓冲区
func server() {
    for {
        b := <-serverChan
        fmt.Println("处理:", b.String())

        // 尝试放回空闲列表
        select {
        case freeList <- b:
            // 缓冲区放回空闲列表
        default:
            // 空闲列表满了,丢弃(GC 会回收)
        }
    }
}

func main() {
    go client()
    go server()

    time.Sleep(200 * time.Millisecond)
    fmt.Println("泄漏桶演示完成")
}

17. 错误(Errors)

Go 中的错误是一个内建接口:

type error interface {
    Error() string
}
package main

import (
    "fmt"
    "os"
    "syscall"
)

// 自定义错误类型
type PathError struct {
    Op   string // "open", "unlink" 等
    Path string // 关联的文件
    Err  error  // 系统调用返回的错误
}

func (e *PathError) Error() string {
    return e.Op + " " + e.Pat h + ": " + e.Err.Error()
}

func Open(path string) error {
    // 模拟打开文件失败
    return &PathError{
        Op:   "open",
        Path: path,
        Err:  syscall.ENOENT,
    }
}

func main() {
    // 错误是值
    err := Open("/etc/passwx")
    if err != nil {
        fmt.Println("错误:", err)
        // 输出: open /etc/passwx: no such file or directory
    }

    // 使用类型断言获取详细错误信息
    _, err = os.Open("/nonexistent")
    if err != nil {
        if e, ok := err.(*os.PathError); ok {
            fmt.Printf("操作: %s, 文件: %s, 错误: %v\n", e.Op, e.Path, e.Err)
        }
    }

    // 重试模式:检查特定错误
    for try := 0; try < 2; try++ {
        _, err := os.Create("/dev/full") // 模拟磁盘满
        if err == nil {
            break // 成功
        }
        if e, ok := err.(*os.PathError); ok && e.Err == syscall.ENOSPC {
            fmt.Println("磁盘满,清理空间后重试...")
            // deleteTempFiles() // 清理空间
            break
        }
        fmt.Println("其他错误:", err)
        break
    }
}

18. Panic 与 Recover

  • panic:停止当前函数执行,开始展开栈(运行所有 defer)
  • recover:在延迟函数中调用,可以恢复 panic 的控制权
package main

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

// 安全的 goroutine 包装器
func safelyDo(work func()) (err error) {
    defer func() {
        if r := recover(); r != nil {
            log.Println("work panic:", r)
            err = fmt.Errorf("panic: %v", r)
        }
    }()
    work()
    return nil
}

// 使用 panic/recover 做错误处理的模式(包内部使用)
type Error string

func (e Error) Error() string { return string(e) }

type Regexp struct{}

func (r *Regexp) error(err string) {
    panic(Error(err))
}

func (r *Regexp) parse(str string) {
    if len(str) == 0 {
        r.error("空模式")
    }
    // ... 正常解析
}

func Compile(str string) (regexp *Regexp, err error) {
    regexp = new(Regexp)
    defer func() {
        if e := recover(); e != nil {
            regexp = nil
            err = e.(Error) // 如果不是 Error 类型,会继续 panic
        }
    }()
    regexp.parse(str)
    return
}

func main() {
    // 安全的 goroutine
    err := safelyDo(func() {
        panic("发生严重错误")
    })
    if err != nil {
        fmt.Println("捕获到错误:", err)
    }

    // 使用 panic/recover 实现错误处理
    re, err := Compile("valid")
    fmt.Printf("有效模式: re=%v, err=%v\n", re, err)

    re, err = Compile("")
    fmt.Printf("空模式: re=%v, err=%v\n", re, err)

    // 验证整数(非 panic 方式)
    result, err := safeDivide(10, 0)
    if err != nil {
        fmt.Println("除零错误:", err)
    }
    _ = result
}

func safeDivide(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("不能除以零")
    }
    return a / b, nil
}

什么时候该用 panic?

  • 真正的不可恢复错误(如初始化失败、不可能发生的情况)
  • 库函数内部使用 panic,但对外通过 error 返回
  • 绝大多数情况应该返回 error,而不是 panic

19. Web 服务器示例

完整的 Go Web 服务器——QR 码生成器。

package main

import (
    "flag"
    "html/template"
    "log"
    "net/http"
)

var addr = flag.String("addr", ":1718", "HTTP 服务地址")

var templ = template.Must(template.New("qr").Parse(templateStr))

func main() {
    flag.Parse()
    http.Handle("/", http.HandlerFunc(QR))
    log.Printf("启动服务器在 %s", *addr)
    err := http.ListenAndServe(*addr, nil)
    if err != nil {
        log.Fatal("ListenAndServe:", err)
    }
}

func QR(w http.ResponseWriter, req *http.Request) {
    templ.Execute(w, req.FormValue("s"))
}

const templateStr = `
<html>
<head>
<title>QR 码生成器</title>
</head>
<body>
{{if .}}
<img src="http://chart.apis.google.com/chart?chs=300x300&cht=qr&choe=UTF-8&chl={{.}}" />
<br>
{{.}}
<br>
<br>
{{end}}
<form action="/" name=f method="GET">
    <input maxLength=1024 size=70 name=s value="" title="要编码为 QR 码的文本">
    <input type=submit value="生成 QR 码" name=qr>
</form>
</body>
</html>
`

简化版:可运行的 Web 服务器

package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
        name := r.URL.Query().Get("name")
        if name == "" {
            name = "世界"
        }
        fmt.Fprintf(w, "你好, %s!", name)
    })

    http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(`{"status": "ok"}`))
    })

    log.Println("服务器启动在 :8080")
    log.Println("访问 http://localhost:8080/hello?name=Go")

    // 用 20 行代码启动一个完整的 HTTP 服务器
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Fatal(err)
    }
}

/*
运行:
    go run effective_go_study.go

然后在浏览器访问:
    http://localhost:8080/hello?name=Go
    http://localhost:8080/health
*/

附录:学习要点速查

主题 核心要点
格式化 gofmt,不要自己手动排版
命名 包名简洁,getter 不加 Get,单方法接口用 -er,多用 MixedCaps
控制结构 if/for/switch 不加括号,if 体以 return 结尾时省略 else
多返回值 函数返回 (result, error) 是 Go 的惯用风格
defer 资源清理,参数立即求值,LIFO 顺序
new vs make new 分配零值指针;make 初始化 slice/map/channel
切片 引用底层数组,传参时元素修改可见
map 访问不存在的 key 返回零值,用"逗号 ok"区分
空标识符 用于丢弃不需要的值、抑制未使用变量/导入错误
嵌入 替代继承,类型的方法被提升到外层结构体
接口 隐式实现,不声明"implements",组合接口优于继承
并发 不要共享内存,而是通过 channel 传递数据
错误 error 是接口,用类型断言获取详细错误
panic/recover 真正的异常用 panic,库对外暴露 error,不要滥用
goroutine 轻量级线程,用 channel 同步,用 select 做多路复用

本文档基于 Effective Go 整理 文档最后更新:2026-07-22