包与模块

Go 的包组织、可见性规则与模块系统深入 一、包(Package)基础 1.1 什么是包 // 每个 Go 文件都必须声明自己属于哪个包 package main // main 包是可执行程序的入口 // 同一目录下的所有 .go 文件必须属于同一个包 // 例外:_test.go 文件可以声明 _test 包(用于黑盒测试) 1.2 包的命名规则 // 包名:小写单词,不要下划线或驼峰 package mypackage // ✅ package my_package // ❌ package MyPackage // ❌ // 目录名 = 包名 // src/ // └── utils/ // 目录名 utils // ├── string.go // package utils // └── math.go // package utils 1.3 main 包的特殊性 // main 包 + main() 函数 = 可执行程序 package main import "fmt" func main() { fmt.Println("Hello!") } 二、导入(Import) 2.1 基本导入 import "fmt" // 标准库 import "github.com/gin-gonic/gin" // 第三方包 // 批量导入 import ( "fmt" "os" "strings" ) 2.2 导入别名 // 解决包名冲突 import ( "fmt" myfmt "github.com/other/fmt" // 别名 ) 2.3 点导入与下划线导入 // 点导入:将包的导出标识符引入当前命名空间 import . "fmt" Println("Hello") // 不需要 fmt.Println // ⚠️ 不推荐使用——降低代码可读性 // 下划线导入:只执行包的 init() 函数 import _ "github.com/go-sql-driver/mysql" // 注册 MySQL 驱动 // 不使用包中的任何标识符,只是让 init() 被调用 三、可见性规则(大小写) 3.1 导出规则 Go 没有 public/private 关键字,通过首字母大小写控制可见性: ...

2026 年 10 月 13 日

变量与类型

Go 的变量声明、基本类型与类型系统基础 一、变量声明 Go 是静态类型语言——变量的类型在编译时确定。 1.1 四种声明方式 // 1. var 显式声明(指定类型) var name string = "Go" // 2. var 显式声明(类型推断) var name = "Go" // 3. := 短变量声明(最常用,只能在函数内使用) name := "Go" // 4. var 批量声明 var ( age int = 25 height float64 = 175.5 isGo bool = true ) 1.2 := 的限制 // ✅ 函数内部 — 可以用 := func main() { x := 10 fmt.Println(x) } // ❌ 包级别 — 不能用 :=,必须用 var var globalVar = "ok" // globalVar := "fail" // 编译错误 1.3 零值 Go 中未赋值的变量自动初始化为零值(不是 undefined 或 null): ...

2026 年 10 月 13 日

并发编程基础

Goroutine、Channel、Select——Go 并发的核心原语 一、Goroutine 1.1 什么是 Goroutine Goroutine 是 Go 运行时管理的轻量级线程。创建成本极低(约 2KB 栈空间),可以轻松创建成千上万个。 // 启动一个 goroutine:在函数调用前加 go 关键字 go doSomething() go func() { fmt.Println("running in goroutine") }() 1.2 基本使用 package main import ( "fmt" "time" ) func printNumbers() { for i := 1; i <= 5; i++ { time.Sleep(100 * time.Millisecond) fmt.Printf("number: %d\n", i) } } func printLetters() { for i := 'a'; i <= 'e'; i++ { time.Sleep(150 * time.Millisecond) fmt.Printf("letter: %c\n", i) } } func main() { go printNumbers() // 并发执行 go printLetters() // 并发执行 time.Sleep(2 * time.Second) // 等待 goroutine 完成 fmt.Println("done") } // 输出交错出现:number: 1, letter: a, number: 2, letter: b... 1.3 主 Goroutine 与 WaitGroup 上面用 time.Sleep 等待是不可靠的。正确做法是用 sync.WaitGroup: ...

2026 年 10 月 13 日

测试与工程实践

Go 的内置测试框架、代码规范与工程化实践 一、单元测试 1.1 测试文件命名 // 测试文件以 _test.go 结尾 math.go → math_test.go user.go → user_test.go utils/string.go → utils/string_test.go 1.2 基本测试函数 // math.go package math func Add(a, b int) int { return a + b } // math_test.go package math import "testing" func TestAdd(t *testing.T) { result := Add(1, 2) if result != 3 { t.Errorf("Add(1, 2) = %d; want 3", result) } } // 运行测试 // go test ./... // go test -v ./...(详细输出) 1.3 Table-Driven Tests(表驱动测试) Go 社区最推崇的测试模式: func TestAdd(t *testing.T) { tests := []struct { name string a, b int expected int }{ {"positive numbers", 1, 2, 3}, {"negative numbers", -1, -2, -3}, {"zero", 0, 0, 0}, {"mixed", -1, 1, 0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := Add(tt.a, tt.b) if result != tt.expected { t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected) } }) } } // 运行特定子测试 // go test -run TestAdd/positive_numbers 1.4 测试辅助函数 // 断言辅助函数(Go 标准库没有内置 assert,但可以自己写) func assertEqual(t *testing.T, got, want interface{}) { t.Helper() // 标记为辅助函数,错误信息显示调用者行号 if got != want { t.Errorf("got %v, want %v", got, want) } } func TestAdd(t *testing.T) { assertEqual(t, Add(1, 2), 3) } t.Helper():标记辅助函数,错误时显示调用方的行号而非辅助函数内部行号。 ...

2026 年 10 月 13 日

错误处理

Go 的错误处理哲学:error 是值,不是异常 一、Go 的错误处理哲学 1.1 与异常机制的区别 特性 Go(error) Java/Python(异常) 表现形式 普通返回值 try-catch 机制 调用开销 零(就是多返回一个值) 较高(栈展开) 强制处理 编译器检查(必须处理) 可以不 catch 可预测性 高——每个可能出错的地方都明确 低——调用链可能随时抛异常 1.2 核心原则 // Go 的错误处理模式:每个可能出错的调用都检查返回值 result, err := doSomething() if err != nil { // 处理错误:记录日志、返回错误、终止程序等 return fmt.Errorf("doSomething failed: %w", err) } // 继续正常逻辑 Go 的哲学:显式胜于隐式。 没有隐藏的控制流,错误路径一目了然。 二、error 接口 2.1 error 的定义 // 标准库中 error 的定义(非常简洁) type error interface { Error() string } 任何实现了 Error() string 方法的类型都是一个 error: // 自定义错误类型 type ValidationError struct { Field string Message string } func (e *ValidationError) Error() string { return fmt.Sprintf("validation failed on field '%s': %s", e.Field, e.Message) } // 使用 err := &ValidationError{Field: "email", Message: "invalid format"} fmt.Println(err) // validation failed on field 'email': invalid format 2.2 创建错误的几种方式 import ( "errors" "fmt" ) // 1. errors.New:简单的静态错误消息 var ErrNotFound = errors.New("not found") // 使用时 if err == ErrNotFound { // 处理特定错误 } // 2. fmt.Errorf:格式化错误消息(最常用) err := fmt.Errorf("user %d not found", userID) // 3. %w 动词:包装(wrap)另一个错误 func getUser(id int) (*User, error) { err := db.Query("...") if err != nil { return nil, fmt.Errorf("getUser(%d): %w", id, err) // %w 保留了原始错误,可以用 errors.Is/As 检查 } return &user, nil } // 4. 自定义错误类型(携带结构化信息) // 见上方 ValidationError 示例 三、错误处理模式 3.1 基本模式 // 模式1:检查并返回 func processFile(path string) error { data, err := os.ReadFile(path) if err != nil { return fmt.Errorf("reading %s: %w", path, err) } // 处理 data... return nil // 成功时返回 nil } // 模式2:检查并记录日志 + 继续 data, err := fetchConfig() if err != nil { log.Printf("config fetch failed, using defaults: %v", err) data = defaultConfig() } // 模式3:检查并终止 file, err := os.Open("critical.txt") if err != nil { log.Fatal("无法打开关键文件:", err) } defer file.Close() 3.2 错误链(Error Wrapping) Go 1.13+ 支持错误包装,可以构建错误链: ...

2026 年 10 月 13 日

复合类型

数组、切片、Map、结构体——Go 的复合数据结构 一、数组(Array) Go 的数组是固定长度、同类型的值类型。 1.1 声明与初始化 // 声明:长度是类型的一部分 var arr [5]int // [0 0 0 0 0] arr := [5]int{1, 2, 3, 4, 5} arr := [...]int{1, 2, 3} // 编译器自动推断长度 = 3 // 指定索引初始化 arr := [5]int{1: 10, 3: 30} // [0 10 0 30 0] 1.2 数组特点 // 长度是类型的一部分 var a [3]int = [3]int{1, 2, 3} var b [3]int = [3]int{1, 2, 3} // a == b ✅ 同类型、同长度、同元素 → 相等 var c [4]int = [4]int{1, 2, 3, 4} // a == c ❌ 编译错误:类型不同([3]int != [4]int) // 值类型:赋值和传参会复制整个数组 arr1 := [3]int{1, 2, 3} arr2 := arr1 // arr2 是 arr1 的完整副本 arr2[0] = 100 // 不影响 arr1 fmt.Println(arr1) // [1 2 3] // ⚠️ 实际开发中很少直接用数组,切片(slice)更常用 1.3 多维数组 matrix := [2][3]int{ {1, 2, 3}, {4, 5, 6}, } fmt.Println(matrix[1][2]) // 6 二、切片(Slice)— 最核心的数据结构 切片是 Go 中最常用的复合类型。它是一个对底层数组的引用。 ...

2026 年 10 月 13 日

函数与方法

Go 的函数设计、方法接收者、闭包与高阶函数 一、函数基础 1.1 函数定义 // 基本语法 func 函数名(参数列表) 返回类型 { // 函数体 } // 多参数 func add(a int, b int) int { return a + b } // 相同类型可以合并参数名 func add(a, b int) int { return a + b } // 多返回值(Go 的核心特性之一) func divide(a, b float64) (float64, error) { if b == 0 { return 0, fmt.Errorf("division by zero") } return a / b, nil } // 使用多返回值 result, err := divide(10, 3) if err != nil { log.Fatal(err) } fmt.Println(result) // 3.3333333333333335 1.2 命名返回值 // 返回值可以有名字,相当于在函数内部声明了局部变量 func split(sum int) (x, y int) { x = sum * 4 / 9 y = sum - x return // 裸 return,自动返回 x, y } // 命名返回值 + 裸 return 在短函数中可读性好 // 但长函数中容易出错,建议谨慎使用 1.3 可变参数函数(Variadic) // 使用 ... 实现可变参数 func sum(nums ...int) int { total := 0 for _, n := range nums { total += n } return total } fmt.Println(sum(1, 2, 3)) // 6 fmt.Println(sum(1, 2, 3, 4, 5)) // 15 // 传递切片给可变参数:使用 ... nums := []int{1, 2, 3} fmt.Println(sum(nums...)) // 6 二、函数作为值 2.1 函数是一等公民 // 函数可以赋值给变量 add := func(a, b int) int { return a + b } fmt.Println(add(1, 2)) // 3 // 函数可以作为参数传递 func apply(a, b int, op func(int, int) int) int { return op(a, b) } result := apply(10, 5, func(a, b int) int { return a - b }) fmt.Println(result) // 5 // 函数可以作为返回值 func makeMultiplier(factor int) func(int) int { return func(n int) int { return n * factor } } double := makeMultiplier(2) fmt.Println(double(5)) // 10 三、闭包(Closure) 闭包是引用了外部变量的匿名函数。Go 中每次创建闭包都会捕获变量的引用。 ...

2026 年 10 月 13 日

接口与多态

Go 的隐式接口、空接口与面向接口编程 一、接口基础 1.1 接口定义 // 接口是一组方法签名的集合 type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } // 组合多个接口 type ReadWriter interface { Reader Writer } 1.2 隐式实现(鸭子类型) Go 的接口是隐式实现的——不需要 implements 关键字: // 只要一个类型实现了接口的所有方法,它就自动满足该接口 type MyReader struct { data []byte pos int } func (r *MyReader) Read(p []byte) (n int, err error) { // 实现 Read 方法 if r.pos >= len(r.data) { return 0, io.EOF } n = copy(p, r.data[r.pos:]) r.pos += n return n, nil } // MyReader 自动实现了 io.Reader 接口,无需声明 var r io.Reader = &MyReader{} // ✅ 编译通过 Go 的哲学:行为决定类型,而不是声明决定类型。 如果它像鸭子一样走路、像鸭子一样叫,那它就是鸭子。 ...

2026 年 10 月 13 日

流程控制

if、for、switch、defer——Go 的控制流语句 一、if 条件语句 1.1 基本语法 // Go 的 if 条件不需要括号,但花括号必须存在 if score >= 60 { fmt.Println("pass") } else if score >= 80 { fmt.Println("good") } else { fmt.Println("fail") } 1.2 if 带初始化语句 Go 的 if 可以包含一条简短的初始化语句,变量作用域仅限于 if/else 块内: // 经典用法:同时获取值和错误 if err := doSomething(); err != nil { fmt.Println("error:", err) return } // 这里 err 不可用 // 读取文件的经典模式 if data, err := os.ReadFile("config.txt"); err != nil { fmt.Println("读取失败:", err) } else { fmt.Println("内容:", string(data)) } Go 的惯例:if err != nil 是 Go 代码中出现频率最高的模式之一。 二、for 循环 Go 只有 for 一种循环语句,但功能覆盖了 while、do-while 的场景。 2.1 三种形式 // 形式1:传统 for 循环 for i := 0; i < 10; i++ { fmt.Println(i) } // 形式2:while 风格(只有条件) for condition { // ... } // 形式3:无限循环 for { // break 退出 if done { break } } 2.2 遍历集合 // 遍历切片/数组 nums := []int{10, 20, 30} for i, v := range nums { fmt.Printf("索引=%d 值=%d\n", i, v) } // 只要值 for _, v := range nums { fmt.Println(v) } // 只要索引 for i := range nums { fmt.Println(i) } // 遍历 map m := map[string]int{"Go": 1, "Java": 2} for k, v := range m { fmt.Printf("%s → %d\n", k, v) } // 遍历字符串(按 rune 遍历) for i, ch := range "Hello, 世界" { fmt.Printf("字节位置=%d 字符=%c Unicode=%U\n", i, ch, ch) } // 输出中 '世' 的索引是 7(不是 10),因为 range 按 rune 遍历 2.3 for-range 陷阱 // 陷阱:range 的值是副本,修改不影响原切片 nums := []int{1, 2, 3} for _, v := range nums { v = v * 2 // ❌ 只修改了副本,nums 不变 } // 正确做法:通过索引修改 for i := range nums { nums[i] = nums[i] * 2 // ✅ 修改原切片 } 三、switch 多分支 Go 的 switch 比其他语言更强大,默认不穿透(fall through)。 ...

2026 年 10 月 13 日