go 从零单排 之 一小时通关

Go 语言语法详解 - 像聊天一样学编程

白晓虾 🦐

用生活化的语言,把 Go 语言讲得像聊天一样简单


目录

  1. 基础概念
  2. 变量与常量
  3. 数据类型
  4. 运算符
  5. 流程控制
  6. 函数
  7. 数组与切片
  8. 映射(Map)
  9. 结构体
  10. 指针
  11. 接口
  12. 错误处理
  13. 并发
  14. 包管理
  15. 实战案例
  16. 最佳实践
  17. 学习建议

基础概念

什么是 Go?

Go 就像是一个高效的管家

  • 简单:语法简洁,没有那么多花里胡哨的东西
  • 快速:编译速度快,运行速度也快
  • 并发:能同时处理很多事情(就像有多个分身)
  • 安全:编译时就能发现很多错误

第一个程序

go 复制代码
package main  // 声明包名,main 包是程序的入口

import "fmt"  // 导入 fmt 包,用于格式化输出

func main() {  // 主函数,程序从这里开始执行
    fmt.Println("你好,世界!")  // 打印一行文字
}

生活比喻

  • package main 就像告诉别人"我是这个家的主人"
  • import "fmt" 就像"我要用一下打印机工具"
  • func main() 就像"开始干活了"
  • fmt.Println() 就像"打印出来"

变量与常量

变量 - 会变的盒子

变量就像一个可以换东西的盒子,你可以随时往里面放不同的东西。

声明变量的几种方式
go 复制代码
package main

import "fmt"

func main() {
    // 方式1:完整声明(最正式)
    var name string = "白晓虾"
    var age int = 18
    var isCool bool = true
    
    fmt.Println("名字:", name)
    fmt.Println("年龄:", age)
    fmt.Println("酷吗:", isCool)
    
    // 方式2:类型推导(让编译器猜)
    var city = "北京"  // 编译器知道这是字符串
    var height = 175   // 编译器知道这是整数
    
    fmt.Println("城市:", city)
    fmt.Println("身高:", height)
    
    // 方式3:短变量声明(最常用,像聊天一样简洁)
    hobby := "写代码"  // := 表示"声明并赋值"
    money := 100.5
    
    fmt.Println("爱好:", hobby)
    fmt.Println("零花钱:", money)
    
    // 方式4:批量声明(一次声明多个)
    var (
        username = "xiaoxia"
        password = "123456"
        email    = "xiaoxia@example.com"
    )
    
    fmt.Println("用户名:", username)
    fmt.Println("密码:", password)
    fmt.Println("邮箱:", email)
}

生活场景

  • var name string = "白晓虾" 就像"我有一个盒子,名字叫 name,专门装字符串,现在里面装着'白晓虾'"
  • hobby := "写代码" 就像"我随手拿个盒子叫 hobby,里面先放个'写代码'"
变量的零值
go 复制代码
package main

import "fmt"

func main() {
    // 声明但不赋值,会有默认值(零值)
    var name string      // 空字符串 ""
    var age int          // 0
    var score float64    // 0.0
    var isStudent bool   // false
    var hobbies []string // nil(空切片)
    
    fmt.Printf("字符串零值: '%s'\n", name)
    fmt.Printf("整数零值: %d\n", age)
    fmt.Printf("浮点数零值: %f\n", score)
    fmt.Printf("布尔值零值: %t\n", isStudent)
    fmt.Printf("切片零值: %v\n", hobbies)
}

生活比喻

  • 新买的盒子,没放东西之前,里面就是"空的"或"默认状态"
  • 字符串盒子默认是空的("")
  • 数字盒子默认是 0
  • 布尔盒子默认是 false(没选中)
变量赋值与修改
go 复制代码
package main

import "fmt"

func main() {
    // 声明并赋值
    name := "小明"
    fmt.Println("初始名字:", name)  // 小明
    
    // 修改变量的值
    name = "小红"
    fmt.Println("修改后:", name)  // 小红
    
    // 多重赋值(一次改多个)
    a, b := 10, 20
    fmt.Println("交换前:", a, b)  // 10 20
    
    // 交换两个变量的值(不需要临时变量!)
    a, b = b, a
    fmt.Println("交换后:", a, b)  // 20 10
    
    // 交换的例子:交换两个人的位置
    person1, person2 := "左边", "右边"
    fmt.Println("换位前:", person1, person2)  // 左边 右边
    person1, person2 = person2, person1
    fmt.Println("换位后:", person1, person2)  // 右边 左边
}

常量 - 不会变的盒子

常量就像一个贴了"禁止修改"标签的盒子,一旦放进去就不能换了。

go 复制代码
package main

import "fmt"

func main() {
    // 声明常量
    const PI = 3.14159
    const MAX_USERS = 1000
    const APP_NAME = "我的应用"
    
    fmt.Println("圆周率:", PI)
    fmt.Println("最大用户数:", MAX_USERS)
    fmt.Println("应用名称:", APP_NAME)
    
    // 批量声明常量
    const (
        STATUS_ACTIVE   = 1  // 活跃
        STATUS_INACTIVE = 0  // 不活跃
        STATUS_BANNED   = -1 // 被封禁
    )
    
    fmt.Println("活跃状态:", STATUS_ACTIVE)
    fmt.Println("不活跃状态:", STATUS_INACTIVE)
    
    // iota - 自动递增的常量生成器
    const (
        MONDAY = iota    // 0
        TUESDAY          // 1
        WEDNESDAY        // 2
        THURSDAY         // 3
        FRIDAY           // 4
        SATURDAY         // 5
        SUNDAY           // 6
    )
    
    fmt.Println("星期一:", MONDAY)    // 0
    fmt.Println("星期五:", FRIDAY)    // 4
    
    // iota 的高级用法
    const (
        _ = iota  // 0(跳过)
        KB = 1 << (10 * iota)  // 1024
        MB                      // 1048576
        GB                      // 1073741824
    )
    
    fmt.Println("1KB:", KB)  // 1024
    fmt.Println("1MB:", MB)  // 1048576
    fmt.Println("1GB:", GB)  // 1073741824
}

生活场景

  • const PI = 3.14159 就像"这个盒子永远装 3.14159,谁也不能改"
  • iota 就像"自动编号器",从 0 开始,每个自动加 1

数据类型

基本类型

go 复制代码
package main

import "fmt"

func main() {
    // ============ 字符串 ============
    name := "白晓虾"
    greeting := `多行字符串
    可以这样写
    不用转义`
    
    fmt.Println("名字:", name)
    fmt.Println("问候:", greeting)
    
    // 字符串拼接
    fullName := "白" + "晓" + "虾"
    fmt.Println("全名:", fullName)
    
    // ============ 整数 ============
    var age int = 18           // 根据系统自动选择 int32 或 int64
    var smallNumber int8 = 127 // -128 到 127
    var bigNumber int64 = 9999999999
    
    var unsigned uint = 100    // 无符号整数(只能是正数)
    var byteVal uint8 = 255    // uint8 的别名,0-255
    
    fmt.Println("年龄:", age)
    fmt.Println("小数字:", smallNumber)
    fmt.Println("大数字:", bigNumber)
    fmt.Println("无符号数:", unsigned)
    
    // ============ 浮点数 ============
    var price float32 = 19.99
    var pi float64 = 3.141592653589793
    
    fmt.Println("价格:", price)
    fmt.Println("圆周率:", pi)
    
    // ============ 布尔值 ============
    var isStudent bool = true
    var isAdult bool = false
    
    fmt.Println("是学生:", isStudent)
    fmt.Println("是成年人:", isAdult)
    
    // 布尔运算
    fmt.Println("true && false:", true && false)  // false(与)
    fmt.Println("true || false:", true || false)  // true(或)
    fmt.Println("!true:", !true)                  // false(非)
}

生活比喻

  • string 就像"文字盒子",只能装文字
  • int 就像"整数盒子",只能装整数
  • float 就像"小数盒子",可以装小数
  • bool 就像"开关盒子",只有开和关两个状态

类型转换

Go 是强类型语言,不会自动转换类型,必须手动转换。

go 复制代码
package main

import "fmt"

func main() {
    // 整数转浮点数
    var age int = 18
    var ageFloat float64 = float64(age)
    
    fmt.Println("年龄(整):", age)
    fmt.Println("年龄(浮):", ageFloat)
    
    // 浮点数转整数(会丢失小数部分)
    var price float64 = 19.99
    var priceInt int = int(price)
    
    fmt.Println("价格(浮):", price)
    fmt.Println("价格(整):", priceInt)  // 19(小数部分丢失)
    
    // 数字转字符串
    var score int = 95
    var scoreStr string = fmt.Sprintf("%d", score)
    
    fmt.Println("分数(数字):", score)
    fmt.Println("分数(字符串):", scoreStr)
    
    // 字符串转数字
    var strNumber string = "123"
    var number int
    fmt.Sscanf(strNumber, "%d", &number)
    
    fmt.Println("字符串:", strNumber)
    fmt.Println("数字:", number)
    
    // 注意:不能直接把字符串当数字用
    // var x int = "123"  // ❌ 编译错误
    var x int = 123       // ✅ 正确
    fmt.Println("x =", x)
}

生活场景

  • 类型转换就像"把东西从一个盒子拿出来,放到另一个盒子里"
  • 但要注意:有些转换会"丢失信息",比如小数转整数会丢掉小数部分

运算符

算术运算符

go 复制代码
package main

import "fmt"

func main() {
    a := 10
    b := 3
    
    // 基本运算
    fmt.Println("加法:", a + b)   // 13
    fmt.Println("减法:", a - b)   // 7
    fmt.Println("乘法:", a * b)   // 30
    fmt.Println("除法:", a / b)   // 3(整数除法,舍去小数)
    fmt.Println("取余:", a % b)   // 1
    
    // 浮点数除法
    fmt.Println("10.0 / 3.0 =", 10.0 / 3.0)  // 3.333...
    
    // 自增和自减(只能作为语句,不能用于表达式)
    x := 5
    x++  // x = x + 1
    fmt.Println("x++ 后:", x)  // 6
    
    x--  // x = x - 1
    fmt.Println("x-- 后:", x)  // 5
    
    // ❌ 不能这样用
    // y := x++  // 编译错误
    // if x++ > 5 { }  // 编译错误
    
    // 复合赋值运算符
    x += 5  // x = x + 5
    fmt.Println("x += 5:", x)  // 10
    
    x -= 3  // x = x - 3
    fmt.Println("x -= 3:", x)  // 7
    
    x *= 2  // x = x * 2
    fmt.Println("x *= 2:", x)  // 14
    
    x /= 7  // x = x / 7
    fmt.Println("x /= 7:", x)  // 2
}

生活场景

  • + - * / 就像日常的加减乘除
  • % 就像"分蛋糕,看剩几块"
  • ++-- 就像"给自己加一块"或"减一块"

比较运算符

go 复制代码
package main

import "fmt"

func main() {
    a := 10
    b := 20
    
    fmt.Println("a == b:", a == b)  // false(等于)
    fmt.Println("a != b:", a != b)  // true(不等于)
    fmt.Println("a < b:", a < b)    // true(小于)
    fmt.Println("a <= b:", a <= b)  // true(小于等于)
    fmt.Println("a > b:", a > b)    // false(大于)
    fmt.Println("a >= b:", a >= b)  // false(大于等于)
    
    // 字符串比较(按字典序)
    str1 := "apple"
    str2 := "banana"
    fmt.Println("apple < banana:", str1 < str2)  // true
}

逻辑运算符

go 复制代码
package main

import "fmt"

func main() {
    a := true
    b := false
    
    // 与(&&):两个都为 true 才是 true
    fmt.Println("true && false:", a && b)  // false
    fmt.Println("true && true:", a && a)   // true
    
    // 或(||):有一个为 true 就是 true
    fmt.Println("true || false:", a || b)  // true
    fmt.Println("false || false:", b || b) // false
    
    // 非(!):取反
    fmt.Println("!true:", !a)   // false
    fmt.Println("!false:", !b)  // true
    
    // 短路求值(重要!)
    // &&:如果第一个是 false,不会计算第二个
    // ||:如果第一个是 true,不会计算第二个
    
    // 例子:检查年龄是否在 18-60 之间
    age := 25
    isAdult := age >= 18 && age <= 60
    fmt.Println("年龄 25 是否在 18-60 之间:", isAdult)  // true
    
    // 例子:检查是否是周末
    day := 6  // 假设 6 是周六
    isWeekend := day == 6 || day == 7
    fmt.Println("是否是周末:", isWeekend)  // true
}

生活场景

  • && 就像"两个条件都要满足"
  • || 就像"满足其中一个就行"
  • ! 就像"反过来"

流程控制

if-else - 如果...就...

go 复制代码
package main

import "fmt"

func main() {
    // 基本的 if
    age := 18
    if age >= 18 {
        fmt.Println("你已经成年了")
    }
    
    // if-else
    if age >= 18 {
        fmt.Println("可以考驾照")
    } else {
        fmt.Println("还不能考驾照")
    }
    
    // if-else if-else
    score := 85
    if score >= 90 {
        fmt.Println("优秀")
    } else if score >= 80 {
        fmt.Println("良好")
    } else if score >= 60 {
        fmt.Println("及格")
    } else {
        fmt.Println("不及格")
    }
    
    // if 的特殊写法:可以在条件前声明变量
    if num := 10; num%2 == 0 {
        fmt.Printf("%d 是偶数\n", num)
    } else {
        fmt.Printf("%d 是奇数\n", num)
    }
    
    // 例子:判断天气
    weather := "晴天"
    if weather == "晴天" {
        fmt.Println("适合出去玩")
    } else if weather == "雨天" {
        fmt.Println("记得带伞")
    } else {
        fmt.Println("随便吧")
    }
}

生活场景

  • if 就像"如果...,就..."
  • else 就像"否则..."
  • else if 就像"或者如果..."

switch - 多选一

go 复制代码
package main

import "fmt"

func main() {
    // 基本的 switch
    day := 3
    
    switch day {
    case 1:
        fmt.Println("星期一")
    case 2:
        fmt.Println("星期二")
    case 3:
        fmt.Println("星期三")
    case 4:
        fmt.Println("星期四")
    case 5:
        fmt.Println("星期五")
    case 6:
        fmt.Println("星期六")
    case 7:
        fmt.Println("星期日")
    default:
        fmt.Println("未知的日子")
    }
    
    // 一个 case 可以有多个值
    month := 7
    
    switch month {
    case 12, 1, 2:
        fmt.Println("冬天")
    case 3, 4, 5:
        fmt.Println("春天")
    case 6, 7, 8:
        fmt.Println("夏天")
    case 9, 10, 11:
        fmt.Println("秋天")
    default:
        fmt.Println("未知季节")
    }
    
    // 不带条件的 switch(相当于 if-else if-else)
    score := 85
    
    switch {
    case score >= 90:
        fmt.Println("优秀")
    case score >= 80:
        fmt.Println("良好")
    case score >= 60:
        fmt.Println("及格")
    default:
        fmt.Println("不及格")
    }
    
    // fallthrough:继续执行下一个 case(不常用)
    num := 1
    switch num {
    case 1:
        fmt.Println("这是 1")
        fallthrough  // 继续执行 case 2
    case 2:
        fmt.Println("这是 2")
    default:
        fmt.Println("其他")
    }
    // 输出:
    // 这是 1
    // 这是 2
}

生活场景

  • switch 就像"多选一"的菜单
  • case 就是每个选项
  • default 就是"以上都不是"
  • fallthrough 就像"这个选了,顺便把下一个也选了"

for - 循环

Go 只有 for 循环,没有 whiledo-while

go 复制代码
package main

import "fmt"

func main() {
    // ============ 形式1:类似 while ============
    i := 0
    for i < 5 {
        fmt.Println("计数:", i)
        i++
    }
    
    // ============ 形式2:经典的 for 循环 ============
    for j := 0; j < 5; j++ {
        fmt.Println("j =", j)
    }
    
    // ============ 形式3:无限循环 ============
    count := 0
    for {
        fmt.Println("无限循环中...")
        count++
        if count >= 3 {
            break  // 跳出循环
        }
    }
    
    // ============ 遍历数组/切片 ============
    fruits := []string{"苹果", "香蕉", "橙子"}
    
    // 方式1:只要值
    for _, fruit := range fruits {
        fmt.Println("水果:", fruit)
    }
    
    // 方式2:要索引和值
    for i, fruit := range fruits {
        fmt.Printf("第 %d 个水果: %s\n", i, fruit)
    }
    
    // 方式3:只要索引
    for i := range fruits {
        fmt.Println("索引:", i)
    }
    
    // ============ 遍历 map ============
    scores := map[string]int{
        "语文": 90,
        "数学": 95,
        "英语": 88,
    }
    
    for subject, score := range scores {
        fmt.Printf("%s: %d 分\n", subject, score)
    }
    
    // ============ break 和 continue ============
    // break:跳出整个循环
    fmt.Println("\nbreak 示例:")
    for i := 0; i < 10; i++ {
        if i == 5 {
            break  // 遇到 5 就停止
        }
        fmt.Println(i)
    }
    // 输出:0 1 2 3 4
    
    // continue:跳过本次循环,继续下一次
    fmt.Println("\ncontinue 示例:")
    for i := 0; i < 5; i++ {
        if i == 2 {
            continue  // 跳过 2
        }
        fmt.Println(i)
    }
    // 输出:0 1 3 4
    
    // ============ 嵌套循环 + 标签 ============
    fmt.Println("\n嵌套循环示例:")
    
OuterLoop:  // 给外层循环起个名字
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            if i == 1 && j == 1 {
                fmt.Println("跳过外层循环")
                break OuterLoop  // 跳出外层循环
            }
            fmt.Printf("i=%d, j=%d\n", i, j)
        }
    }
}

生活场景

  • for 就像"重复做某事"
  • break 就像"不干了,停下来"
  • continue 就像"这次算了,下次继续"
  • 嵌套循环就像"做套娃,一层套一层"

函数

函数基础

函数就像一个"工具箱",你把东西放进去,它处理后给你结果。

go 复制代码
package main

import "fmt"

// 无参数无返回值的函数
func sayHello() {
    fmt.Println("你好!")
}

// 有参数无返回值的函数
func greet(name string) {
    fmt.Printf("你好,%s!\n", name)
}

// 有参数有返回值的函数
func add(a int, b int) int {
    return a + b
}

// 多个返回值(Go 的特色!)
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("不能除以 0")
    }
    return a / b, nil
}

// 命名返回值(返回时不用写变量名)
func rectangle(width, height float64) (area, perimeter float64) {
    area = width * height
    perimeter = 2 * (width + height)
    return  // 直接返回 area 和 perimeter
}

// 可变参数函数(参数个数不固定)
func sum(numbers ...int) int {
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}

func main() {
    // 调用函数
    sayHello()
    
    greet("白晓虾")
    
    result := add(10, 20)
    fmt.Println("10 + 20 =", result)
    
    // 多返回值
    quotient, err := divide(10, 2)
    if err != nil {
        fmt.Println("错误:", err)
    } else {
        fmt.Println("10 / 2 =", quotient)
    }
    
    quotient, err = divide(10, 0)
    if err != nil {
        fmt.Println("错误:", err)
    }
    
    // 命名返回值
    area, perimeter := rectangle(5, 3)
    fmt.Printf("面积: %.2f, 周长: %.2f\n", area, perimeter)
    
    // 可变参数
    fmt.Println("1+2+3 =", sum(1, 2, 3))
    fmt.Println("1+2+3+4+5 =", sum(1, 2, 3, 4, 5))
    
    // 也可以传切片
    nums := []int{10, 20, 30}
    fmt.Println("10+20+30 =", sum(nums...))
}

生活场景

  • 函数就像"洗衣机":你把衣服放进去(参数),它洗完给你干净衣服(返回值)
  • 多返回值就像"洗衣机洗完衣服,顺便把水也排了"
  • 可变参数就像"不管你放多少衣服,我都能洗"

函数作为值

在 Go 中,函数也是一种类型,可以像变量一样传递。

go 复制代码
package main

import "fmt"

// 函数类型
type operation func(int, int) int

// 接收函数作为参数
func calculate(a, b int, op operation) int {
    return op(a, b)
}

// 返回函数
func getOperation(opType string) operation {
    switch opType {
    case "add":
        return func(a, b int) int {
            return a + b
        }
    case "multiply":
        return func(a, b int) int {
            return a * b
        }
    default:
        return func(a, b int) int {
            return 0
        }
    }
}

func main() {
    // 函数作为变量
    add := func(a, b int) int {
        return a + b
    }
    
    result := add(10, 20)
    fmt.Println("10 + 20 =", result)
    
    // 函数作为参数
    sum := calculate(10, 20, add)
    fmt.Println("计算结果:", sum)
    
    // 匿名函数
    multiply := func(a, b int) int {
        return a * b
    }
    
    product := calculate(5, 6, multiply)
    fmt.Println("5 * 6 =", product)
    
    // 函数作为返回值
    op := getOperation("add")
    fmt.Println("getOperation('add')(10, 20) =", op(10, 20))
    
    op = getOperation("multiply")
    fmt.Println("getOperation('multiply')(5, 6) =", op(5, 6))
}

生活场景

  • 函数作为值就像"工具箱本身也可以被传递"
  • 你可以把"锤子"给别人用,也可以让别人给你"锤子"

defer - 延迟执行

defer 会在函数返回前执行,常用于资源清理。

go 复制代码
package main

import "fmt"

func main() {
    // defer 会延迟执行,直到函数结束
    defer fmt.Println("1. 这是 defer 语句")
    defer fmt.Println("2. 这也是 defer 语句")
    fmt.Println("3. 普通语句")
    
    // 执行顺序:3 -> 2 -> 1(后进先出)
    
    fmt.Println("\n--- defer 的实际应用 ---")
    
    // 例子:打开文件后关闭
    // 假设这是打开文件
    file := "文件已打开"
    defer fmt.Println("关闭文件:", file)  // 确保文件会被关闭
    
    fmt.Println("读取文件内容...")
    fmt.Println("处理数据...")
    // 即使中间出错,defer 也会执行
    
    // 例子:记录函数执行时间
    fmt.Println("\n--- 计时示例 ---")
    defer func() {
        fmt.Println("函数执行完毕")
    }()
    
    fmt.Println("开始执行...")
    fmt.Println("执行中...")
    fmt.Println("快完成了...")
}

生活场景

  • defer 就像"出门前最后检查一遍:关灯、锁门、带钥匙"
  • 不管中间发生了什么,最后都要做这些事

数组与切片

数组 - 固定大小的盒子

数组就像一排固定数量的盒子,每个盒子都有编号。

go 复制代码
package main

import "fmt"

func main() {
    // 声明数组(指定长度)
    var arr1 [5]int  // 5 个整数,初始值都是 0
    fmt.Println("arr1:", arr1)  // [0 0 0 0 0]
    
    // 声明并初始化
    arr2 := [5]int{1, 2, 3, 4, 5}
    fmt.Println("arr2:", arr2)  // [1 2 3 4 5]
    
    // 让编译器数长度
    arr3 := [...]int{10, 20, 30}
    fmt.Println("arr3:", arr3)  // [10 20 30]
    
    // 指定索引初始化
    arr4 := [5]int{1: 100, 3: 300}
    fmt.Println("arr4:", arr4)  // [0 100 0 300 0]
    
    // 访问和修改元素
    arr := [3]string{"苹果", "香蕉", "橙子"}
    fmt.Println("第一个水果:", arr[0])  // 苹果
    fmt.Println("最后一个水果:", arr[len(arr)-1])  // 橙子
    
    arr[1] = "葡萄"  // 修改第二个元素
    fmt.Println("修改后:", arr)  // [苹果 葡萄 橙子]
    
    // 遍历数组
    fmt.Println("\n遍历数组:")
    for i, fruit := range arr {
        fmt.Printf("索引 %d: %s\n", i, fruit)
    }
    
    // 数组长度
    fmt.Println("\n数组长度:", len(arr))
    
    // 数组比较(相同类型和长度才能比较)
    a := [3]int{1, 2, 3}
    b := [3]int{1, 2, 3}
    c := [3]int{1, 2, 4}
    
    fmt.Println("a == b:", a == b)  // true
    fmt.Println("a == c:", a == c)  // false
}

生活场景

  • 数组就像"一排固定数量的储物柜"
  • 每个柜子都有编号(索引),从 0 开始
  • 一旦建好,柜子数量就不能改了

切片 - 动态的盒子

切片就像"可以随时增减的盒子",更灵活。

go 复制代码
package main

import "fmt"

func main() {
    // ============ 创建切片 ============
    // 方式1:直接创建
    fruits := []string{"苹果", "香蕉", "橙子"}
    fmt.Println("水果:", fruits)
    
    // 方式2:用 make 创建(指定长度和容量)
    nums := make([]int, 5)  // 长度 5,容量 5
    fmt.Println("nums:", nums)  // [0 0 0 0 0]
    
    // 方式3:从数组切出来
    arr := [5]int{1, 2, 3, 4, 5}
    slice := arr[1:4]  // 取索引 1 到 3(不包括 4)
    fmt.Println("切片:", slice)  // [2 3 4]
    
    // ============ 切片操作 ============
    // 添加元素
    fruits = append(fruits, "葡萄")
    fmt.Println("添加后:", fruits)  // [苹果 香蕉 橙子 葡萄]
    
    // 添加多个元素
    fruits = append(fruits, "西瓜", "草莓")
    fmt.Println("再添加:", fruits)
    
    // 合并切片
    moreFruits := []string{"桃子", "李子"}
    fruits = append(fruits, moreFruits...)
    fmt.Println("合并后:", fruits)
    
    // ============ 切片的长度和容量 ============
    s := make([]int, 3, 5)  // 长度 3,容量 5
    fmt.Printf("长度: %d, 容量: %d\n", len(s), cap(s))
    
    // ============ 切片的切片 ============
    original := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    
    // 取前 5 个
    first5 := original[:5]
    fmt.Println("前 5 个:", first5)
    
    // 取后 5 个
    last5 := original[5:]
    fmt.Println("后 5 个:", last5)
    
    // 取中间 3 个
    middle := original[3:6]
    fmt.Println("中间 3 个:", middle)
    
    // ============ 复制切片 ============
    a := []int{1, 2, 3}
    b := make([]int, len(a))
    copy(b, a)
    
    fmt.Println("原切片:", a)
    fmt.Println("复制的:", b)
    
    // 修改 b 不会影响 a
    b[0] = 999
    fmt.Println("修改 b 后:")
    fmt.Println("a:", a)  // [1 2 3]
    fmt.Println("b:", b)  // [999 2 3]
    
    // ============ 删除元素 ============
    // 删除索引为 2 的元素
    items := []string{"a", "b", "c", "d", "e"}
    index := 2
    items = append(items[:index], items[index+1:]...)
    fmt.Println("删除索引 2 后:", items)  // [a b d e]
    
    // ============ 切片的底层 ============
    // 切片是对数组的引用
    arr2 := [5]int{10, 20, 30, 40, 50}
    slice1 := arr2[1:4]  // [20 30 40]
    slice2 := arr2[2:5]  // [30 40 50]
    
    // 修改 slice1 会影响 slice2(因为它们共享底层数组)
    slice1[1] = 999
    fmt.Println("slice1:", slice1)  // [20 999 40]
    fmt.Println("slice2:", slice2)  // [999 40 50]
    fmt.Println("arr2:", arr2)      // [10 20 999 40 50]
}

生活场景

  • 切片就像"弹性储物柜",可以随时增加或减少柜子
  • append 就像"再加一个柜子"
  • 切片的切片就像"从储物柜里拿出一部分,但它们还是连着的"

映射(Map)

Map 就像一本字典,你可以通过"键"找到对应的"值"。

go 复制代码
package main

import "fmt"

func main() {
    // ============ 创建 map ============
    // 方式1:直接创建
    person := map[string]string{
        "name": "白晓虾",
        "city": "北京",
        "job":  "程序员",
    }
    fmt.Println("个人信息:", person)
    
    // 方式2:用 make 创建
    scores := make(map[string]int)
    scores["语文"] = 90
    scores["数学"] = 95
    scores["英语"] = 88
    fmt.Println("成绩:", scores)
    
    // ============ 访问和修改 ============
    // 访问
    fmt.Println("姓名:", person["name"])  // 白晓虾
    fmt.Println("城市:", person["city"])  // 北京
    
    // 修改
    person["city"] = "上海"
    fmt.Println("修改后的城市:", person["city"])
    
    // 添加新键值对
    person["age"] = "18"
    fmt.Println("添加年龄后:", person)
    
    // ============ 检查键是否存在 ============
    // 使用 "comma ok" 模式
    value, exists := person["name"]
    if exists {
        fmt.Println("姓名:", value)
    } else {
        fmt.Println("没有找到姓名")
    }
    
    // 检查不存在的键
    value, exists = person["hobby"]
    if exists {
        fmt.Println("爱好:", value)
    } else {
        fmt.Println("没有找到爱好")
    }
    
    // ============ 删除键值对 ============
    delete(person, "age")
    fmt.Println("删除年龄后:", person)
    
    // ============ 遍历 map ============
    fmt.Println("\n遍历成绩:")
    for subject, score := range scores {
        fmt.Printf("%s: %d 分\n", subject, score)
    }
    
    // 只遍历键
    fmt.Println("\n所有科目:")
    for subject := range scores {
        fmt.Println(subject)
    }
    
    // ============ map 的长度 ============
    fmt.Println("\n成绩数量:", len(scores))
    
    // ============ 嵌套 map ============
    students := map[string]map[string]int{
        "小明": {
            "语文": 90,
            "数学": 95,
        },
        "小红": {
            "语文": 88,
            "数学": 92,
        },
    }
    
    fmt.Println("\n学生成绩:")
    for name, subjects := range students {
        fmt.Printf("%s: ", name)
        for subject, score := range subjects {
            fmt.Printf("%s=%d ", subject, score)
        }
        fmt.Println()
    }
    
    // ============ 注意事项 ============
    // map 是引用类型
    m1 := map[string]int{"a": 1, "b": 2}
    m2 := m1
    m2["a"] = 999
    fmt.Println("\nm1:", m1)  // map[a:999 b:2] - m1 也被修改了!
    fmt.Println("m2:", m2)    // map[a:999 b:2]
    
    // map 的零值是 nil
    var nilMap map[string]int
    fmt.Println("\nnilMap 是否为 nil:", nilMap == nil)  // true
    
    // nil map 不能直接使用
    // nilMap["key"] = 1  // ❌ 运行时错误!
    
    // 需要先初始化
    nilMap = make(map[string]int)
    nilMap["key"] = 1  // ✅ 现在可以了
    fmt.Println("nilMap 初始化后:", nilMap)
}

生活场景

  • Map 就像"电话簿",通过名字找到电话号码
  • map[key] 就像"查电话簿"
  • delete(map, key) 就像"撕掉那一页"
  • Map 是引用类型,就像"两个人用同一本电话簿"

结构体

结构体就像一个自定义的盒子,可以装不同类型的东西。

go 复制代码
package main

import "fmt"

// 定义结构体
type Person struct {
    Name string  // 大写开头表示公开
    Age  int
    City string
}

// 结构体方法(值接收者)
func (p Person) Introduce() string {
    return fmt.Sprintf("我是 %s,今年 %d 岁,住在 %s", p.Name, p.Age, p.City)
}

// 结构体方法(指针接收者,可以修改结构体)
func (p *Person) Birthday() {
    p.Age++
    fmt.Printf("%s 过生日了!现在 %d 岁\n", p.Name, p.Age)
}

// 结构体嵌套
type Address struct {
    Province string
    City     string
    Street   string
}

type Employee struct {
    Name    string
    Age     int
    Address Address  // 嵌套结构体
}

// 匿名结构体
func getPoint() struct {
    X int
    Y int
} {
    return struct {
        X int
        Y int
    }{X: 10, Y: 20}
}

func main() {
    // ============ 创建结构体实例 ============
    // 方式1:完整写法
    p1 := Person{
        Name: "白晓虾",
        Age:  18,
        City: "北京",
    }
    fmt.Println("p1:", p1)
    
    // 方式2:按顺序(不推荐,容易出错)
    p2 := Person{"小红", 20, "上海"}
    fmt.Println("p2:", p2)
    
    // 方式3:部分初始化
    p3 := Person{Name: "小明"}
    fmt.Println("p3:", p3)  // {小明 0 }
    
    // 方式4:new 函数(返回指针)
    p4 := new(Person)
    p4.Name = "小李"
    p4.Age = 25
    fmt.Println("p4:", *p4)
    
    // ============ 访问和修改 ============
    fmt.Println("\n访问字段:")
    fmt.Println("姓名:", p1.Name)
    fmt.Println("年龄:", p1.Age)
    
    p1.Age = 19
    fmt.Println("修改后的年龄:", p1.Age)
    
    // ============ 结构体方法 ============
    fmt.Println("\n调用方法:")
    intro := p1.Introduce()
    fmt.Println(intro)
    
    p1.Birthday()  // 年龄变成 20
    
    // ============ 结构体指针 ============
    p5 := &Person{Name: "小王", Age: 30}
    fmt.Println("\np5:", *p5)
    p5.Birthday()  // 指针接收者自动解引用
    
    // ============ 结构体嵌套 ============
    emp := Employee{
        Name: "张三",
        Age:  28,
        Address: Address{
            Province: "北京",
            City:     "北京市",
            Street:   "长安街",
        },
    }
    fmt.Println("\n员工信息:")
    fmt.Printf("姓名: %s\n", emp.Name)
    fmt.Printf("地址: %s %s %s\n", emp.Address.Province, emp.Address.City, emp.Address.Street)
    
    // ============ 匿名结构体 ============
    point := struct {
        X int
        Y int
    }{
        X: 100,
        Y: 200,
    }
    fmt.Println("\n坐标:", point)
    
    // ============ 结构体比较 ============
    a := Person{Name: "小明", Age: 20, City: "北京"}
    b := Person{Name: "小明", Age: 20, City: "北京"}
    c := Person{Name: "小红", Age: 20, City: "北京"}
    
    fmt.Println("\n结构体比较:")
    fmt.Println("a == b:", a == b)  // true
    fmt.Println("a == c:", a == c)  // false
    
    // ============ 结构体切片 ============
    people := []Person{
        {Name: "小明", Age: 20, City: "北京"},
        {Name: "小红", Age: 22, City: "上海"},
        {Name: "小李", Age: 25, City: "广州"},
    }
    
    fmt.Println("\n人员列表:")
    for i, p := range people {
        fmt.Printf("%d. %s (%d岁, %s)\n", i+1, p.Name, p.Age, p.City)
    }
    
    // ============ 结构体工厂函数 ============
    newPerson := func(name string, age int, city string) *Person {
        return &Person{
            Name: name,
            Age:  age,
            City: city,
        }
    }
    
    p6 := newPerson("小赵", 30, "深圳")
    fmt.Println("\n工厂函数创建:", *p6)
}

生活场景

  • 结构体就像"名片",上面有姓名、年龄、地址等信息
  • 结构体方法就像"名片主人能做的事"
  • 结构体嵌套就像"名片夹里还有小名片"

指针

指针就像一个地址标签,告诉你东西在哪里,而不是东西本身。

go 复制代码
package main

import "fmt"

func main() {
    // ============ 指针基础 ============
    a := 10
    
    // & 取地址
    p := &a  // p 是指向 a 的指针
    
    fmt.Println("a 的值:", a)           // 10
    fmt.Println("a 的地址:", &a)        // 0x...
    fmt.Println("p 的值:", p)           // 0x...(和 &a 一样)
    fmt.Println("p 指向的值:", *p)      // 10(* 是解引用)
    
    // 通过指针修改值
    *p = 20
    fmt.Println("\n通过指针修改后:")
    fmt.Println("a 的值:", a)   // 20
    fmt.Println("*p 的值:", *p) // 20
    
    // ============ 指针和函数 ============
    // 值传递(不会修改原值)
    x := 10
    fmt.Println("\n值传递:")
    fmt.Println("修改前 x:", x)
    modifyByValue(x)
    fmt.Println("修改后 x:", x)  // 还是 10
    
    // 指针传递(会修改原值)
    fmt.Println("\n指针传递:")
    fmt.Println("修改前 x:", x)
    modifyByPointer(&x)
    fmt.Println("修改后 x:", x)  // 变成 100
    
    // ============ 指针和结构体 ============
    type Person struct {
        Name string
        Age  int
    }
    
    p1 := Person{Name: "小明", Age: 20}
    fmt.Println("\n结构体指针:")
    fmt.Println("修改前:", p1)
    
    // 指针接收者可以修改结构体
    modifyPerson(&p1)
    fmt.Println("修改后:", p1)
    
    // ============ new 函数 ============
    // new 创建指针并初始化为零值
    p2 := new(int)    // *int,值为 0
    p3 := new(string) // *string,值为 ""
    
    fmt.Println("\nnew 函数:")
    fmt.Println("*p2:", *p2)  // 0
    fmt.Println("*p3:", *p3)  // ""
    
    *p2 = 100
    fmt.Println("赋值后 *p2:", *p2)
    
    // ============ 指针数组 ============
    arr := [3]int{1, 2, 3}
    pArr := &arr
    
    fmt.Println("\n指针数组:")
    fmt.Println("arr:", arr)
    fmt.Println("*pArr:", *pArr)
    fmt.Println("(*pArr)[0]:", (*pArr)[0])
    
    // ============ 指针的指针 ============
    val := 10
    p1_ := &val
    p2_ := &p1_  // 指针的指针
    
    fmt.Println("\n指针的指针:")
    fmt.Println("val:", val)
    fmt.Println("*p1_:", *p1_)   // 10
    fmt.Println("**p2_:", **p2_) // 10
    
    // ============ 指针注意事项 ============
    // Go 没有指针运算(不像 C 语言)
    // p++ 是错误的
    
    // nil 指针
    var nilPointer *int
    fmt.Println("\nnil 指针:", nilPointer)  // <nil>
    
    // 使用前要检查
    if nilPointer == nil {
        fmt.Println("指针是 nil,不能解引用")
    }
}

func modifyByValue(n int) {
    n = 100  // 修改的是副本
}

func modifyByPointer(n *int) {
    *n = 100  // 修改的是原值
}

func modifyPerson(p *struct {
    Name string
    Age  int
}) {
    p.Name = "小红"
    p.Age = 25
}

生活场景

  • 指针就像"门牌号",告诉你东西在哪里
  • & 就像"告诉我地址"
  • * 就像"去那个地址拿东西"
  • 值传递就像"复印一份给别人"
  • 指针传递就像"告诉别人地址,让他自己去取"

接口

接口就像一个约定,规定了一组方法,任何实现了这些方法的类型都满足这个接口。

go 复制代码
package main

import (
    "fmt"
    "math"
)

// ============ 定义接口 ============
// 形状接口
type Shape interface {
    Area() float64
    Perimeter() float64
}

// 描述接口
type Describer interface {
    Describe() string
}

// ============ 实现接口 ============
// 圆形
type Circle struct {
    Radius float64
}

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

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

func (c Circle) Describe() string {
    return fmt.Sprintf("圆形,半径 %.2f", c.Radius)
}

// 矩形
type Rectangle struct {
    Width  float64
    Height float64
}

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

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

func (r Rectangle) Describe() string {
    return fmt.Sprintf("矩形,宽 %.2f,高 %.2f", r.Width, r.Height)
}

// ============ 使用接口 ============
// 接受接口作为参数的函数
func PrintShapeInfo(s Shape) {
    fmt.Printf("面积: %.2f, 周长: %.2f\n", s.Area(), s.Perimeter())
}

func PrintDescription(d Describer) {
    fmt.Println(d.Describe())
}

// ============ 空接口 ============
// interface{} 可以接受任何类型
func PrintAny(v interface{}) {
    fmt.Printf("值: %v, 类型: %T\n", v, v)
}

// ============ 类型断言 ============
func GetType(s Shape) {
    switch v := s.(type) {
    case Circle:
        fmt.Printf("这是一个圆形,半径 %.2f\n", v.Radius)
    case Rectangle:
        fmt.Printf("这是一个矩形,宽 %.2f,高 %.2f\n", v.Width, v.Height)
    default:
        fmt.Println("未知形状")
    }
}

func main() {
    // 创建形状
    circle := Circle{Radius: 5}
    rectangle := Rectangle{Width: 4, Height: 3}
    
    fmt.Println("=== 圆形 ===")
    PrintShapeInfo(circle)
    PrintDescription(circle)
    
    fmt.Println("\n=== 矩形 ===")
    PrintShapeInfo(rectangle)
    PrintDescription(rectangle)
    
    // 接口切片
    fmt.Println("\n=== 形状列表 ===")
    shapes := []Shape{circle, rectangle}
    for i, shape := range shapes {
        fmt.Printf("形状 %d: ", i+1)
        PrintShapeInfo(shape)
    }
    
    // 空接口
    fmt.Println("\n=== 空接口 ===")
    PrintAny(42)
    PrintAny("hello")
    PrintAny(3.14)
    PrintAny([]int{1, 2, 3})
    
    // 类型断言
    fmt.Println("\n=== 类型断言 ===")
    GetType(circle)
    GetType(rectangle)
    
    // 类型断言(带检查)
    var s Shape = circle
    if c, ok := s.(Circle); ok {
        fmt.Printf("确认是圆形,半径: %.2f\n", c.Radius)
    }
    
    // ============ 接口组合 ============
    // 组合多个接口
    type ShapeDescriber interface {
        Shape
        Describer
    }
    
    var sd ShapeDescriber = circle
    fmt.Println("\n=== 接口组合 ===")
    fmt.Printf("面积: %.2f\n", sd.Area())
    fmt.Println(sd.Describe())
    
    // ============ 接口零值 ============
    var nilShape Shape
    fmt.Println("\n=== 接口零值 ===")
    fmt.Println("nilShape == nil:", nilShape == nil)  // true
    
    // 注意:接口包含类型和值两部分
    // 只有类型和值都是 nil,接口才等于 nil
    var p *Circle  // p 是 nil
    var s2 Shape = p
    fmt.Println("p == nil:", p == nil)     // true
    fmt.Println("s2 == nil:", s2 == nil)   // false!s2 有类型信息
}

生活场景

  • 接口就像"工作合同",规定了要做什么事
  • 实现接口就像"签合同",保证会做这些事
  • 空接口就像"万能容器",什么都能装
  • 类型断言就像"检查里面到底是什么"

错误处理

Go 的错误处理很直接:函数返回错误,调用者检查错误。

go 复制代码
package main

import (
    "errors"
    "fmt"
)

// ============ 自定义错误 ============
type MyError struct {
    Code    int
    Message string
}

func (e *MyError) Error() string {
    return fmt.Sprintf("错误 %d: %s", e.Code, e.Message)
}

// ============ 返回错误的函数 ============
func Divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, &MyError{
            Code:    1001,
            Message: "不能除以零",
        }
    }
    return a / b, nil
}

func GetUser(id int) (string, error) {
    if id <= 0 {
        return "", errors.New("用户 ID 必须大于 0")
    }
    if id > 100 {
        return "", fmt.Errorf("用户 %d 不存在", id)
    }
    return fmt.Sprintf("用户_%d", id), nil
}

// ============ panic 和 recover ============
func SafeDivide(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("发生 panic: %v", r)
        }
    }()
    
    if b == 0 {
        panic("除数不能为零")
    }
    return a / b, nil
}

func main() {
    // ============ 基本错误处理 ============
    result, err := Divide(10, 2)
    if err != nil {
        fmt.Println("错误:", err)
    } else {
        fmt.Println("结果:", result)
    }
    
    result, err = Divide(10, 0)
    if err != nil {
        fmt.Println("错误:", err)  // 错误 1001: 不能除以零
    }
    
    // ============ 错误类型断言 ============
    if myErr, ok := err.(*MyError); ok {
        fmt.Printf("自定义错误 - 代码: %d, 消息: %s\n", myErr.Code, myErr.Message)
    }
    
    // ============ 错误包装 ============
    user, err := GetUser(-1)
    if err != nil {
        fmt.Println("\n获取用户错误:", err)
    }
    
    user, err = GetUser(200)
    if err != nil {
        fmt.Println("获取用户错误:", err)
    }
    
    user, err = GetUser(50)
    if err != nil {
        fmt.Println("错误:", err)
    } else {
        fmt.Println("用户:", user)
    }
    
    // ============ panic 和 recover ============
    fmt.Println("\n=== panic 和 recover ===")
    result2, err := SafeDivide(10, 0)
    if err != nil {
        fmt.Println("错误:", err)
    } else {
        fmt.Println("结果:", result2)
    }
    
    result2, err = SafeDivide(10, 2)
    if err != nil {
        fmt.Println("错误:", err)
    } else {
        fmt.Println("结果:", result2)
    }
    
    // ============ 错误处理最佳实践 ============
    fmt.Println("\n=== 错误处理最佳实践 ===")
    
    // 1. 不要忽略错误
    // val, _ := someFunc()  // ❌ 不好
    // val, err := someFunc()  // ✅ 好
    // if err != nil { ... }
    
    // 2. 尽早返回
    // if err != nil {
    //     return err  // 尽早返回,减少嵌套
    // }
    
    // 3. 添加上下文
    // if err != nil {
    //     return fmt.Errorf("处理用户 %d 时出错: %w", userID, err)
    // }
    
    fmt.Println("错误处理示例完成")
}

生活场景

  • 错误就像"快递没送到",需要检查并处理
  • panic 就像"紧急情况",程序崩溃
  • recover 就像"急救",把程序救回来
  • defer 就像"最后检查",确保资源被释放

并发

并发是 Go 的强项,用 goroutinechannel 轻松实现。

Goroutine - 轻量级线程

go 复制代码
package main

import (
    "fmt"
    "time"
)

func sayHello(name string) {
    for i := 0; i < 3; i++ {
        fmt.Printf("%s: 你好 %d\n", name, i)
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    // ============ 启动 goroutine ============
    // 普通函数调用(同步)
    fmt.Println("=== 同步调用 ===")
    sayHello("主线程")
    
    // 启动 goroutine(异步)
    fmt.Println("\n=== 异步调用 ===")
    go sayHello("协程1")
    go sayHello("协程2")
    go sayHello("协程3")
    
    // 主函数要等待,否则 goroutine 还没执行完程序就退出了
    time.Sleep(500 * time.Millisecond)
    
    // ============ 匿名函数 goroutine ============
    fmt.Println("\n=== 匿名函数 goroutine ===")
    go func(msg string) {
        fmt.Println(msg)
    }("这是匿名 goroutine")
    
    time.Sleep(100 * time.Millisecond)
    
    // ============ goroutine 数量 ============
    fmt.Println("\n=== 启动多个 goroutine ===")
    for i := 0; i < 5; i++ {
        go func(id int) {
            fmt.Printf("Goroutine %d\n", id)
        }(i)  // 注意:要把 i 作为参数传入
    }
    
    time.Sleep(200 * time.Millisecond)
    
    // ============ WaitGroup 等待 goroutine ============
    fmt.Println("\n=== WaitGroup ===")
    
    // 使用 sync.WaitGroup 等待所有 goroutine 完成
    // 见下面的示例
}

Channel - 通道

go 复制代码
package main

import (
    "fmt"
    "time"
)

func main() {
    // ============ 创建 channel ============
    // 无缓冲 channel
    ch := make(chan string)
    
    // 发送和接收
    go func() {
        time.Sleep(100 * time.Millisecond)
        ch <- "你好"  // 发送
    }()
    
    msg := <-ch  // 接收(会阻塞直到收到消息)
    fmt.Println("收到:", msg)
    
    // ============ 有缓冲 channel ============
    fmt.Println("\n=== 有缓冲 channel ===")
    bufferedCh := make(chan int, 3)  // 缓冲区大小为 3
    
    bufferedCh <- 1  // 不阻塞
    bufferedCh <- 2  // 不阻塞
    bufferedCh <- 3  // 不阻塞
    // bufferedCh <- 4  // 阻塞!缓冲区满了
    
    fmt.Println("缓冲区大小:", len(bufferedCh), "容量:", cap(bufferedCh))
    
    fmt.Println("接收:", <-bufferedCh)  // 1
    fmt.Println("接收:", <-bufferedCh)  // 2
    fmt.Println("接收:", <-bufferedCh)  // 3
    
    // ============ 关闭 channel ============
    fmt.Println("\n=== 关闭 channel ===")
    jobs := make(chan int, 5)
    done := make(chan bool)
    
    // 消费者
    go func() {
        for {
            job, more := <-jobs
            if more {
                fmt.Println("处理任务:", job)
            } else {
                fmt.Println("所有任务完成")
                done <- true
                return
            }
        }
    }()
    
    // 生产者
    for i := 1; i <= 3; i++ {
        jobs <- i
    }
    close(jobs)  // 关闭 channel
    
    <-done  // 等待完成
    
    // ============ range channel ============
    fmt.Println("\n=== range channel ===")
    nums := make(chan int, 3)
    nums <- 1
    nums <- 2
    nums <- 3
    close(nums)
    
    for n := range nums {
        fmt.Println("收到:", n)
    }
    
    // ============ select 多路复用 ============
    fmt.Println("\n=== select ===")
    ch1 := make(chan string)
    ch2 := make(chan string)
    
    go func() {
        time.Sleep(100 * time.Millisecond)
        ch1 <- "来自 ch1"
    }()
    
    go func() {
        time.Sleep(200 * time.Millisecond)
        ch2 <- "来自 ch2"
    }()
    
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println(msg1)
        case msg2 := <-ch2:
            fmt.Println(msg2)
        case <-time.After(300 * time.Millisecond):
            fmt.Println("超时")
        }
    }
    
    // ============ 单向 channel ============
    fmt.Println("\n=== 单向 channel ===")
    
    // 只发送
    sender := func(ch chan<- int) {
        ch <- 42
    }
    
    // 只接收
    receiver := func(ch <-chan int) {
        fmt.Println("收到:", <-ch)
    }
    
    ch3 := make(chan int, 1)
    sender(ch3)
    receiver(ch3)
}

sync 包 - 同步原语

go 复制代码
package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    // ============ WaitGroup ============
    fmt.Println("=== WaitGroup ===")
    
    var wg sync.WaitGroup
    
    for i := 0; i < 5; i++ {
        wg.Add(1)  // 计数器 +1
        go func(id int) {
            defer wg.Done()  // 计数器 -1
            fmt.Printf("Goroutine %d 开始\n", id)
            time.Sleep(100 * time.Millisecond)
            fmt.Printf("Goroutine %d 完成\n", id)
        }(i)
    }
    
    wg.Wait()  // 等待所有 goroutine 完成
    fmt.Println("所有 goroutine 完成")
    
    // ============ Mutex 互斥锁 ============
    fmt.Println("\n=== Mutex ===")
    
    var mu sync.Mutex
    counter := 0
    
    increment := func() {
        mu.Lock()
        defer mu.Unlock()
        counter++
    }
    
    var wg2 sync.WaitGroup
    for i := 0; i < 1000; i++ {
        wg2.Add(1)
        go func() {
            defer wg2.Done()
            increment()
        }()
    }
    
    wg2.Wait()
    fmt.Println("计数器:", counter)  // 1000
    
    // ============ RWMutex 读写锁 ============
    fmt.Println("\n=== RWMutex ===")
    
    var rwmu sync.RWMutex
    data := make(map[string]int)
    
    // 写操作用 Lock/Unlock
    writeData := func(key string, value int) {
        rwmu.Lock()
        defer rwmu.Unlock()
        data[key] = value
    }
    
    // 读操作用 RLock/RUnlock
    readData := func(key string) int {
        rwmu.RLock()
        defer rwmu.RUnlock()
        return data[key]
    }
    
    writeData("a", 1)
    writeData("b", 2)
    fmt.Println("读取 a:", readData("a"))
    fmt.Println("读取 b:", readData("b"))
    
    // ============ Once ============
    fmt.Println("\n=== Once ===")
    
    var once sync.Once
    
    for i := 0; i < 5; i++ {
        once.Do(func() {
            fmt.Println("只执行一次")
        })
    }
    
    // ============ Pool 对象池 ============
    fmt.Println("\n=== Pool ===")
    
    pool := &sync.Pool{
        New: func() interface{} {
            fmt.Println("创建新对象")
            return 0
        },
    }
    
    // 从池中获取
    obj1 := pool.Get().(int)
    fmt.Println("获取:", obj1)
    
    // 放回池中
    pool.Put(42)
    
    // 再次获取(可能获取到刚才放回的)
    obj2 := pool.Get().(int)
    fmt.Println("再次获取:", obj2)
}

生活场景

  • goroutine 就像"多个人同时干活"
  • channel 就像"管道",用来传递东西
  • WaitGroup 就像"点名册",等所有人到齐再开始
  • Mutex 就像"厕所锁",一次只能一个人用
  • RWMutex 就像"图书馆",多人可以同时读,但写的时候只能一个人

包管理

包的导入和使用

go 复制代码
package main

import (
    "fmt"           // 标准库
    "math"          // 标准库
    "os"            // 标准库
    "strings"       // 标准库
    
    // 第三方包
    // "github.com/gin-gonic/gin"
    
    // 别名
    myfmt "fmt"
    
    // 只执行初始化代码
    _ "database/sql"
)

// 导出规则:首字母大写 = 公开,首字母小写 = 私有

// 公开函数(可以被其他包调用)
func Add(a, b int) int {
    return a + b
}

// 私有函数(只能在当前包内使用)
func subtract(a, b int) int {
    return a - b
}

func main() {
    // 使用标准库
    fmt.Println("Hello")
    fmt.Println("圆周率:", math.Pi)
    fmt.Println("平方根:", math.Sqrt(2))
    
    // 使用别名
    myfmt.Println("使用别名")
    
    // 字符串操作
    fmt.Println(strings.ToUpper("hello"))      // HELLO
    fmt.Println(strings.Split("a,b,c", ","))   // [a b c]
    fmt.Println(strings.Contains("hello", "ell")) // true
    
    // 命令行参数
    fmt.Println("参数数量:", len(os.Args))
    fmt.Println("参数:", os.Args)
}

go mod - 模块管理

bash 复制代码
# 初始化模块
go mod init myproject

# 下载依赖
go mod tidy

# 查看依赖
go list -m all

# 更新依赖
go get -u

# 清理未使用的依赖
go mod tidy

实战案例

案例1:简单的 Web 服务器

go 复制代码
package main

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

func main() {
    // 处理根路径
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "欢迎来到我的网站!")
    })
    
    // 处理 /hello 路径
    http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
        name := r.URL.Query().Get("name")
        if name == "" {
            name = "世界"
        }
        fmt.Fprintf(w, "你好,%s!", name)
    })
    
    // 处理 /api 路径
    http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        fmt.Fprintf(w, `{"message": "这是 API 响应"}`)
    })
    
    // 启动服务器
    fmt.Println("服务器启动在 http://localhost:8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

案例2:并发下载器

go 复制代码
package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "sync"
    "time"
)

func downloadFile(url, filename string, wg *sync.WaitGroup) error {
    defer wg.Done()
    
    start := time.Now()
    
    resp, err := http.Get(url)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    
    file, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer file.Close()
    
    _, err = io.Copy(file, resp.Body)
    if err != nil {
        return err
    }
    
    fmt.Printf("下载完成: %s (耗时: %v)\n", filename, time.Since(start))
    return nil
}

func main() {
    urls := []struct {
        url      string
        filename string
    }{
        {"https://example.com/file1", "file1.txt"},
        {"https://example.com/file2", "file2.txt"},
        {"https://example.com/file3", "file3.txt"},
    }
    
    var wg sync.WaitGroup
    
    for _, item := range urls {
        wg.Add(1)
        go downloadFile(item.url, item.filename, &wg)
    }
    
    wg.Wait()
    fmt.Println("所有下载完成")
}

案例3:简单的任务队列

go 复制代码
package main

import (
    "fmt"
    "time"
)

type Task struct {
    ID   int
    Name string
}

func worker(id int, tasks <-chan Task, results chan<- string) {
    for task := range tasks {
        fmt.Printf("工作者 %d 开始处理任务 %d: %s\n", id, task.ID, task.Name)
        time.Sleep(500 * time.Millisecond)  // 模拟处理时间
        results <- fmt.Sprintf("工作者 %d 完成任务 %d", id, task.ID)
    }
}

func main() {
    tasks := make(chan Task, 10)
    results := make(chan string, 10)
    
    // 启动 3 个工作者
    for i := 1; i <= 3; i++ {
        go worker(i, tasks, results)
    }
    
    // 添加任务
    for i := 1; i <= 5; i++ {
        tasks <- Task{
            ID:   i,
            Name: fmt.Sprintf("任务-%d", i),
        }
    }
    close(tasks)
    
    // 收集结果
    for i := 1; i <= 5; i++ {
        fmt.Println(<-results)
    }
    
    fmt.Println("所有任务完成")
}

最佳实践

1. 命名规范

go 复制代码
// ✅ 好的命名
func calculateTotalPrice(items []Item) float64 { ... }
type UserService struct { ... }
const MAX_RETRY_COUNT = 3

// ❌ 不好的命名
func calc(items []Item) float64 { ... }  // 太短
type UserSvc struct { ... }              // 缩写不清晰
const max = 3                            // 太短

2. 错误处理

go 复制代码
// ✅ 好的错误处理
func ReadConfig(filename string) (*Config, error) {
    data, err := os.ReadFile(filename)
    if err != nil {
        return nil, fmt.Errorf("读取配置文件 %s 失败: %w", filename, err)
    }
    
    var config Config
    if err := json.Unmarshal(data, &config); err != nil {
        return nil, fmt.Errorf("解析配置文件失败: %w", err)
    }
    
    return &config, nil
}

// ❌ 不好的错误处理
func readConfig(filename string) *Config {
    data, _ := os.ReadFile(filename)  // 忽略错误
    var config Config
    json.Unmarshal(data, &config)     // 忽略错误
    return &config
}

3. 并发安全

go 复制代码
// ✅ 线程安全
type SafeCounter struct {
    mu    sync.Mutex
    count int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count++
}

func (c *SafeCounter) GetCount() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

// ❌ 非线程安全
type UnsafeCounter struct {
    count int
}

func (c *UnsafeCounter) Increment() {
    c.count++  // 多个 goroutine 同时访问会出问题
}

4. 资源管理

go 复制代码
// ✅ 使用 defer 确保资源释放
func ReadFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()  // 确保文件会被关闭
    
    // 处理文件...
    return nil
}

// ✅ 使用 context 控制超时
func FetchData(ctx context.Context) ([]byte, error) {
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()
    
    req, err := http.NewRequestWithContext(ctx, "GET", "https://api.example.com/data", nil)
    if err != nil {
        return nil, err
    }
    
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    return io.ReadAll(resp.Body)
}

5. 代码组织

go 复制代码
// ✅ 好的项目结构
/*
myproject/
├── cmd/                # 主程序入口
│   └── myapp/
│       └── main.go
├── internal/           # 私有代码
│   ├── handler/
│   ├── service/
│   └── repository/
├── pkg/                # 公共代码
│   └── utils/
├── api/                # API 定义
├── configs/            # 配置文件
├── scripts/            # 脚本
├── go.mod
└── go.sum
*/

// ✅ 接口定义放在使用方
// 而不是实现方
package service

type UserRepository interface {
    FindByID(id int) (*User, error)
    Save(user *User) error
}

type UserService struct {
    repo UserRepository
}

func NewUserService(repo UserRepository) *UserService {
    return &UserService{repo: repo}
}

推荐资源

  1. 官方资源

  2. 书籍

    • 《Go 语言圣经》
    • 《Go 语言实战》
    • 《Go 语言高级编程》
  3. 在线练习

常见陷阱

  1. 循环变量陷阱
go 复制代码
// ❌ 错误:所有 goroutine 都会打印 3
for i := 0; i < 3; i++ {
    go func() {
        fmt.Println(i)
    }()
}

// ✅ 正确:把 i 作为参数传入
for i := 0; i < 3; i++ {
    go func(n int) {
        fmt.Println(n)
    }(i)
}
  1. 切片引用陷阱
go 复制代码
// ❌ 错误:切片引用同一底层数组
a := []int{1, 2, 3}
b := a[:2]
b[0] = 999  // a[0] 也变成 999

// ✅ 正确:使用 copy
a := []int{1, 2, 3}
b := make([]int, 2)
copy(b, a[:2])
b[0] = 999  // a 不受影响
  1. nil 接口陷阱
go 复制代码
// ❌ 错误:nil 接口不等于 nil
var p *int  // p 是 nil
var i interface{} = p
fmt.Println(i == nil)  // false!因为 i 有类型信息

// ✅ 正确:检查 nil 接口
var i interface{}
fmt.Println(i == nil)  // true
  1. defer 参数陷阱
go 复制代码
// ❌ 错误:defer 使用最终值
i := 0
defer fmt.Println(i)  // 打印 0
i = 10

// ✅ 正确:立即求值
i := 0
defer func(n int) {
    fmt.Println(n)  // 打印 0
}(i)
i = 10

// 或者
i := 0
defer func() {
    fmt.Println(i)  // 打印 10
}()
i = 10

总结

Go 语言的核心特点:

  1. 简单 - 语法简洁,只有 25 个关键字
  2. 高效 - 编译快,运行快
  3. 并发 - goroutine 和 channel 让并发变得简单
  4. 安全 - 强类型,编译时检查
  5. 实用 - 标准库丰富,工具链完善

记住这些核心概念:

  • 变量 - 存储数据的盒子
  • 函数 - 可重用的代码块
  • 结构体 - 自定义数据类型
  • 接口 - 行为的抽象
  • goroutine - 轻量级线程
  • channel - goroutine 之间的通信管道

小虾:青蛙掀门帘---露一小手!🦐 祝大家轻松、愉快、高效的学习!🦐

相关推荐
花花无缺3 小时前
搞懂@Autowired 与@Resuorce
java·spring boot·后端
CodeMonkey3 小时前
记一次傻逼一样的 OOM 异常
后端
初次攀爬者3 小时前
RocketMQ 基础学习
后端·消息队列·rocketmq
重庆穿山甲3 小时前
Java开发者的大模型入门:LangChain4j组件全攻略(二)
后端
重庆穿山甲3 小时前
Java开发者的大模型入门:LangChain4j组件全攻略(一)
后端
颜酱3 小时前
单调队列:滑动窗口极值问题的最优解(通用模板版)
javascript·后端·算法
Java水解4 小时前
Rust嵌入式开发实战——从ARM裸机编程到RTOS应用
后端·rust
AI探索者4 小时前
LangGraph 条件路由:构建支持工具调用的智能 Agent
后端
苍何4 小时前
终于,我把 Openclaw 加 Seed2.0 Skills 做 AI 漫剧搞定了
后端