GO 基础语法完全教程
📖 关于本教程本教程涵盖 Go 语言从入门到进阶的核心语法知识,配合大量代码示例与注释讲解,适合有一定编程基础的开发者系统学习 Go 语言。
1. Go 代码命名习惯
Go 语言对命名有严格的约定,遵循这些约定是写出地道 Go 代码的第一步。
1.1 基本命名规则
// ✅ Go 命名核心原则:
// 1. 首字母大写 = 导出(public),首字母小写 = 未导出(private)
// 2. 使用 驼峰命名法(CamelCase),不使用下划线分隔
// 包名:全小写,简短,不使用下划线
package httputil // ✅ 好
package http_util // ❌ 不推荐
// 变量名:驼峰命名
var userName string // ✅ 未导出变量
var UserName string // ✅ 导出变量
var user_name string // ❌ 不推荐(不使用蛇形命名)
// 常量:驼峰命名(Go 不推荐全大写)
const maxRetryCount = 3 // ✅ 未导出常量
const MaxRetryCount = 3 // ✅ 导出常量
const MAX_RETRY_COUNT = 3 // ❌ 不推荐
// 函数名:驼峰命名
func getUserByID(id int) {} // ✅ 未导出函数
func GetUserByID(id int) {} // ✅ 导出函数
// 接口命名:单方法接口通常以 -er 结尾
type Reader interface {
Read(p []byte) (n int, err error)
}
type Stringer interface {
String() string
}
// 结构体命名:名词,驼峰
type HttpClient struct {} // ❌ 应保留缩写词大写
type HTTPClient struct {} // ✅ 缩写词全大写:HTTP, URL, ID, JSON, API1.2 常见缩写词的大小写
// Go 中缩写词要保持统一大小写:
// ID 不是 Id,URL 不是 Url,HTTP 不是 Http
type UserID int // ✅
type UserId int // ❌
func ServeHTTP() {} // ✅
func ServeHttp() {} // ❌
var apiURL string // ✅
var apiUrl string // ❌
type JSONParser struct{} // ✅
type JsonParser struct{} // ❌1.3 文件命名
文件名规则:
- 全小写
- 使用下划线分隔单词
- 测试文件以 _test.go 结尾
示例:
user_service.go ✅ 源码文件
user_service_test.go ✅ 测试文件
userservice.go ✅ 也可以(简短时不加下划线)
UserService.go ❌ 不要大写2. 基础数据类型及其格式化输出
2.1 基础类型一览
package main
import "fmt"
func main() {
// ==================== 整数类型 ====================
var i8 int8 = 127 // -128 ~ 127
var i16 int16 = 32767 // -32768 ~ 32767
var i32 int32 = 2147483647 // 约 ±21 亿
var i64 int64 = 9223372036854775807
var i int = 42 // 平台相关:32位系统=int32,64位系统=int64
// 无符号整数
var u8 uint8 = 255 // 0 ~ 255(等同于 byte)
var u16 uint16 = 65535
var u32 uint32 = 4294967295
var u64 uint64 = 18446744073709551615
var u uint = 42 // 平台相关
// ==================== 浮点类型 ====================
var f32 float32 = 3.14 // 单精度,约 7 位有效数字
var f64 float64 = 3.141592653589793 // 双精度,约 15 位有效数字
// ==================== 布尔类型 ====================
var b bool = true // 只有 true / false,不能用 0/1 代替
// ==================== 字符串类型 ====================
var s string = "Hello, Go!"
// ==================== 字节和字符 ====================
var by byte = 'A' // byte 是 uint8 的别名,存 ASCII
var r rune = '中' // rune 是 int32 的别名,存 Unicode 码点
// ==================== 复数类型(了解即可)====================
var c64 complex64 = 1 + 2i
var c128 complex128 = 1 + 2i
fmt.Println(i8, i16, i32, i64, i, u8, u16, u32, u64, u)
fmt.Println(f32, f64, b, s, by, r, c64, c128)
}2.2 格式化输出详解
package main
import "fmt"
func main() {
// ==================== 通用占位符 ====================
x := 42
fmt.Printf("%v\n", x) // 值的默认格式: 42
fmt.Printf("%+v\n", x) // 对结构体会打印字段名
fmt.Printf("%#v\n", x) // Go 语法表示: 42
fmt.Printf("%T\n", x) // 类型: int
fmt.Printf("%%\n") // 输出百分号: %
// ==================== 整数占位符 ====================
n := 255
fmt.Printf("%d\n", n) // 十进制: 255
fmt.Printf("%b\n", n) // 二进制: 11111111
fmt.Printf("%o\n", n) // 八进制: 377
fmt.Printf("%O\n", n) // 带 0o 前缀的八进制: 0o377
fmt.Printf("%x\n", n) // 十六进制(小写): ff
fmt.Printf("%X\n", n) // 十六进制(大写): FF
fmt.Printf("%#x\n", n) // 带 0x 前缀: 0xff
fmt.Printf("%c\n", 65) // 对应的字符: A
fmt.Printf("%U\n", '中') // Unicode 格式: U+4E2D
// ==================== 浮点数占位符 ====================
f := 3.141592653589793
fmt.Printf("%f\n", f) // 默认 6 位小数: 3.141593
fmt.Printf("%.2f\n", f) // 保留 2 位小数: 3.14
fmt.Printf("%e\n", f) // 科学计数法: 3.141593e+00
fmt.Printf("%g\n", f) // 自动选择 %e 或 %f
// ==================== 字符串占位符 ====================
s := "Hello"
fmt.Printf("%s\n", s) // 字符串: Hello
fmt.Printf("%q\n", s) // 带引号: "Hello"
fmt.Printf("%x\n", s) // 十六进制编码: 48656c6c6f
// ==================== 宽度和对齐 ====================
fmt.Printf("|%10d|\n", 42) // 右对齐,宽度 10: | 42|
fmt.Printf("|%-10d|\n", 42) // 左对齐,宽度 10: |42 |
fmt.Printf("|%010d|\n", 42) // 前补零,宽度 10: |0000000042|
// ==================== 指针 ====================
p := &x
fmt.Printf("%p\n", p) // 指针地址: 0xc0000b2008
}3. 进制练习:Excel 一共多少列
💡 题目 Excel 的列名从 A 到 Z,然后是 AA、AB...AZ、BA...ZZ,再然后是 AAA...XFD。Excel 最多支持 16384 列(从 A 到 XFD)。请用 Go 验证这一点,并实现列号与列名的互相转换。
package main
import (
"fmt"
"strings"
)
// 列号转列名(1-based)
// 本质是 26 进制转换,但没有 "0",所以每次要减 1
func columnNumberToName(n int) string {
var result []byte
for n > 0 {
n-- // 关键:26进制没有0,所以先减1
remainder := n % 26
result = append([]byte{byte('A' + remainder)}, result...)
n /= 26
}
return string(result)
}
// 列名转列号
// 相当于 26 进制转 10 进制,A=1, B=2, ..., Z=26
func columnNameToNumber(name string) int {
name = strings.ToUpper(name)
result := 0
for _, ch := range name {
result = result*26 + int(ch-'A'+1)
}
return result
}
func main() {
// Excel 最大列号:16384
maxCol := 16384
maxName := columnNumberToName(maxCol)
fmt.Printf("Excel 最大列号: %d, 对应列名: %s\n", maxCol, maxName)
// 输出: Excel 最大列号: 16384, 对应列名: XFD
// 验证反向转换
fmt.Printf("列名 XFD 对应列号: %d\n", columnNameToNumber("XFD"))
// 输出: 列名 XFD 对应列号: 16384
// 展示各进制表示
fmt.Printf("16384 的二进制: %b\n", maxCol) // 100000000000000
fmt.Printf("16384 的八进制: %o\n", maxCol) // 40000
fmt.Printf("16384 的十六进制: %x\n", maxCol) // 4000
fmt.Printf("16384 = 2^%d\n", 14) // 2^14
// 打印一些示例
examples := []int{1, 26, 27, 52, 702, 703, 16384}
for _, num := range examples {
name := columnNumberToName(num)
fmt.Printf("列号 %5d -> 列名 %4s -> 反转 %5d\n",
num, name, columnNameToNumber(name))
}
// 列号 1 -> 列名 A -> 反转 1
// 列号 26 -> 列名 Z -> 反转 26
// 列号 27 -> 列名 AA -> 反转 27
// 列号 52 -> 列名 AZ -> 反转 52
// 列号 702 -> 列名 ZZ -> 反转 702
// 列号 703 -> 列名 AAA -> 反转 703
// 列号 16384 -> 列名 XFD -> 反转 16384
}4. Go 代码注释书写规范
// ==================== Go 注释规范 ====================
// Go 使用 godoc 工具自动生成文档,注释即文档。
// Package mathutil 提供常用的数学工具函数。
// 这是包注释,放在 package 声明之前。
// 对于多文件包,只需在一个文件中写包注释(通常是 doc.go)。
package mathutil
// MaxInt 返回两个整数中的较大值。
//
// 使用示例:
//
// result := MaxInt(3, 5)
// fmt.Println(result) // 输出: 5
//
// 注意:注释中的代码示例需要缩进一个 Tab(godoc 会识别为代码块)。
func MaxInt(a, b int) int {
if a > b {
return a
}
return b
}
// UserStatus 表示用户状态的枚举类型。
type UserStatus int
const (
// StatusActive 表示用户处于活跃状态。
StatusActive UserStatus = iota
// StatusInactive 表示用户处于非活跃状态。
StatusInactive
// StatusBanned 表示用户已被封禁。
StatusBanned
)
// User 代表系统中的一个用户。
//
// Name 字段不能为空,Age 字段必须大于 0。
type User struct {
Name string // 用户姓名
Age int // 用户年龄
Status UserStatus // 当前状态
}
// 注释规范总结:
// 1. 导出的名称(大写开头)必须有注释
// 2. 注释第一个词应该是被注释的名称本身
// 3. 注释用完整的句子,以句号结尾
// 4. 包注释在 package 语句前,说明包的功能
// 5. TODO/FIXME/BUG 标记用法:
// // TODO(username): 待完成的功能说明
// // FIXME(username): 需要修复的问题
// // BUG(username): 已知的缺陷
// Deprecated: OldFunction 已废弃,请使用 NewFunction 代替。
func OldFunction() {}5. 常量与枚举
5.1 常量定义
package main
import "fmt"
// 单个常量
const Pi = 3.14159265358979323846
// 批量定义常量
const (
StatusOK = 200
StatusNotFound = 404
StatusError = 500
)
// 常量可以是编译期确定的表达式
const (
KB = 1024
MB = KB * 1024 // 1048576
GB = MB * 1024 // 1073741824
TB = GB * 1024
)
func main() {
fmt.Println("1 GB =", GB, "bytes")
// 常量没有类型时,它是"无类型常量",精度非常高
const big = 1 << 100 // 这是合法的!
// fmt.Println(big) // ❌ 但赋值给变量时溢出
fmt.Println(big >> 90) // ✅ 可以参与常量运算:1024
}5.2 iota 枚举
package main
import "fmt"
// iota 是常量计数器,从 0 开始,在 const 块中每新增一行加 1
// 基本枚举
type Weekday int
const (
Sunday Weekday = iota // 0
Monday // 1(自动 iota)
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)
// 跳值枚举
type LogLevel int
const (
DEBUG LogLevel = iota // 0
INFO // 1
_ // 2(用空白标识符跳过)
WARNING // 3
ERROR // 4
)
// 位掩码枚举(常用于权限系统)
type Permission uint8
const (
Read Permission = 1 << iota // 1 (001)
Write // 2 (010)
Execute // 4 (100)
)
// iota 表达式
type ByteSize float64
const (
_ = iota // 跳过 0
KiloByte ByteSize = 1 << (10 * iota) // 1 << 10 = 1024
MegaByte // 1 << 20
GigaByte // 1 << 30
TeraByte // 1 << 40
)
func main() {
fmt.Println("周三:", Wednesday) // 3
// 位掩码组合权限
var perm Permission = Read | Write // 3 (011)
fmt.Printf("权限: %03b\n", perm) // 011
// 判断是否有某权限
hasRead := perm&Read != 0
hasExec := perm&Execute != 0
fmt.Printf("可读: %v, 可执行: %v\n", hasRead, hasExec)
// 可读: true, 可执行: false
fmt.Printf("1 KB = %.0f bytes\n", float64(KiloByte)) // 1024
fmt.Printf("1 MB = %.0f bytes\n", float64(MegaByte)) // 1048576
}6. 结构体及其指针
6.1 结构体定义与使用
package main
import "fmt"
// 定义结构体
type Person struct {
Name string
Age int
City string
}
func main() {
// ==================== 创建结构体的多种方式 ====================
// 方式1:字段名赋值(推荐,顺序无关)
p1 := Person{
Name: "Alice",
Age: 30,
City: "Beijing",
}
// 方式2:按顺序赋值(不推荐,加字段会出bug)
p2 := Person{"Bob", 25, "Shanghai"}
// 方式3:零值初始化
var p3 Person // Name="", Age=0, City=""
// 方式4:使用 new 创建(返回指针)
p4 := new(Person) // p4 是 *Person 类型
p4.Name = "Dave"
// 方式5:取地址创建(最常用,返回指针)
p5 := &Person{
Name: "Eve",
Age: 22,
}
fmt.Println(p1, p2, p3, p4, p5)
}6.2 结构体指针
package main
import "fmt"
type User struct {
Name string
Age int
}
// 值接收:修改的是副本,不影响原始数据
func growUp(u User) {
u.Age++
fmt.Println("函数内:", u.Age) // 26
}
// 指针接收:修改原始数据
func growUpPtr(u *User) {
u.Age++ // Go 自动解引用,等价于 (*u).Age++
fmt.Println("函数内:", u.Age) // 26
}
func main() {
u := User{Name: "Alice", Age: 25}
growUp(u)
fmt.Println("值传递后:", u.Age) // 25(未改变)
growUpPtr(&u)
fmt.Println("指针传递后:", u.Age) // 26(已改变)
// 结构体指针访问字段
p := &u
// 以下两种写法等价,Go 会自动解引用
fmt.Println(p.Name) // Alice
fmt.Println((*p).Name) // Alice
}7. 继承的实现与应用
⚠️ 注意 Go 没有传统 OOP 中的继承(inheritance),而是通过 组合(composition) 和 嵌入(embedding) 来实现代码复用。Go 的设计哲学:组合优于继承。
package main
import "fmt"
// ==================== "父类":基础结构体 ====================
type Animal struct {
Name string
Age int
}
func (a Animal) Eat() {
fmt.Printf("%s is eating\n", a.Name)
}
func (a Animal) Sleep() {
fmt.Printf("%s is sleeping\n", a.Name)
}
func (a Animal) Info() string {
return fmt.Sprintf("[%s, age: %d]", a.Name, a.Age)
}
// ==================== "子类":通过嵌入实现继承 ====================
type Dog struct {
Animal // 匿名嵌入(类似继承)
Breed string // Dog 自己的字段
}
// Dog 可以有自己的方法
func (d Dog) Bark() {
fmt.Printf("%s: Woof!\n", d.Name)
}
// 方法重写(Override)
func (d Dog) Info() string {
// 调用"父类"方法
base := d.Animal.Info()
return fmt.Sprintf("%s, breed: %s", base, d.Breed)
}
// 更深的"继承"链
type GuideDog struct {
Dog
Owner string
}
func (g GuideDog) Guide() {
fmt.Printf("%s is guiding %s\n", g.Name, g.Owner)
}
func main() {
dog := Dog{
Animal: Animal{Name: "Buddy", Age: 3},
Breed: "Golden Retriever",
}
// 直接调用"继承"来的方法
dog.Eat() // Buddy is eating
dog.Sleep() // Buddy is sleeping
dog.Bark() // Buddy: Woof!
// 调用重写的方法
fmt.Println(dog.Info()) // [Buddy, age: 3], breed: Golden Retriever
// 仍然可以调用"父类"的原始方法
fmt.Println(dog.Animal.Info()) // [Buddy, age: 3]
// 多层"继承"
guide := GuideDog{
Dog: Dog{Animal: Animal{Name: "Rex", Age: 5}, Breed: "Labrador"},
Owner: "John",
}
guide.Eat() // Rex is eating(穿透两层嵌入)
guide.Bark() // Rex: Woof!
guide.Guide() // Rex is guiding John
}8. 结构体嵌套的几种形式和场景
package main
import "fmt"
// ==================== 形式1:命名嵌套(has-a 关系)====================
// 场景:明确的所属关系,如"用户有一个地址"
type Address struct {
Province string
City string
Street string
}
type Employee struct {
Name string
HomeAddr Address // 命名字段:必须通过字段名访问
WorkAddr Address
}
// ==================== 形式2:匿名嵌入(is-a 或功能扩展)====================
// 场景:继承行为,字段和方法被"提升"到外层
type Logger struct{}
func (l Logger) Log(msg string) {
fmt.Println("[LOG]", msg)
}
type Service struct {
Logger // 匿名嵌入:Service "is a" Logger
Name string
}
// ==================== 形式3:指针嵌入 ====================
// 场景:延迟初始化、共享实例、避免拷贝大结构体
type Config struct {
Debug bool
Version string
}
type App struct {
*Config // 指针嵌入:多个 App 可以共享同一个 Config
Name string
}
// ==================== 形式4:接口嵌入 ====================
// 场景:装饰器模式、Mock 测试
type Writer interface {
Write(data []byte) (int, error)
}
type BufferedWriter struct {
Writer // 嵌入接口(而非具体类型)
bufSize int
}
// ==================== 形式5:自引用(链表、树)====================
type TreeNode struct {
Value int
Left *TreeNode // 必须用指针,否则无限递归
Right *TreeNode
}
func main() {
// 形式1:命名嵌套
emp := Employee{
Name: "Alice",
HomeAddr: Address{Province: "北京", City: "北京", Street: "长安街"},
WorkAddr: Address{Province: "北京", City: "北京", Street: "中关村"},
}
fmt.Println(emp.HomeAddr.City) // 必须通过 HomeAddr 访问
fmt.Println(emp.WorkAddr.City)
// 形式2:匿名嵌入
svc := Service{Name: "UserService"}
svc.Log("started") // 直接调用 Logger 的方法:[LOG] started
// 形式3:指针嵌入
cfg := &Config{Debug: true, Version: "1.0"}
app1 := App{Config: cfg, Name: "App1"}
app2 := App{Config: cfg, Name: "App2"}
app1.Debug = false
fmt.Println(app2.Debug) // false(共享同一个 Config)
// 形式5:自引用
tree := &TreeNode{
Value: 1,
Left: &TreeNode{Value: 2},
Right: &TreeNode{Value: 3},
}
fmt.Println(tree.Value, tree.Left.Value, tree.Right.Value)
}选择建议 | 形式 | 使用场景 | |------|---------| | 命名嵌套 | 明确的 has-a 关系,多个同类型字段 | | 匿名嵌入 | 继承行为(方法提升),功能组合 | | 指针嵌入 | 共享实例,延迟初始化,大结构体 | | 接口嵌入 | 装饰器,Stub/Mock,部分实现 | | 自引用 | 树、链表、图等递归数据结构 |
9. 变量作用域
package main
import "fmt"
// 包级变量:整个包可见
var packageVar = "I'm package-level"
func main() {
// 函数级变量:整个函数可见
funcVar := "I'm function-level"
{
// 块级变量:只在当前 {} 内可见
blockVar := "I'm block-level"
fmt.Println(packageVar) // ✅
fmt.Println(funcVar) // ✅
fmt.Println(blockVar) // ✅
}
// fmt.Println(blockVar) // ❌ 编译错误:blockVar 不可见
// 变量遮蔽(Shadowing)—— Go 中常见的坑!
x := 10
fmt.Println("外层 x:", x) // 10
{
x := 20 // 这是一个新变量,遮蔽了外层的 x
fmt.Println("内层 x:", x) // 20
}
fmt.Println("外层 x:", x) // 10(未被修改)
// 经典坑::= 在 if 中创建新变量
var err error
if true {
// 这里 err 是新变量,不是外面那个 err!
val, err := someFunc()
fmt.Println(val, err)
}
fmt.Println("外层 err:", err) // 仍然是 nil!
// 解决办法:先声明再赋值
var val int
if true {
val, err = someFunc() // 用 = 而不是 :=
}
fmt.Println(val, err)
_ = funcVar
}
func someFunc() (int, error) {
return 42, nil
}10. if 语句及其局部变量
package main
import (
"fmt"
"os"
"strconv"
)
func main() {
// ==================== 基本 if ====================
age := 20
if age >= 18 {
fmt.Println("成年人")
} else {
fmt.Println("未成年")
}
// ==================== if 带初始化语句(Go 特色)====================
// 变量 f 只在 if-else 块内可见
if f, err := os.Open("test.txt"); err != nil {
fmt.Println("打开失败:", err)
// f 在这里也可用(虽然可能无效)
} else {
fmt.Println("打开成功")
f.Close()
}
// fmt.Println(f) // ❌ f 在这里不可见
// ==================== 实际应用:类型转换检查 ====================
input := "123"
if num, err := strconv.Atoi(input); err != nil {
fmt.Println("转换失败:", err)
} else {
fmt.Println("转换成功:", num*2) // 246
}
// ==================== 多条件 ====================
score := 85
if score >= 90 {
fmt.Println("优秀")
} else if score >= 80 {
fmt.Println("良好")
} else if score >= 60 {
fmt.Println("及格")
} else {
fmt.Println("不及格")
}
// Go 中 if 条件不需要括号
// if (x > 0) {} // 能编译但不推荐
// if x > 0 {} // ✅ 推荐写法
}11. for 循环的各种变体
package main
import "fmt"
func main() {
// ==================== 形式1:经典三段式 ====================
for i := 0; i < 5; i++ {
fmt.Print(i, " ") // 0 1 2 3 4
}
fmt.Println()
// ==================== 形式2:类似 while ====================
count := 0
for count < 5 {
fmt.Print(count, " ") // 0 1 2 3 4
count++
}
fmt.Println()
// ==================== 形式3:无限循环 ====================
// for {
// // 相当于 while(true)
// break // 必须有退出条件
// }
// ==================== 形式4:range 遍历 ====================
// 遍历切片
nums := []int{10, 20, 30, 40, 50}
for index, value := range nums {
fmt.Printf("index=%d, value=%d\n", index, value)
}
// 只要索引
for i := range nums {
fmt.Println("index:", i)
}
// 只要值(用 _ 忽略索引)
for _, v := range nums {
fmt.Println("value:", v)
}
// 遍历字符串(按 rune,不是按 byte)
for i, ch := range "Hello你好" {
fmt.Printf("byte位置=%d, 字符=%c, 码点=%d\n", i, ch, ch)
}
// byte位置=0, 字符=H, 码点=72
// byte位置=5, 字符=你, 码点=20320 ← 注意索引跳了!
// byte位置=8, 字符=好, 码点=22909
// 遍历 map
m := map[string]int{"a": 1, "b": 2, "c": 3}
for key, value := range m {
fmt.Printf("%s: %d\n", key, value) // 顺序不确定!
}
// 遍历 channel
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)
for v := range ch {
fmt.Println(v) // 1, 2, 3
}
// ==================== break 和 continue ====================
for i := 0; i < 10; i++ {
if i == 3 {
continue // 跳过 3
}
if i == 7 {
break // 到 7 就停
}
fmt.Print(i, " ") // 0 1 2 4 5 6
}
fmt.Println()
}12. label 与 goto
package main
import "fmt"
func main() {
// ==================== Label + break:跳出多层循环 ====================
// 这是 label 最常见和最推荐的用法
outer:
for i := 0; i < 5; i++ {
for j := 0; j < 5; j++ {
if i == 2 && j == 3 {
fmt.Println("跳出所有循环")
break outer // 跳出外层循环
}
fmt.Printf("(%d,%d) ", i, j)
}
fmt.Println()
}
fmt.Println("---")
// ==================== Label + continue:跳到外层的下一次迭代 ====================
loop:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if j == 1 {
continue loop // 跳到外层循环的下一次迭代
}
fmt.Printf("(%d,%d) ", i, j) // 只打印 j=0 的
}
}
fmt.Println()
// ==================== goto 语句 ====================
// goto 可以跳转到函数内的任意标签
// ⚠️ 不推荐使用,难以维护。只在特定场景(如错误清理)有用
i := 0
start:
if i >= 5 {
goto end
}
fmt.Print(i, " ")
i++
goto start
end:
fmt.Println("done")
// goto 的唯一合理场景:多步资源清理(但 defer 通常更好)
// 注意:goto 不能跳过变量声明
}⚠️ goto 使用建议在实际项目中,goto 几乎不应该使用。Go 提供了 defer 机制来处理资源清理,配合 break label 和 continue label 已足够处理复杂的流程控制。标准库中 goto 的使用也极其稀少。
13. switch 表达式与 fallthrough
package main
import (
"fmt"
"runtime"
)
func main() {
// ==================== 基本 switch ====================
day := "Monday"
switch day {
case "Monday":
fmt.Println("周一")
case "Tuesday":
fmt.Println("周二")
case "Saturday", "Sunday": // 多值匹配
fmt.Println("周末")
default:
fmt.Println("工作日")
}
// Go 的 switch 默认不穿透(不需要 break)
// ==================== switch 带初始化语句 ====================
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("macOS")
case "linux":
fmt.Println("Linux")
default:
fmt.Println(os)
}
// ==================== 无表达式 switch(替代 if-else 链)====================
score := 85
switch {
case score >= 90:
fmt.Println("A")
case score >= 80:
fmt.Println("B") // 命中这个
case score >= 60:
fmt.Println("C")
default:
fmt.Println("F")
}
// ==================== fallthrough(强制穿透到下一个 case)====================
num := 3
switch {
case num > 0:
fmt.Println("正数")
fallthrough // 无条件执行下一个 case 的代码
case num > -5:
fmt.Println("大于 -5") // 也会执行
// 不再 fallthrough,停止
case num > -10:
fmt.Println("大于 -10") // 不执行
}
// 输出:
// 正数
// 大于 -5
// ==================== 类型 switch ====================
var val interface{} = "hello"
switch v := val.(type) {
case int:
fmt.Printf("整数: %d\n", v)
case string:
fmt.Printf("字符串: %s\n", v) // 命中
case bool:
fmt.Printf("布尔: %v\n", v)
default:
fmt.Printf("未知类型: %T\n", v)
}
}fallthrough 注意事项 fallthrough 是无条件穿透,它不会检查下一个 case 的条件是否为 true。fallthrough 不能用在 type switch 中。实际开发中很少使用 fallthrough。
14. 位运算及其应用
package main
import "fmt"
func main() {
// ==================== 基本位运算 ====================
a := uint8(0b_1100_1010) // 202
b := uint8(0b_1010_1100) // 172
fmt.Printf("a = %08b (%d)\n", a, a)
fmt.Printf("b = %08b (%d)\n", b, b)
fmt.Printf("a & b = %08b (AND 按位与)\n", a&b) // 10001000
fmt.Printf("a | b = %08b (OR 按位或)\n", a|b) // 11101110
fmt.Printf("a ^ b = %08b (XOR 异或)\n", a^b) // 01100110
fmt.Printf("^a = %08b (NOT 取反)\n", ^a) // 00110101
fmt.Printf("a &^ b= %08b (AND NOT 位清除)\n", a&^b) // 01000010
fmt.Printf("a << 2= %08b (左移)\n", a<<2) // 00101000
fmt.Printf("a >> 2= %08b (右移)\n", a>>2) // 00110010
// ==================== 应用1:权限管理 ====================
const (
PermRead = 1 << iota // 001 = 1
PermWrite // 010 = 2
PermExecute // 100 = 4
)
// 组合权限
userPerm := PermRead | PermWrite // 011 = 3
fmt.Printf("\n用户权限: %03b\n", userPerm)
// 检查权限
fmt.Println("可读?", userPerm&PermRead != 0) // true
fmt.Println("可执行?", userPerm&PermExecute != 0) // false
// 添加权限
userPerm |= PermExecute
fmt.Printf("添加执行后: %03b\n", userPerm) // 111
// 移除权限
userPerm &^= PermWrite
fmt.Printf("移除写入后: %03b\n", userPerm) // 101
// ==================== 应用2:判断奇偶 ====================
fmt.Println("\n7 是奇数?", 7&1 == 1) // true(最低位为1)
fmt.Println("8 是奇数?", 8&1 == 1) // false
// ==================== 应用3:交换两个变量(不用临时变量)====================
x, y := 10, 20
x ^= y
y ^= x
x ^= y
fmt.Printf("\n交换后: x=%d, y=%d\n", x, y) // x=20, y=10
// ==================== 应用4:判断是否是 2 的幂 ====================
isPow2 := func(n int) bool {
return n > 0 && n&(n-1) == 0
}
fmt.Println("\n16 是 2 的幂?", isPow2(16)) // true
fmt.Println("18 是 2 的幂?", isPow2(18)) // false
}15. 函数 & Go 语言的拷贝原则
15.1 函数基础
package main
import (
"errors"
"fmt"
)
// 基本函数
func add(a, b int) int {
return a + b
}
// 多返回值
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("除数不能为 0")
}
return a / b, nil
}
// 命名返回值
func swap(a, b int) (x, y int) {
x = b
y = a
return // 裸 return,返回命名返回值
}
// 可变参数
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
// 函数作为值
func apply(f func(int, int) int, a, b int) int {
return f(a, b)
}
func main() {
fmt.Println(add(3, 5))
fmt.Println(divide(10, 3))
fmt.Println(swap(1, 2))
fmt.Println(sum(1, 2, 3, 4, 5))
// 匿名函数
double := func(x int) int { return x * 2 }
fmt.Println(double(5)) // 10
// 高阶函数
fmt.Println(apply(add, 10, 20)) // 30
}15.2 Go 的值拷贝原则
package main
import "fmt"
// ==================== 核心原则 ====================
// Go 中一切赋值和传参都是 **值拷贝**
// 但不同类型"拷贝的内容"不同:
//
// 值类型(深拷贝效果):int, float, bool, string, array, struct
// → 拷贝完整数据,修改互不影响
//
// 引用类型(浅拷贝效果):slice, map, channel, func, interface, *T
// → 拷贝的是"头部/描述符",底层数据共享
// → 所以看起来"像引用传递",但本质仍是值拷贝
type Point struct {
X, Y int
}
func modifyStruct(p Point) {
p.X = 999 // 修改的是副本
}
func modifyStructPtr(p *Point) {
p.X = 999 // 修改原始数据
}
func modifySlice(s []int) {
s[0] = 999 // 会影响原始切片(底层数组共享)
}
func modifyMap(m map[string]int) {
m["key"] = 999 // 会影响原始 map(本身是引用语义)
}
func main() {
// 结构体:值拷贝
p := Point{1, 2}
modifyStruct(p)
fmt.Println("struct 值传递:", p) // {1 2}(未变)
modifyStructPtr(&p)
fmt.Println("struct 指针传递:", p) // {999 2}(已变)
// 切片:拷贝 header(指针+长度+容量),底层数组共享
s := []int{1, 2, 3}
modifySlice(s)
fmt.Println("slice 传递后:", s) // [999 2 3](已变)
// map:本身就是指针语义
m := map[string]int{"key": 1}
modifyMap(m)
fmt.Println("map 传递后:", m) // map[key:999](已变)
}16. 数组 & 函数参数需要传数组指针吗
package main
import "fmt"
func main() {
// ==================== 数组基础 ====================
// 数组是值类型,长度是类型的一部分
var arr1 [5]int // 零值: [0 0 0 0 0]
arr2 := [5]int{1, 2, 3, 4, 5} // 完整初始化
arr3 := [5]int{1, 2} // 部分初始化: [1 2 0 0 0]
arr4 := [...]int{1, 2, 3} // 编译器推断长度: [3]int
arr5 := [5]int{1: 10, 3: 30} // 指定索引: [0 10 0 30 0]
fmt.Println(arr1, arr2, arr3, arr4, arr5)
// [5]int 和 [3]int 是不同类型!
// arr2 = arr4 // ❌ 编译错误
// 数组是值类型,赋值会拷贝
a := [3]int{1, 2, 3}
b := a // 完整拷贝
b[0] = 999
fmt.Println("a:", a) // [1 2 3](未变)
fmt.Println("b:", b) // [999 2 3]
}
// ==================== 数组做函数参数 ====================
// 方式1:传值(拷贝整个数组,大数组很浪费)
func sumArray(arr [1000000]int) int {
total := 0
for _, v := range arr {
total += v
}
return total // 每次调用拷贝 ~8MB!
}
// 方式2:传数组指针(避免拷贝,但类型耦合)
func sumArrayPtr(arr *[1000000]int) int {
total := 0
for _, v := range arr {
total += v
}
return total // 只拷贝一个指针(8 bytes)
}
// 方式3:传切片(✅ 推荐!灵活且高效)
func sumSlice(s []int) int {
total := 0
for _, v := range s {
total += v
}
return total // 只拷贝 slice header(24 bytes)
}
// 结论:
// ❌ 不推荐传数组(值拷贝浪费内存)
// ⚠️ 传数组指针可以但不灵活(长度绑死)
// ✅ 推荐传切片(既高效又灵活,这是 Go 的惯用写法)17. 切片及扩容规律
17.1 切片基础与内部结构
package main
import (
"fmt"
"unsafe"
)
// 切片的内部结构等价于:
// type slice struct {
// array unsafe.Pointer // 指向底层数组
// len int // 当前长度
// cap int // 容量
// }
// 所以 slice header 固定占 24 字节(64位系统)
func main() {
// ==================== 创建切片的多种方式 ====================
// 方式1:从数组切割
arr := [5]int{1, 2, 3, 4, 5}
s1 := arr[1:4] // [2, 3, 4], len=3, cap=4
// 方式2:字面量
s2 := []int{10, 20, 30}
// 方式3:make
s3 := make([]int, 5) // len=5, cap=5, 全 0
s4 := make([]int, 3, 10) // len=3, cap=10
// 方式4:var 声明(nil 切片)
var s5 []int // nil 切片,len=0, cap=0
fmt.Println(s1, s2, s3, s4, s5)
fmt.Println("nil切片:", s5 == nil) // true
fmt.Println("header 大小:", unsafe.Sizeof(s1)) // 24
}17.2 扩容规律
package main
import "fmt"
func main() {
// Go 1.18+ 切片扩容规律:
// 1. 当 cap < 256 时:新 cap = 旧 cap * 2(翻倍)
// 2. 当 cap >= 256 时:新 cap = 旧 cap + (旧 cap + 3*256) / 4
// 即大约增长 25% + 192,逐渐从 2x 平滑过渡到 ~1.25x
// 3. 最终结果会向上对齐到内存分配器的 size class
s := make([]int, 0)
prevCap := 0
for i := 0; i < 2000; i++ {
s = append(s, i)
if cap(s) != prevCap {
fmt.Printf("len=%-5d cap=%-5d 增长比=%.2f\n",
len(s), cap(s), float64(cap(s))/float64(max(prevCap, 1)))
prevCap = cap(s)
}
}
// 输出示例(部分):
// len=1 cap=1 增长比=1.00
// len=2 cap=2 增长比=2.00
// len=3 cap=4 增长比=2.00
// len=5 cap=8 增长比=2.00
// len=9 cap=16 增长比=2.00
// ...小容量时翻倍...
// len=257 cap=512 增长比=2.00
// len=513 cap=848 增长比=1.66 ← 开始减缓
// len=849 cap=1280 增长比=1.51
// len=1281 cap=1792 增长比=1.40
}
func max(a, b int) int {
if a > b {
return a
}
return b
}18. 切片内存共享
⚠️ 这是 Go 切片最容易踩的坑多个切片可以共享同一个底层数组,修改一个会影响另一个。
package main
import "fmt"
func main() {
// ==================== 内存共享演示 ====================
original := []int{1, 2, 3, 4, 5}
sub := original[1:3] // [2, 3]
// sub 和 original 共享底层数组
sub[0] = 999
fmt.Println("original:", original) // [1 999 3 4 5] 被影响了!
fmt.Println("sub: ", sub) // [999 3]
// ==================== append 的陷阱 ====================
a := []int{1, 2, 3, 4, 5}
b := a[0:2] // [1, 2], len=2, cap=5
// b 的 cap 还有空间,append 不会分配新数组
b = append(b, 999)
fmt.Println("a:", a) // [1 2 999 4 5] ← a[2] 被覆盖了!
fmt.Println("b:", b) // [1 2 999]
// ==================== 安全切割:限制容量 ====================
c := []int{1, 2, 3, 4, 5}
d := c[0:2:2] // [start:end:max] → len=2, cap=2(限制 cap!)
d = append(d, 999) // cap 不够,会分配新数组
fmt.Println("c:", c) // [1 2 3 4 5] ← 安全!未被修改
fmt.Println("d:", d) // [1 2 999]
// ==================== 安全复制 ====================
e := []int{1, 2, 3, 4, 5}
f := make([]int, len(e))
copy(f, e) // 完整复制,完全独立
f[0] = 999
fmt.Println("e:", e) // [1 2 3 4 5](安全)
fmt.Println("f:", f) // [999 2 3 4 5]
}19. 切片相关函数
package main
import (
"fmt"
"slices" // Go 1.21+
)
func main() {
// ==================== append ====================
s := []int{1, 2, 3}
s = append(s, 4) // 追加单个元素
s = append(s, 5, 6, 7) // 追加多个元素
s = append(s, []int{8, 9}...) // 追加另一个切片(展开)
fmt.Println("append:", s) // [1 2 3 4 5 6 7 8 9]
// ==================== copy ====================
src := []int{1, 2, 3, 4, 5}
dst := make([]int, 3)
n := copy(dst, src) // 拷贝 min(len(dst), len(src)) 个元素
fmt.Println("copy:", dst, "copied:", n) // [1 2 3] copied: 3
// ==================== 删除元素 ====================
s2 := []int{1, 2, 3, 4, 5}
// 删除索引 2(保持顺序)
i := 2
s2 = append(s2[:i], s2[i+1:]...)
fmt.Println("删除后:", s2) // [1 2 4 5]
// 删除索引 1(不保持顺序,性能更好)
s3 := []int{1, 2, 3, 4, 5}
j := 1
s3[j] = s3[len(s3)-1] // 用最后一个元素覆盖
s3 = s3[:len(s3)-1]
fmt.Println("快速删除:", s3) // [1 5 3 4]
// ==================== 插入元素 ====================
s4 := []int{1, 2, 4, 5}
// 在索引 2 处插入 3
idx := 2
s4 = append(s4[:idx], append([]int{3}, s4[idx:]...)...)
fmt.Println("插入后:", s4) // [1 2 3 4 5]
// ==================== Go 1.21+ slices 包 ====================
s5 := []int{3, 1, 4, 1, 5, 9, 2, 6}
// 排序
slices.Sort(s5)
fmt.Println("排序:", s5) // [1 1 2 3 4 5 6 9]
// 查找
idx2, found := slices.BinarySearch(s5, 4)
fmt.Println("查找 4:", idx2, found) // 4 true
// 去重(需先排序)
s5 = slices.Compact(s5)
fmt.Println("去重:", s5) // [1 2 3 4 5 6 9]
// 包含
fmt.Println("包含 5?", slices.Contains(s5, 5)) // true
// 反转
slices.Reverse(s5)
fmt.Println("反转:", s5) // [9 6 5 4 3 2 1]
}20. 字符串及常用操作
package main
import (
"fmt"
"strings"
"unicode/utf8"
)
func main() {
// ==================== 字符串本质 ====================
// Go 字符串是只读的字节切片 ([]byte 的不可变版本)
// 内部结构:struct { ptr *byte; len int }
// 字符串默认 UTF-8 编码
s := "Hello, 世界"
// 长度:字节数 vs 字符数
fmt.Println("字节长度:", len(s)) // 13("世界"各占3字节)
fmt.Println("字符个数:", utf8.RuneCountInString(s)) // 9
// 遍历字节 vs 遍历字符
for i := 0; i < len(s); i++ {
fmt.Printf("%x ", s[i]) // 逐字节
}
fmt.Println()
for _, r := range s {
fmt.Printf("%c ", r) // 逐字符(rune)
}
fmt.Println()
// ==================== strings 包常用函数 ====================
// 查找
fmt.Println(strings.Contains("seafood", "foo")) // true
fmt.Println(strings.HasPrefix("hello", "he")) // true
fmt.Println(strings.HasSuffix("hello", "lo")) // true
fmt.Println(strings.Index("hello", "ll")) // 2
fmt.Println(strings.Count("cheese", "e")) // 3
// 转换
fmt.Println(strings.ToUpper("hello")) // HELLO
fmt.Println(strings.ToLower("HELLO")) // hello
fmt.Println(strings.Title("hello world")) // Hello World(已废弃)
// 修剪
fmt.Println(strings.TrimSpace(" hello ")) // "hello"
fmt.Println(strings.Trim("***hello***", "*")) // "hello"
fmt.Println(strings.TrimPrefix("foobar", "foo")) // "bar"
fmt.Println(strings.TrimSuffix("foobar", "bar")) // "foo"
// 分割与连接
parts := strings.Split("a,b,c,d", ",")
fmt.Println(parts) // [a b c d]
joined := strings.Join(parts, " | ")
fmt.Println(joined) // a | b | c | d
// 替换
fmt.Println(strings.Replace("aaa", "a", "b", 2)) // bba(替换前2个)
fmt.Println(strings.ReplaceAll("aaa", "a", "b")) // bbb
// 重复
fmt.Println(strings.Repeat("ha", 3)) // hahaha
// ==================== strings.Builder(高效拼接)====================
// 不要用 + 拼接大量字符串(每次产生新对象)
var builder strings.Builder
for i := 0; i < 100; i++ {
builder.WriteString("hello")
}
result := builder.String()
fmt.Println(len(result)) // 500
}21. 数据类型转换
package main
import (
"fmt"
"strconv"
)
func main() {
// ==================== 数值类型之间的转换 ====================
// Go 不允许隐式类型转换,必须显式转换
var i int = 42
var f float64 = float64(i) // int → float64
var u uint = uint(f) // float64 → uint
fmt.Println(i, f, u)
// 注意精度丢失
var bigFloat float64 = 1.9
var truncated int = int(bigFloat) // 直接截断,不是四舍五入!
fmt.Println(truncated) // 1
// 溢出(不报错,直接截断)
var big int64 = 256
var small int8 = int8(big)
fmt.Println(small) // 0(溢出)
// ==================== 字符串 ↔ 数值 ====================
// int → string
s1 := strconv.Itoa(42) // "42"
s2 := fmt.Sprintf("%d", 42) // "42"(更通用)
// string → int
n1, err := strconv.Atoi("42")
fmt.Println(n1, err) // 42 <nil>
n2, err := strconv.Atoi("abc")
fmt.Println(n2, err) // 0 parsing "abc": invalid syntax
// string → float
f1, _ := strconv.ParseFloat("3.14", 64)
fmt.Println(f1) // 3.14
// float → string
s3 := strconv.FormatFloat(3.14, 'f', 2, 64)
fmt.Println(s3) // "3.14"
// string → bool
b1, _ := strconv.ParseBool("true")
fmt.Println(b1) // true
// ==================== 字符串 ↔ 字节切片 ====================
str := "Hello, 世界"
bs := []byte(str) // string → []byte(拷贝)
rs := []rune(str) // string → []rune(拷贝)
str2 := string(bs) // []byte → string(拷贝)
fmt.Println(bs) // [72 101 108 108 111 44 32 228 184 150 231 149 140]
fmt.Println(rs) // [72 101 108 108 111 44 32 19990 30028]
fmt.Println(str2) // Hello, 世界
// 单个数字转字符
fmt.Println(string(65)) // "A"(Unicode 码点 → 字符)
fmt.Println(string(0x4e16)) // "世"
_ = s1
_ = s2
}22. HashTable 实现原理,map 扩容与遍历
22.1 map 基础用法
package main
import "fmt"
func main() {
// ==================== 创建 map ====================
m1 := map[string]int{"a": 1, "b": 2} // 字面量
m2 := make(map[string]int) // make 创建
m3 := make(map[string]int, 100) // 指定初始容量(建议)
var m4 map[string]int // nil map
_ = m2
_ = m3
// nil map 可以读,但不能写
fmt.Println(m4["key"]) // 0(零值)
// m4["key"] = 1 // ❌ panic!
// ==================== CRUD 操作 ====================
m1["c"] = 3 // 增/改
delete(m1, "a") // 删
v := m1["b"] // 查
v2, ok := m1["x"] // 查(带存在性检查)
fmt.Println(v, v2, ok) // 2 0 false
// ==================== 遍历(顺序随机)====================
for k, v := range m1 {
fmt.Printf("%s: %d\n", k, v)
}
}22.2 底层原理
// ==================== Go map 底层结构(简化)====================
//
// Go 的 map 使用 HashTable 实现,核心结构:
//
// hmap {
// count int // 当前元素个数
// B uint8 // 桶数组大小 = 2^B
// buckets unsafe.Pointer // 桶数组指针
// oldbuckets unsafe.Pointer // 扩容时的旧桶
// ...
// }
//
// 每个 bucket(bmap)最多存 8 个 key-value 对:
// bmap {
// tophash [8]uint8 // 每个 key 的 hash 高 8 位(快速比较)
// keys [8]keyType
// values [8]valueType
// overflow *bmap // 溢出桶链表
// }
//
// ==================== 查找过程 ====================
// 1. 计算 key 的 hash
// 2. hash 低 B 位确定桶编号
// 3. hash 高 8 位在桶内逐个比较 tophash
// 4. tophash 匹配后再比较完整 key
// 5. 找不到就沿 overflow 链继续找
//
// ==================== 扩容机制 ====================
// 触发条件(满足其一):
// 1. 装载因子 > 6.5(count / 2^B > 6.5)→ 翻倍扩容
// 2. 溢出桶太多 → 等量扩容(整理碎片)
//
// 扩容方式:渐进式迁移
// - 不是一次性搬完,而是每次读写操作时迁移 1-2 个旧桶
// - 扩容期间 oldbuckets 和 buckets 同时存在
//
// ==================== 遍历为什么是随机的 ====================
// Go 故意在 range map 时加入随机起始位置
// 防止开发者依赖 map 的遍历顺序(因为扩容后顺序会变)
// 这是语言设计决策,不是实现副作用23. 不定长参数和函数递归
package main
import "fmt"
// ==================== 不定长参数 ====================
// 不定长参数本质是一个切片
func sum(nums ...int) int {
// nums 的类型是 []int
total := 0
for _, n := range nums {
total += n
}
return total
}
// 不定长参数必须是最后一个参数
func printInfo(prefix string, values ...interface{}) {
fmt.Print(prefix, ": ")
for _, v := range values {
fmt.Printf("%v ", v)
}
fmt.Println()
}
// 传递不定长参数
func wrapper(nums ...int) int {
// 用 ... 展开传递给另一个不定长参数函数
return sum(nums...)
}
// ==================== 递归 ====================
// 阶乘
func factorial(n int) int {
if n <= 1 { // 递归终止条件(极其重要!)
return 1
}
return n * factorial(n-1)
}
// 斐波那契(朴素递归,效率很低)
func fib(n int) int {
if n <= 1 {
return n
}
return fib(n-1) + fib(n-2)
}
// 斐波那契(尾递归优化版本)
func fibTail(n, a, b int) int {
if n == 0 {
return a
}
return fibTail(n-1, b, a+b)
}
// 实用递归:遍历嵌套结构
type TreeNode struct {
Value int
Children []*TreeNode
}
func traverse(node *TreeNode, depth int) {
if node == nil {
return
}
for i := 0; i < depth; i++ {
fmt.Print(" ")
}
fmt.Println(node.Value)
for _, child := range node.Children {
traverse(child, depth+1)
}
}
func main() {
fmt.Println(sum(1, 2, 3, 4, 5)) // 15
fmt.Println(sum()) // 0
// 切片展开传递
nums := []int{10, 20, 30}
fmt.Println(sum(nums...)) // 60
printInfo("debug", "hello", 42, true)
fmt.Println("5! =", factorial(5)) // 120
fmt.Println("fib(10) =", fib(10)) // 55
fmt.Println("fib(10) =", fibTail(10, 0, 1)) // 55
// 树形遍历
tree := &TreeNode{
Value: 1,
Children: []*TreeNode{
{Value: 2, Children: []*TreeNode{
{Value: 4}, {Value: 5},
}},
{Value: 3},
},
}
traverse(tree, 0)
}24. 时间类型及其格式化
package main
import (
"fmt"
"time"
)
func main() {
// ==================== 获取当前时间 ====================
now := time.Now()
fmt.Println("当前时间:", now)
fmt.Println("年:", now.Year())
fmt.Println("月:", now.Month())
fmt.Println("日:", now.Day())
fmt.Println("时:", now.Hour())
fmt.Println("分:", now.Minute())
fmt.Println("秒:", now.Second())
fmt.Println("纳秒:", now.Nanosecond())
fmt.Println("星期:", now.Weekday())
fmt.Println("时间戳(秒):", now.Unix())
fmt.Println("时间戳(毫秒):", now.UnixMilli())
// ==================== 时间格式化(Go 的独特设计)====================
// Go 使用「参考时间」而不是 yyyy-MM-dd 这种符号
// 参考时间:Mon Jan 2 15:04:05 MST 2006
// 记忆口诀:1月2日下午3点4分5秒 2006年(1 2 3 4 5 6 7)
fmt.Println(now.Format("2006-01-02 15:04:05")) // 2024-01-15 14:30:45
fmt.Println(now.Format("2006/01/02")) // 2024/01/15
fmt.Println(now.Format("15:04:05")) // 14:30:45
fmt.Println(now.Format("2006年01月02日")) // 2024年01月15日
fmt.Println(now.Format(time.RFC3339)) // 2024-01-15T14:30:45+08:00
fmt.Println(now.Format("Mon, 02 Jan 2006")) // Mon, 15 Jan 2024
// 12小时制 vs 24小时制
fmt.Println(now.Format("03:04 PM")) // 02:30 PM(12小时制用 03)
fmt.Println(now.Format("15:04")) // 14:30 (24小时制用 15)
// ==================== 字符串解析为时间 ====================
t, err := time.Parse("2006-01-02 15:04:05", "2024-06-15 10:30:00")
if err != nil {
fmt.Println("解析错误:", err)
}
fmt.Println("解析结果:", t)
// 带时区的解析
loc, _ := time.LoadLocation("Asia/Shanghai")
t2, _ := time.ParseInLocation("2006-01-02 15:04:05", "2024-06-15 10:30:00", loc)
fmt.Println("上海时间:", t2)
// ==================== 时间运算 ====================
// 加减时间
future := now.Add(24 * time.Hour) // 加一天
past := now.Add(-2 * time.Hour) // 减两小时
nextMonth := now.AddDate(0, 1, 0) // 加一个月
fmt.Println("明天:", future.Format("2006-01-02"))
fmt.Println("两小时前:", past.Format("15:04:05"))
fmt.Println("下个月:", nextMonth.Format("2006-01-02"))
// 计算时间差
duration := future.Sub(now)
fmt.Println("时间差:", duration) // 24h0m0s
// 比较时间
fmt.Println("future 在 now 之后?", future.After(now)) // true
fmt.Println("past 在 now 之前?", past.Before(now)) // true
}25. 定时执行和周期执行
package main
import (
"fmt"
"time"
)
func main() {
// ==================== time.Sleep:最简单的等待 ====================
fmt.Println("开始")
time.Sleep(1 * time.Second) // 阻塞 1 秒
fmt.Println("1秒后")
// ==================== time.After:一次性定时器(返回 channel)====================
fmt.Println("等待 2 秒...")
<-time.After(2 * time.Second) // 阻塞直到时间到
fmt.Println("2秒到了")
// 常用于超时控制
ch := make(chan string)
go func() {
time.Sleep(3 * time.Second)
ch <- "result"
}()
select {
case result := <-ch:
fmt.Println("收到:", result)
case <-time.After(2 * time.Second):
fmt.Println("超时了!")
}
// ==================== time.NewTimer:可控的一次性定时器 ====================
timer := time.NewTimer(2 * time.Second)
// 可以提前停止
go func() {
time.Sleep(1 * time.Second)
stopped := timer.Stop() // 停止定时器
fmt.Println("定时器已停止:", stopped)
}()
// 也可以重置
// timer.Reset(5 * time.Second)
// ==================== time.NewTicker:周期执行 ====================
ticker := time.NewTicker(500 * time.Millisecond)
done := make(chan bool)
go func() {
for {
select {
case <-done:
return
case t := <-ticker.C:
fmt.Println("Tick:", t.Format("15:04:05.000"))
}
}
}()
time.Sleep(2 * time.Second) // 让 ticker 运行 2 秒
ticker.Stop() // 停止 ticker
done <- true
fmt.Println("Ticker 已停止")
}26. defer 经典案例
package main
import (
"fmt"
"os"
"sync"
)
func main() {
// ==================== 基本 defer ====================
// defer 在函数返回时执行,先进后出(LIFO)
fmt.Println("=== 执行顺序 ===")
defer fmt.Println("第一个 defer")
defer fmt.Println("第二个 defer")
defer fmt.Println("第三个 defer")
// 输出顺序:第三个 → 第二个 → 第一个
// ==================== 案例1:资源清理 ====================
fmt.Println("\n=== 资源清理 ===")
file, err := os.Create("/tmp/test.txt")
if err != nil {
fmt.Println(err)
return
}
defer file.Close() // 确保文件一定会关闭
file.WriteString("hello")
// ==================== 案例2:解锁 ====================
var mu sync.Mutex
mu.Lock()
defer mu.Unlock() // 确保一定会解锁
// ==================== 案例3:defer 与返回值的关系 ====================
fmt.Println("\n=== defer 修改返回值 ===")
fmt.Println("结果:", deferReturn()) // 2(不是 1!)
// ==================== 案例4:defer 参数立即求值 ====================
fmt.Println("\n=== 参数立即求值 ===")
x := 10
defer fmt.Printf("defer 中 x = %d\n", x) // x 在 defer 注册时就已求值为 10
x = 20
fmt.Println("当前 x =", x) // 20
// defer 输出: x = 10(不是 20)
// ==================== 案例5:循环中的 defer(陷阱)====================
fmt.Println("\n=== 循环中的 defer ===")
// ❌ 错误:defer 在函数结束才执行,循环中会堆积
// for i := 0; i < 10000; i++ {
// f, _ := os.Open("file")
// defer f.Close() // 所有 Close 在函数结束才执行!
// }
// ✅ 正确:用匿名函数包裹
for i := 0; i < 3; i++ {
func() {
f, err := os.CreateTemp("", "test")
if err != nil {
return
}
defer f.Close() // 每次循环结束就关闭
f.WriteString(fmt.Sprintf("file %d", i))
}()
}
}
// defer 可以修改命名返回值
func deferReturn() (result int) {
result = 1
defer func() {
result++ // 修改命名返回值
}()
return result // 返回 2
// 执行顺序:result = 1 → return(设置返回值) → defer(result++) → 实际返回
}27. panic 与 recover
package main
import (
"fmt"
"runtime/debug"
)
// ==================== panic:程序崩溃 ====================
// panic 的触发方式:
// 1. 主动调用 panic(msg)
// 2. 运行时错误:数组越界、nil 指针、类型断言失败...
// ==================== recover:捕获 panic ====================
// recover 只能在 defer 函数中使用
// 类似于 try-catch,但 Go 不鼓励滥用
// 安全的除法
func safeDivide(a, b int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic recovered: %v", r)
// 可选:打印调用栈
debug.PrintStack()
}
}()
return a / b, nil // 如果 b=0 会 panic
}
// HTTP 处理器中的 recover(实际场景)
func safeHandler(handler func()) {
defer func() {
if r := recover(); r != nil {
fmt.Printf("[Recovery] panic: %v\n", r)
fmt.Printf("Stack:\n%s\n", debug.Stack())
// 实际项目中:返回 500 错误
}
}()
handler()
}
func main() {
// 基本 recover
result, err := safeDivide(10, 0)
if err != nil {
fmt.Println("错误:", err)
} else {
fmt.Println("结果:", result)
}
// 正常的除法
result2, _ := safeDivide(10, 3)
fmt.Println("10/3 =", result2)
// 模拟 HTTP 处理器
safeHandler(func() {
var s []int
_ = s[10] // panic: index out of range
})
fmt.Println("程序继续运行...") // recover 后程序不会崩溃
}
// ==================== 使用原则 ====================
// 1. 绝大多数情况下,用 error 返回值处理错误,不要用 panic
// 2. panic 只用于"不可恢复的错误"(程序初始化失败等)
// 3. recover 主要用于:
// - HTTP/RPC 框架的中间件(防止单个请求崩溃服务)
// - goroutine 的兜底保护
// - 库代码中将 panic 转为 error 返回给调用者
// 4. 不要用 panic/recover 做常规流程控制(这不是 try-catch)28. channel 及引用类型的本质
package main
import (
"fmt"
"sync"
)
func main() {
// ==================== channel 基础 ====================
// channel 本质上是一个指向 runtime.hchan 结构的指针
// 所以 channel 是引用类型,零值为 nil
// 无缓冲 channel(同步通信,发送和接收必须同时就绪)
ch1 := make(chan int)
go func() {
ch1 <- 42 // 发送,阻塞直到有人接收
}()
val := <-ch1 // 接收,阻塞直到有人发送
fmt.Println("收到:", val)
// 有缓冲 channel(异步通信,缓冲区满才阻塞)
ch2 := make(chan string, 3)
ch2 <- "a"
ch2 <- "b"
ch2 <- "c"
// ch2 <- "d" // 会阻塞(缓冲区已满)
fmt.Println(<-ch2, <-ch2, <-ch2)
// ==================== 单向 channel ====================
// 用于函数签名,限制 channel 的使用方向
// 只能发送
producer := func(ch chan<- int) {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}
// 只能接收
consumer := func(ch <-chan int) {
for v := range ch {
fmt.Print(v, " ")
}
fmt.Println()
}
ch3 := make(chan int, 5)
producer(ch3)
consumer(ch3)
// ==================== select:多路复用 ====================
ch4 := make(chan string)
ch5 := make(chan string)
go func() { ch4 <- "from ch4" }()
go func() { ch5 <- "from ch5" }()
// select 随机选择一个就绪的 case
for i := 0; i < 2; i++ {
select {
case msg := <-ch4:
fmt.Println(msg)
case msg := <-ch5:
fmt.Println(msg)
}
}
// ==================== 引用类型的本质 ====================
// Go 中的引用类型:slice, map, channel, func, interface
// 它们的"零值"都是 nil
// 赋值/传参时拷贝的是"描述符/指针",底层数据共享
// channel 传参示例
var wg sync.WaitGroup
ch6 := make(chan int, 10)
wg.Add(1)
go func(ch chan int) { // ch 是 ch6 的拷贝,但指向同一个 hchan
defer wg.Done()
for v := range ch {
fmt.Print(v, " ")
}
}(ch6)
for i := 0; i < 5; i++ {
ch6 <- i
}
close(ch6)
wg.Wait()
fmt.Println()
}29. 接口有什么用怎么用
package main
import (
"fmt"
"math"
)
// ==================== 接口定义 ====================
// 接口是一组方法签名的集合
// Go 使用隐式接口实现(不需要 implements 关键字)
type Shape interface {
Area() float64
Perimeter() float64
}
// 实现 Shape 的类型 1:圆
type Circle struct {
Radius float64
}
// Circle 实现了 Shape 的所有方法,自动满足接口
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
// 实现 Shape 的类型 2:矩形
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// ==================== 接口的作用 ====================
// 作用1:统一处理不同类型
func printShapeInfo(s Shape) {
fmt.Printf("类型: %T, 面积: %.2f, 周长: %.2f\n",
s, s.Area(), s.Perimeter())
}
// 作用2:依赖注入、可测试性
type Logger interface {
Log(msg string)
}
type ConsoleLogger struct{}
func (l ConsoleLogger) Log(msg string) { fmt.Println("[Console]", msg) }
type FileLogger struct{ Path string }
func (l FileLogger) Log(msg string) { fmt.Printf("[File:%s] %s\n", l.Path, msg) }
type Service struct {
logger Logger // 依赖接口而非具体类型
}
func NewService(logger Logger) *Service {
return &Service{logger: logger}
}
func (s *Service) DoWork() {
s.logger.Log("doing work...")
}
// 作用3:接口组合
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type ReadWriter interface {
Reader
Writer
}
func main() {
// 多态:不同类型统一处理
shapes := []Shape{
Circle{Radius: 5},
Rectangle{Width: 3, Height: 4},
}
for _, s := range shapes {
printShapeInfo(s)
}
// 依赖注入:灵活切换实现
svc1 := NewService(ConsoleLogger{})
svc1.DoWork() // [Console] doing work...
svc2 := NewService(FileLogger{Path: "/var/log/app.log"})
svc2.DoWork() // [File:/var/log/app.log] doing work...
// 类型断言
var s Shape = Circle{Radius: 3}
if c, ok := s.(Circle); ok {
fmt.Println("是圆形,半径:", c.Radius)
}
}30. 面向接口编程案例——推荐系统
package main
import (
"fmt"
"math/rand"
"sort"
)
// ==================== 接口设计 ====================
// Item 代表可推荐的内容
type Item struct {
ID string
Title string
Category string
Tags []string
Score float64 // 推荐得分
}
// Recommender 推荐策略接口
type Recommender interface {
Name() string
Recommend(userTags []string, items []Item, limit int) []Item
}
// Ranker 排序策略接口
type Ranker interface {
Rank(items []Item) []Item
}
// Filter 过滤策略接口
type Filter interface {
Filter(items []Item) []Item
}
// ==================== 推荐策略实现 ====================
// 基于标签匹配的推荐
type TagBasedRecommender struct{}
func (r TagBasedRecommender) Name() string { return "TagBased" }
func (r TagBasedRecommender) Recommend(userTags []string, items []Item, limit int) []Item {
tagSet := make(map[string]bool)
for _, t := range userTags {
tagSet[t] = true
}
var results []Item
for _, item := range items {
score := 0.0
for _, tag := range item.Tags {
if tagSet[tag] {
score += 1.0
}
}
if score > 0 {
item.Score = score
results = append(results, item)
}
}
sort.Slice(results, func(i, j int) bool {
return results[i].Score > results[j].Score
})
if len(results) > limit {
results = results[:limit]
}
return results
}
// 随机推荐(用于冷启动)
type RandomRecommender struct{}
func (r RandomRecommender) Name() string { return "Random" }
func (r RandomRecommender) Recommend(_ []string, items []Item, limit int) []Item {
shuffled := make([]Item, len(items))
copy(shuffled, items)
rand.Shuffle(len(shuffled), func(i, j int) {
shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
})
if len(shuffled) > limit {
return shuffled[:limit]
}
return shuffled
}
// ==================== 推荐引擎(组装各策略)====================
type RecommendEngine struct {
primary Recommender // 主推荐策略
fallback Recommender // 降级策略
filters []Filter // 过滤链
ranker Ranker // 排序策略
}
func NewEngine(primary, fallback Recommender) *RecommendEngine {
return &RecommendEngine{
primary: primary,
fallback: fallback,
}
}
func (e *RecommendEngine) AddFilter(f Filter) {
e.filters = append(e.filters, f)
}
func (e *RecommendEngine) SetRanker(r Ranker) {
e.ranker = r
}
func (e *RecommendEngine) GetRecommendations(userTags []string, items []Item, limit int) []Item {
// 1. 过滤
filtered := items
for _, f := range e.filters {
filtered = f.Filter(filtered)
}
// 2. 推荐
results := e.primary.Recommend(userTags, filtered, limit)
fmt.Printf(" [%s] 推荐了 %d 个结果\n", e.primary.Name(), len(results))
// 3. 降级
if len(results) < limit && e.fallback != nil {
more := e.fallback.Recommend(userTags, filtered, limit-len(results))
results = append(results, more...)
fmt.Printf(" [%s] 补充了 %d 个结果\n", e.fallback.Name(), len(more))
}
// 4. 排序
if e.ranker != nil {
results = e.ranker.Rank(results)
}
return results
}
// ==================== 实现一个过滤器 ====================
type CategoryFilter struct {
Allowed map[string]bool
}
func (f CategoryFilter) Filter(items []Item) []Item {
var result []Item
for _, item := range items {
if f.Allowed[item.Category] {
result = append(result, item)
}
}
return result
}
func main() {
// 准备数据
items := []Item{
{ID: "1", Title: "Go 入门", Category: "tech", Tags: []string{"go", "编程"}},
{ID: "2", Title: "Python 实战", Category: "tech", Tags: []string{"python", "编程"}},
{ID: "3", Title: "美食推荐", Category: "food", Tags: []string{"美食", "旅行"}},
{ID: "4", Title: "Go 高级编程", Category: "tech", Tags: []string{"go", "并发"}},
{ID: "5", Title: "旅行日记", Category: "travel", Tags: []string{"旅行", "摄影"}},
{ID: "6", Title: "机器学习", Category: "tech", Tags: []string{"AI", "python"}},
}
userTags := []string{"go", "编程", "并发"}
// 构建推荐引擎(面向接口组装,策略可随时替换)
engine := NewEngine(
TagBasedRecommender{}, // 主策略:标签匹配
RandomRecommender{}, // 降级策略:随机推荐
)
// 添加过滤器
engine.AddFilter(CategoryFilter{
Allowed: map[string]bool{"tech": true},
})
// 获取推荐
fmt.Println("=== 推荐结果 ===")
results := engine.GetRecommendations(userTags, items, 5)
for _, item := range results {
fmt.Printf(" [%.1f] %s (%s)\n", item.Score, item.Title, item.Category)
}
}31. type 定义一种新类型
package main
import "fmt"
// ==================== type 定义新类型 ====================
// 基于基础类型定义新类型
type UserID int64
type Email string
type Celsius float64
type Fahrenheit float64
// 新类型与原类型是不同类型!
func (c Celsius) ToFahrenheit() Fahrenheit {
return Fahrenheit(c*9/5 + 32)
}
func (f Fahrenheit) ToCelsius() Celsius {
return Celsius((f - 32) * 5 / 9)
}
// ==================== type 别名 vs 新类型 ====================
type MyInt int // 新类型:MyInt 和 int 是不同类型
type YourInt = int // 别名:YourInt 就是 int(完全相同的类型)
// ==================== 实际应用:增加类型安全 ====================
type Meter float64
type Kilogram float64
// 这样就不会把米和千克搞混
func calculateBMI(height Meter, weight Kilogram) float64 {
return float64(weight) / (float64(height) * float64(height))
}
func main() {
// 类型安全
var uid UserID = 12345
// var n int = uid // ❌ 编译错误:不能直接赋值
var n int = int(uid) // ✅ 需要显式转换
fmt.Println(uid, n)
// 温度转换
boiling := Celsius(100)
fmt.Printf("%.1f°C = %.1f°F\n", boiling, boiling.ToFahrenheit())
// BMI 计算
bmi := calculateBMI(Meter(1.75), Kilogram(70))
fmt.Printf("BMI: %.1f\n", bmi)
// 别名 vs 新类型
var a MyInt = 10
var b YourInt = 20
// var c int = a // ❌ MyInt 不是 int
var d int = b // ✅ YourInt 就是 int
fmt.Println(a, d)
}32. 函数类型
package main
import (
"fmt"
"strings"
)
// ==================== 函数类型定义 ====================
type MathFunc func(int, int) int
type Predicate func(string) bool
type Transformer func(string) string
type Middleware func(Handler) Handler
type Handler func(request string) string
// ==================== 策略模式 ====================
type Operation struct {
Name string
Fn MathFunc
}
// ==================== 中间件模式 ====================
func loggingMiddleware(next Handler) Handler {
return func(request string) string {
fmt.Printf("[LOG] Request: %s\n", request)
response := next(request)
fmt.Printf("[LOG] Response: %s\n", response)
return response
}
}
func authMiddleware(next Handler) Handler {
return func(request string) string {
if !strings.HasPrefix(request, "AUTH:") {
return "401 Unauthorized"
}
return next(request[5:]) // 去掉 "AUTH:" 前缀
}
}
// ==================== 函数式选项模式(Option Pattern)====================
type Server struct {
host string
port int
timeout int
}
type ServerOption func(*Server)
func WithHost(host string) ServerOption {
return func(s *Server) { s.host = host }
}
func WithPort(port int) ServerOption {
return func(s *Server) { s.port = port }
}
func WithTimeout(timeout int) ServerOption {
return func(s *Server) { s.timeout = timeout }
}
func NewServer(opts ...ServerOption) *Server {
s := &Server{
host: "localhost",
port: 8080,
timeout: 30,
}
for _, opt := range opts {
opt(s)
}
return s
}
func main() {
// 策略模式
operations := []Operation{
{"加法", func(a, b int) int { return a + b }},
{"乘法", func(a, b int) int { return a * b }},
{"最大值", func(a, b int) int {
if a > b { return a }
return b
}},
}
for _, op := range operations {
fmt.Printf("%s: %d\n", op.Name, op.Fn(10, 3))
}
// 中间件链
handler := func(req string) string {
return "Hello, " + req
}
// 从内到外包裹
wrapped := loggingMiddleware(authMiddleware(handler))
fmt.Println(wrapped("AUTH:World"))
fmt.Println(wrapped("World")) // 401 Unauthorized
// 函数式选项
server := NewServer(
WithHost("0.0.0.0"),
WithPort(9090),
)
fmt.Printf("Server: %+v\n", server)
}33. 用函数替代接口
package main
import "fmt"
// ==================== 传统接口方式 ====================
type Validator interface {
Validate(s string) bool
}
// 为了实现接口需要创建结构体
type MinLengthValidator struct {
Min int
}
func (v MinLengthValidator) Validate(s string) bool {
return len(s) >= v.Min
}
func validateWithInterface(s string, v Validator) bool {
return v.Validate(s)
}
// ==================== 函数类型替代接口 ====================
// 当接口只有一个方法时,用函数类型更简洁
type ValidateFunc func(string) bool
func validateWithFunc(s string, fn ValidateFunc) bool {
return fn(s)
}
// Go 标准库的经典案例:http.HandlerFunc
// type Handler interface {
// ServeHTTP(ResponseWriter, *Request)
// }
// type HandlerFunc func(ResponseWriter, *Request)
// func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
// f(w, r) // HandlerFunc 让函数也能满足 Handler 接口
// }
// ==================== 函数类型实现接口的桥梁 ====================
// 让函数类型也满足接口(类似 http.HandlerFunc)
type ValidatorFunc func(string) bool
// ValidatorFunc 实现了 Validator 接口
func (f ValidatorFunc) Validate(s string) bool {
return f(s)
}
func main() {
// 传统方式:需要定义结构体
ok1 := validateWithInterface("hello", MinLengthValidator{Min: 3})
// 函数方式:直接传 lambda,更简洁
ok2 := validateWithFunc("hello", func(s string) bool {
return len(s) >= 3
})
// 桥梁模式:函数也能满足接口
var v Validator = ValidatorFunc(func(s string) bool {
return len(s) >= 3
})
ok3 := v.Validate("hello")
fmt.Println(ok1, ok2, ok3) // true true true
}
// 选择建议:
// - 接口只有 1 个方法 → 优先考虑函数类型
// - 接口有多个方法 → 必须用接口
// - 需要在接口和函数之间桥接 → 用 HandlerFunc 模式34. 空接口本质及其使用方法
package main
import "fmt"
// ==================== 空接口 interface{} / any ====================
// any 是 interface{} 的别名(Go 1.18+)
// 空接口没有方法,所以所有类型都实现了空接口
//
// 内部结构(eface):
// type eface struct {
// _type *_type // 类型信息指针
// data unsafe.Pointer // 数据指针
// }
// 占 16 字节(两个指针)
func main() {
// 空接口可以存放任何值
var a any = 42
var b any = "hello"
var c any = []int{1, 2, 3}
fmt.Println(a, b, c)
// ==================== 类型断言 ====================
// 从空接口中取出具体类型
// 方式1:直接断言(失败会 panic)
str := b.(string)
fmt.Println("字符串:", str)
// 方式2:安全断言(推荐)
if num, ok := a.(int); ok {
fmt.Println("整数:", num)
}
// 方式3:type switch
printType := func(v any) {
switch val := v.(type) {
case int:
fmt.Printf("int: %d\n", val)
case string:
fmt.Printf("string: %s\n", val)
case []int:
fmt.Printf("[]int: %v\n", val)
default:
fmt.Printf("unknown: %T\n", val)
}
}
printType(a) // int: 42
printType(b) // string: hello
printType(c) // []int: [1 2 3]
// ==================== 空接口的常见用途 ====================
// 1. 通用容器
config := map[string]any{
"name": "myapp",
"port": 8080,
"debug": true,
"tags": []string{"web", "api"},
}
fmt.Println(config)
// 2. 函数接受任意参数(类似 fmt.Println)
// func Println(a ...any) (n int, err error)
// 3. JSON 解析
// var data any
// json.Unmarshal(jsonBytes, &data)
}
// ⚠️ 空接口使用原则:
// 1. 能用具体类型就用具体类型
// 2. 能用有方法的接口就用有方法的接口
// 3. 空接口是最后手段(牺牲了类型安全)
// 4. Go 1.18+ 有泛型后,很多 any 场景可以用泛型替代35. 空结构体的本质及其应用场景
package main
import (
"fmt"
"unsafe"
)
func main() {
// ==================== 空结构体的本质 ====================
// struct{} 不占内存(size = 0)
// 所有空结构体变量的地址都指向同一个位置:runtime.zerobase
var a struct{}
var b struct{}
fmt.Println(unsafe.Sizeof(a)) // 0
fmt.Printf("a 地址: %p\n", &a)
fmt.Printf("b 地址: %p\n", &b) // 和 a 相同!
// ==================== 应用1:实现 Set ====================
// Go 没有内置 Set,用 map[T]struct{} 模拟
set := make(map[string]struct{})
set["apple"] = struct{}{}
set["banana"] = struct{}{}
set["apple"] = struct{}{} // 重复添加无效
// 检查是否存在
if _, exists := set["apple"]; exists {
fmt.Println("apple 在集合中")
}
fmt.Println("集合大小:", len(set)) // 2
// 使用 map[string]bool 也可以,但每个 value 多占 1 byte
// 大数据量时 struct{} 更省内存
// ==================== 应用2:信号 channel ====================
// 只传递信号,不传递数据
done := make(chan struct{})
go func() {
fmt.Println("工作中...")
close(done) // 用 close 发送信号
}()
<-done // 等待信号
fmt.Println("完成")
// ==================== 应用3:方法接收器占位 ====================
// 不需要任何数据,只需要方法
type Converter struct{}
// ==================== 应用4:嵌入接口实现 ====================
// 用于测试桩,只实现需要的方法
}36. error 接口及自定义 Error
package main
import (
"errors"
"fmt"
"time"
)
// ==================== error 接口 ====================
// type error interface {
// Error() string
// }
// 任何实现了 Error() string 方法的类型都是 error
// ==================== 自定义错误类型 ====================
// 简单的自定义错误
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on field '%s': %s", e.Field, e.Message)
}
// 带错误码的错误
type APIError struct {
Code int
Message string
Detail string
}
func (e *APIError) Error() string {
return fmt.Sprintf("[%d] %s: %s", e.Code, e.Message, e.Detail)
}
// 带重试信息的错误
type RetryableError struct {
Err error
RetryAfter time.Duration
}
func (e *RetryableError) Error() string {
return fmt.Sprintf("%s (retry after %s)", e.Err.Error(), e.RetryAfter)
}
func (e *RetryableError) Unwrap() error {
return e.Err // 支持 errors.Is / errors.As
}
// ==================== 哨兵错误(Sentinel Error)====================
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
ErrForbidden = errors.New("forbidden")
)
// ==================== 使用示例 ====================
func findUser(id int) (*struct{ Name string }, error) {
if id <= 0 {
return nil, &ValidationError{
Field: "id",
Message: "must be positive",
}
}
if id == 404 {
return nil, fmt.Errorf("findUser: %w", ErrNotFound)
}
return &struct{ Name string }{"Alice"}, nil
}
func main() {
// 基本错误处理
_, err := findUser(-1)
if err != nil {
fmt.Println("错误:", err)
// 判断具体错误类型
var validErr *ValidationError
if errors.As(err, &validErr) {
fmt.Printf("字段: %s, 原因: %s\n", validErr.Field, validErr.Message)
}
}
// 哨兵错误判断
_, err = findUser(404)
if errors.Is(err, ErrNotFound) {
fmt.Println("用户不存在")
}
// 可重试错误
retryErr := &RetryableError{
Err: ErrNotFound,
RetryAfter: 5 * time.Second,
}
fmt.Println(retryErr)
fmt.Println("是 NotFound?", errors.Is(retryErr, ErrNotFound)) // true
}37. 何时抛出 error,如何处理 error
package main
import (
"errors"
"fmt"
"log"
"os"
)
// ==================== 何时返回 error ====================
//
// 返回 error 的情况:
// 1. I/O 操作(文件、网络、数据库)
// 2. 输入验证失败
// 3. 资源不足(内存、连接池)
// 4. 依赖服务调用失败
// 5. 业务规则不满足
//
// 使用 panic 的情况(极少):
// 1. 程序初始化失败(配置加载失败等)
// 2. 编程错误,不应该发生的情况(断言)
// ==================== 错误处理模式 ====================
// 模式1:直接返回
func readFile(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err // 直接返回
}
return data, nil
}
// 模式2:包装错误(添加上下文)
func loadConfig(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("loadConfig(%s): %w", path, err)
// %w 包装错误,保留原始错误链
}
return data, nil
}
// 模式3:降级处理
func getPort() int {
portStr := os.Getenv("PORT")
if portStr == "" {
return 8080 // 默认值降级
}
// ...
return 8080
}
// 模式4:重试
func fetchWithRetry(url string, maxRetries int) (string, error) {
var lastErr error
for i := 0; i < maxRetries; i++ {
// result, err := httpGet(url)
err := fmt.Errorf("attempt %d failed", i+1)
if err == nil {
return "ok", nil
}
lastErr = err
// time.Sleep(time.Duration(i+1) * time.Second) // 退避
}
return "", fmt.Errorf("all %d retries failed: %w", maxRetries, lastErr)
}
// 模式5:错误分类处理
func handleError(err error) {
if err == nil {
return
}
// 按错误类型分类处理
var pathErr *os.PathError
if errors.As(err, &pathErr) {
log.Printf("路径错误 [%s]: %v\n", pathErr.Path, pathErr.Err)
return
}
if errors.Is(err, os.ErrNotExist) {
log.Println("文件不存在,使用默认配置")
return
}
if errors.Is(err, os.ErrPermission) {
log.Fatal("权限不足,程序退出")
}
// 未知错误
log.Printf("未知错误: %v\n", err)
}
func main() {
_, err := loadConfig("nonexistent.yml")
if err != nil {
handleError(err)
}
// 不要忽略错误!
// f, _ := os.Open("file") // ❌ 忽略错误很危险
// f.Read(...) // 如果 Open 失败,f 是 nil,这里会 panic
}
// ==================== 错误处理最佳实践 ====================
// 1. 错误只处理一次(要么返回,要么处理,不要又返回又 log)
// 2. 使用 %w 包装错误,保留错误链
// 3. 在 error message 中添加有用的上下文信息
// 4. 不要忽略 error(至少记录日志)
// 5. 使用 errors.Is 和 errors.As 检查错误,不要比较字符串
// 6. 在包的边界处定义错误类型/哨兵错误38. error 的追踪,errors.Is 和 errors.As
package main
import (
"errors"
"fmt"
)
// 定义错误
var ErrNotFound = errors.New("not found")
var ErrDatabase = errors.New("database error")
type DetailedError struct {
Code int
Message string
Cause error
}
func (e *DetailedError) Error() string {
return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}
// 实现 Unwrap 方法,支持错误链
func (e *DetailedError) Unwrap() error {
return e.Cause
}
// ==================== 错误包装链 ====================
func queryDB() error {
return ErrNotFound // 原始错误
}
func getUser(id int) error {
err := queryDB()
if err != nil {
// %w 创建错误链
return fmt.Errorf("getUser(%d): %w", id, err)
}
return nil
}
func handleRequest() error {
err := getUser(42)
if err != nil {
return &DetailedError{
Code: 404,
Message: "user not found",
Cause: err, // 嵌套错误
}
}
return nil
}
func main() {
err := handleRequest()
// ==================== errors.Is:判断错误链中是否包含某个错误值 ====================
fmt.Println("=== errors.Is ===")
fmt.Println("Is ErrNotFound?", errors.Is(err, ErrNotFound)) // true
fmt.Println("Is ErrDatabase?", errors.Is(err, ErrDatabase)) // false
// ==================== errors.As:从错误链中提取某个类型的错误 ====================
fmt.Println("\n=== errors.As ===")
var detailedErr *DetailedError
if errors.As(err, &detailedErr) {
fmt.Printf("错误码: %d, 消息: %s\n", detailedErr.Code, detailedErr.Message)
}
// ==================== 错误链展开 ====================
fmt.Println("\n=== 错误链 ===")
e := err
for e != nil {
fmt.Printf(" → %v\n", e)
e = errors.Unwrap(e)
}
// → [404] user not found
// → getUser(42): not found
// → not found
// ==================== 多错误合并(Go 1.20+)====================
fmt.Println("\n=== errors.Join ===")
errs := errors.Join(
fmt.Errorf("error 1"),
fmt.Errorf("error 2"),
ErrNotFound,
)
fmt.Println(errs)
fmt.Println("包含 ErrNotFound?", errors.Is(errs, ErrNotFound)) // true
}39. 综合练习:PKCS7 数据填充算法
💡 PKCS7 Padding PKCS7 用于块加密(如 AES)前的数据填充。将数据填充到块大小的整数倍,填充字节的值等于需要填充的字节数。例如块大小为 8,数据长度为 5,需要填充 3 个字节,每个字节的值为 0x03。
package main
import (
"errors"
"fmt"
)
var (
ErrInvalidBlockSize = errors.New("pkcs7: invalid block size (must be 1-255)")
ErrInvalidPadding = errors.New("pkcs7: invalid padding")
ErrDataEmpty = errors.New("pkcs7: data is empty")
)
// PKCS7Pad 对数据进行 PKCS7 填充
// blockSize: 块大小,取值范围 1-255
func PKCS7Pad(data []byte, blockSize int) ([]byte, error) {
if blockSize < 1 || blockSize > 255 {
return nil, ErrInvalidBlockSize
}
// 计算需要填充的字节数
// 如果数据已经是 blockSize 的整数倍,仍需填充一个完整的块
padding := blockSize - (len(data) % blockSize)
// 创建填充字节切片,每个字节的值 = 填充长度
padBytes := make([]byte, padding)
for i := range padBytes {
padBytes[i] = byte(padding)
}
// 将填充追加到原数据后面
return append(data, padBytes...), nil
}
// PKCS7Unpad 去除 PKCS7 填充
func PKCS7Unpad(data []byte, blockSize int) ([]byte, error) {
if blockSize < 1 || blockSize > 255 {
return nil, ErrInvalidBlockSize
}
if len(data) == 0 {
return nil, ErrDataEmpty
}
if len(data)%blockSize != 0 {
return nil, ErrInvalidPadding
}
// 最后一个字节就是填充长度
padding := int(data[len(data)-1])
// 验证填充
if padding == 0 || padding > blockSize || padding > len(data) {
return nil, ErrInvalidPadding
}
// 验证所有填充字节的值是否一致
for i := len(data) - padding; i < len(data); i++ {
if data[i] != byte(padding) {
return nil, ErrInvalidPadding
}
}
return data[:len(data)-padding], nil
}
func main() {
blockSize := 8
// 测试用例
testCases := []struct {
name string
data []byte
}{
{"空数据", []byte{}},
{"短数据", []byte("Hi")},
{"正好一块", []byte("12345678")},
{"中文", []byte("你好世界")},
}
for _, tc := range testCases {
fmt.Printf("\n=== %s ===\n", tc.name)
fmt.Printf("原始数据: %v (长度: %d)\n", tc.data, len(tc.data))
padded, err := PKCS7Pad(tc.data, blockSize)
if err != nil {
fmt.Println("填充错误:", err)
continue
}
fmt.Printf("填充后: %v (长度: %d)\n", padded, len(padded))
// 展示填充的十六进制
fmt.Printf("十六进制: %x\n", padded)
unpadded, err := PKCS7Unpad(padded, blockSize)
if err != nil {
fmt.Println("去填充错误:", err)
continue
}
fmt.Printf("去填充后: %v (长度: %d)\n", unpadded, len(unpadded))
fmt.Printf("还原文本: %s\n", string(unpadded))
}
// 测试异常情况
fmt.Println("\n=== 异常测试 ===")
_, err := PKCS7Pad([]byte("test"), 0)
fmt.Println("blockSize=0:", err)
_, err = PKCS7Unpad([]byte{1, 2, 3}, 8)
fmt.Println("长度不对齐:", err)
_, err = PKCS7Unpad([]byte{1, 2, 3, 4, 5, 6, 7, 9}, 8)
fmt.Println("填充值无效:", err)
}40. 结构体方法接收值和接收指针的区别
package main
import "fmt"
type Counter struct {
Name string
Count int
}
// 值接收者:方法操作的是副本
func (c Counter) GetCount() int {
return c.Count
}
// 值接收者中修改不影响原始数据
func (c Counter) IncrementWrong() {
c.Count++ // 修改的是副本,原始对象不变!
}
// 指针接收者:方法操作的是原始数据
func (c *Counter) Increment() {
c.Count++ // 修改原始对象
}
func (c *Counter) Reset() {
c.Count = 0
}
func main() {
c := Counter{Name: "clicks", Count: 0}
c.IncrementWrong()
fmt.Println("错误递增后:", c.Count) // 0(未变)
c.Increment()
fmt.Println("正确递增后:", c.Count) // 1
// Go 自动取地址/解引用
p := &c
p.GetCount() // 指针调用值方法 → 自动解引用 (*p).GetCount()
c.Increment() // 值调用指针方法 → 自动取地址 (&c).Increment()
}
// ==================== 如何选择?====================
//
// 使用指针接收者 *T 的情况:
// 1. 方法需要修改接收者的状态
// 2. 结构体很大,避免值拷贝开销
// 3. 该类型的其他方法已经用了指针接收者(保持一致性)
// 4. 类型包含 sync.Mutex 等不能拷贝的字段
//
// 使用值接收者 T 的情况:
// 1. 方法不修改接收者
// 2. 结构体很小(几个基本类型字段)
// 3. 需要值语义(如 time.Time)
//
// ⚠️ 重要区别:接口实现
// - *T 的方法集包含 T 和 *T 的所有方法
// - T 的方法集只包含 T 的方法
// 这意味着:
// 如果接口有指针接收者方法,只有 *T 能满足该接口
type Stringer interface {
String() string
}
type MyType struct{ Name string }
// 指针接收者方法
func (m *MyType) String() string {
return m.Name
}
// var _ Stringer = MyType{} // ❌ 编译错误!
// var _ Stringer = &MyType{} // ✅41. 各种数据类型的零值,以及返回零值的设计原则
package main
import "fmt"
func main() {
// ==================== 各类型的零值 ====================
var (
b bool // false
i int // 0
f float64 // 0
s string // ""(空字符串)
p *int // nil
sl []int // nil
m map[string]int // nil
ch chan int // nil
fn func() // nil
itf interface{} // nil
err error // nil
)
fmt.Printf("bool: %v\n", b) // false
fmt.Printf("int: %v\n", i) // 0
fmt.Printf("float64: %v\n", f) // 0
fmt.Printf("string: %q\n", s) // ""
fmt.Printf("*int: %v\n", p) // <nil>
fmt.Printf("[]int: %v (nil: %v)\n", sl, sl == nil) // [] true
fmt.Printf("map: %v (nil: %v)\n", m, m == nil) // map[] true
fmt.Printf("chan: %v\n", ch) // <nil>
fmt.Printf("func: %v\n", fn) // <nil>
fmt.Printf("interface: %v\n", itf) // <nil>
fmt.Printf("error: %v\n", err) // <nil>
// 结构体的零值:所有字段都是零值
type User struct {
Name string
Age int
Friends []string
}
var u User
fmt.Printf("struct: %+v\n", u) // {Name: Age:0 Friends:[]}
// ==================== 零值可用的设计 ====================
// Go 鼓励零值可用(zero value is useful)
// sync.Mutex 零值就是未锁定的互斥锁,无需初始化
// var mu sync.Mutex // 直接可用
// bytes.Buffer 零值就是空缓冲区
// var buf bytes.Buffer
// buf.WriteString("hello") // 直接可用
// nil slice 可以 append
var ns []int
ns = append(ns, 1, 2, 3) // 无需 make
fmt.Println(ns) // [1 2 3]
// nil map 可以读(返回零值),但不能写
// m["key"] → 0(不 panic)
// m["key"] = 1 → panic!
}
// ==================== 返回零值的设计原则 ====================
//
// 1. 函数在错误情况下应返回零值 + error
// func Find(id int) (User, error) {
// if err != nil {
// return User{}, err // 返回零值
// }
// }
//
// 2. 指针类型在"不存在"的情况下返回 nil
// func FindPtr(id int) (*User, error) {
// if notFound {
// return nil, ErrNotFound
// }
// }
//
// 3. 设计自己的类型时,让零值有意义
// ✅ type Config struct { Timeout time.Duration }
// → 零值 Timeout=0 可以在代码中被检测为"未设置"
// ✅ 在方法中处理零值
// func (c *Config) GetTimeout() time.Duration {
// if c.Timeout == 0 {
// return 30 * time.Second // 默认值
// }
// return c.Timeout
// }42. nil 非 nil
⚠️ Go 最著名的陷阱之一一个接口值可以不为 nil,即使它内部持有的值是 nil。
package main
import "fmt"
type MyError struct {
Message string
}
func (e *MyError) Error() string {
return e.Message
}
// 这个函数有 bug!
func doSomethingBad() error {
var err *MyError = nil // *MyError 类型的 nil
// ... 一些逻辑 ...
return err // 返回的是 interface{type: *MyError, value: nil}
// 不是真正的 nil!
}
// 正确写法
func doSomethingGood() error {
var err *MyError = nil
// ... 一些逻辑 ...
if err != nil {
return err
}
return nil // 显式返回 nil interface
}
func main() {
// ==================== nil 接口 vs 非 nil 接口 ====================
// 接口内部结构:(type, value)
// 只有 type 和 value 都为 nil 时,接口才 == nil
err1 := doSomethingBad()
fmt.Println("err1 == nil?", err1 == nil) // false!!!
// 因为 err1 的内部是 (*MyError, nil),type 不为 nil
err2 := doSomethingGood()
fmt.Println("err2 == nil?", err2 == nil) // true
// err2 的内部是 (nil, nil)
// ==================== 可视化 ====================
var i interface{} = nil
fmt.Printf("nil interface: type=%T, value=%v, isNil=%v\n", i, i, i == nil)
// type=<nil>, value=<nil>, isNil=true
var p *MyError = nil
i = p
fmt.Printf("nil pointer in interface: type=%T, value=%v, isNil=%v\n", i, i, i == nil)
// type=*main.MyError, value=<nil>, isNil=false !!!
// ==================== 如何安全检查 ====================
// 方法1:在返回时确保返回 nil interface
// 方法2:使用 reflect(不推荐,性能差)
// import "reflect"
// isNil := i == nil || reflect.ValueOf(i).IsNil()
}
// 记住这个规则:
// var err error = nil → (nil, nil) → == nil ✅
// var err error = (*MyError)(nil) → (*MyError, nil) → != nil ❌43. 闭包
package main
import "fmt"
// ==================== 什么是闭包 ====================
// 闭包 = 函数 + 它引用的外部变量
// 闭包"捕获"了外部变量的引用(不是值的拷贝)
// 计数器:经典闭包示例
func makeCounter() func() int {
count := 0 // 这个变量被闭包捕获
return func() int {
count++ // 修改的是外部的 count
return count
}
}
// 累加器
func makeAccumulator(initial int) func(int) int {
sum := initial
return func(n int) int {
sum += n
return sum
}
}
// 缓存/记忆化
func memoize(fn func(int) int) func(int) int {
cache := make(map[int]int) // 闭包捕获 cache
return func(n int) int {
if v, ok := cache[n]; ok {
fmt.Printf(" cache hit: fib(%d) = %d\n", n, v)
return v
}
result := fn(n)
cache[n] = result
return result
}
}
func main() {
// 计数器
counter := makeCounter()
fmt.Println(counter()) // 1
fmt.Println(counter()) // 2
fmt.Println(counter()) // 3
// 两个独立的计数器
c1 := makeCounter()
c2 := makeCounter()
fmt.Println(c1(), c1(), c1()) // 1 2 3
fmt.Println(c2(), c2()) // 1 2(独立计数)
// 累加器
acc := makeAccumulator(100)
fmt.Println(acc(10)) // 110
fmt.Println(acc(20)) // 130
// ==================== 闭包的常见陷阱 ====================
// 陷阱:循环变量捕获
funcs := make([]func(), 5)
for i := 0; i < 5; i++ {
funcs[i] = func() {
fmt.Print(i, " ") // 捕获的是变量 i 的引用!
}
}
fmt.Print("陷阱: ")
for _, f := range funcs {
f() // 全部输出 5 5 5 5 5(因为循环结束后 i=5)
}
fmt.Println()
// 解决方法1:参数传递(创建副本)
funcs2 := make([]func(), 5)
for i := 0; i < 5; i++ {
funcs2[i] = func(n int) func() {
return func() { fmt.Print(n, " ") }
}(i) // 立即调用,传入当前 i 的值
}
fmt.Print("修复1: ")
for _, f := range funcs2 {
f() // 0 1 2 3 4
}
fmt.Println()
// 解决方法2:局部变量(Go 1.22+ 自动修复了 for 循环变量)
funcs3 := make([]func(), 5)
for i := 0; i < 5; i++ {
i := i // 创建新的局部变量 i(遮蔽外层 i)
funcs3[i] = func() { fmt.Print(i, " ") }
}
fmt.Print("修复2: ")
for _, f := range funcs3 {
f() // 0 1 2 3 4
}
fmt.Println()
}44. 指针总结
package main
import (
"fmt"
"unsafe"
)
func main() {
// ==================== 指针基础 ====================
x := 42
p := &x // 取地址
fmt.Println(*p) // 解引用:42
*p = 100 // 通过指针修改值
fmt.Println(x) // 100
// 指针的零值是 nil
var np *int
fmt.Println(np == nil) // true
// fmt.Println(*np) // panic: nil pointer dereference
// ==================== new vs & ====================
p1 := new(int) // new 分配零值内存,返回指针
p2 := &struct{ X int }{X: 10} // & 更常用
fmt.Println(*p1) // 0
fmt.Println(p2.X) // 10
// ==================== 指针不能运算 ====================
// Go 不允许指针加减运算(不像 C)
// p++ // ❌ 编译错误
// 但可以用 unsafe.Pointer 做底层操作(非常规手段)
arr := [3]int{10, 20, 30}
ptr := unsafe.Pointer(&arr[0])
// 计算第二个元素的地址
ptr2 := unsafe.Pointer(uintptr(ptr) + unsafe.Sizeof(arr[0]))
fmt.Println(*(*int)(ptr2)) // 20
// ==================== 指针与各数据类型 ====================
// 结构体指针(最常用)
type User struct{ Name string }
u := &User{Name: "Alice"}
u.Name = "Bob" // 自动解引用
// 切片不需要指针(本身已是引用语义)
s := []int{1, 2, 3}
modifySlice(s) // 不需要传 *[]int
fmt.Println(s) // [999 2 3]
// map 不需要指针(本身已是引用语义)
m := map[string]int{"a": 1}
modifyMap(m)
fmt.Println(m) // map[a:999]
// 数组需要指针(值类型,很大时应传指针)
arr2 := [3]int{1, 2, 3}
modifyArray(&arr2)
fmt.Println(arr2) // [999 2 3]
// ==================== 指针的指针 ====================
a := 42
pa := &a
ppa := &pa // **int
fmt.Println(**ppa) // 42
}
func modifySlice(s []int) { s[0] = 999 }
func modifyMap(m map[string]int) { m["a"] = 999 }
func modifyArray(a *[3]int) { a[0] = 999 }
// ==================== 何时用指针?====================
// ✅ 用指针:
// - 需要修改接收者的方法
// - 大结构体的函数参数(避免拷贝)
// - 表示"可选/可空"值(nil = 不存在)
// - sync.Mutex 等不可拷贝的类型
//
// ❌ 不用指针:
// - slice, map, channel, func(自带引用语义)
// - 小结构体(拷贝成本很低)
// - 不需要修改的只读参数
// - 并发安全的不可变值45. 泛型
package main
import (
"cmp"
"fmt"
)
// ==================== 泛型函数 ====================
// 类型约束:comparable(可比较的类型)
func Contains[T comparable](slice []T, target T) bool {
for _, v := range slice {
if v == target {
return true
}
}
return false
}
// 类型约束:cmp.Ordered(可排序的类型)
func Max[T cmp.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
// 多类型参数
func Map[T any, R any](slice []T, fn func(T) R) []R {
result := make([]R, len(slice))
for i, v := range slice {
result[i] = fn(v)
}
return result
}
func Filter[T any](slice []T, predicate func(T) bool) []T {
var result []T
for _, v := range slice {
if predicate(v) {
result = append(result, v)
}
}
return result
}
func Reduce[T any, R any](slice []T, initial R, fn func(R, T) R) R {
result := initial
for _, v := range slice {
result = fn(result, v)
}
return result
}
// ==================== 泛型结构体 ====================
type Pair[T, U any] struct {
First T
Second U
}
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
func (s *Stack[T]) Pop() (T, bool) {
var zero T
if len(s.items) == 0 {
return zero, false
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item, true
}
func (s *Stack[T]) Peek() (T, bool) {
var zero T
if len(s.items) == 0 {
return zero, false
}
return s.items[len(s.items)-1], true
}
func (s *Stack[T]) Len() int {
return len(s.items)
}
// ==================== 自定义类型约束 ====================
type Number interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
~float32 | ~float64
}
func Sum[T Number](nums []T) T {
var total T
for _, n := range nums {
total += n
}
return total
}
func main() {
// 泛型函数
fmt.Println(Contains([]int{1, 2, 3}, 2)) // true
fmt.Println(Contains([]string{"a", "b"}, "c")) // false
fmt.Println(Max(3, 7)) // 7
fmt.Println(Max("apple", "banana")) // banana
// Map / Filter / Reduce
nums := []int{1, 2, 3, 4, 5}
doubled := Map(nums, func(n int) int { return n * 2 })
fmt.Println("Map:", doubled) // [2 4 6 8 10]
evens := Filter(nums, func(n int) bool { return n%2 == 0 })
fmt.Println("Filter:", evens) // [2 4]
sum := Reduce(nums, 0, func(acc, n int) int { return acc + n })
fmt.Println("Reduce:", sum) // 15
// 泛型结构体
stack := &Stack[string]{}
stack.Push("hello")
stack.Push("world")
v, _ := stack.Pop()
fmt.Println("Pop:", v) // world
// Pair
p := Pair[string, int]{"age", 25}
fmt.Println(p) // {age 25}
// 自定义约束
fmt.Println(Sum([]float64{1.1, 2.2, 3.3})) // 6.6
}46. 练习:基于泛型实现 Set
package main
import "fmt"
// ==================== 泛型 Set 实现 ====================
type Set[T comparable] struct {
data map[T]struct{}
}
// 创建新 Set
func NewSet[T comparable](items ...T) *Set[T] {
s := &Set[T]{data: make(map[T]struct{})}
for _, item := range items {
s.Add(item)
}
return s
}
// 添加元素
func (s *Set[T]) Add(item T) {
s.data[item] = struct{}{}
}
// 删除元素
func (s *Set[T]) Remove(item T) {
delete(s.data, item)
}
// 判断是否包含
func (s *Set[T]) Contains(item T) bool {
_, exists := s.data[item]
return exists
}
// 集合大小
func (s *Set[T]) Len() int {
return len(s.data)
}
// 转为切片
func (s *Set[T]) ToSlice() []T {
result := make([]T, 0, len(s.data))
for k := range s.data {
result = append(result, k)
}
return result
}
// 遍历
func (s *Set[T]) ForEach(fn func(T)) {
for k := range s.data {
fn(k)
}
}
// ==================== 集合运算 ====================
// 并集:A ∪ B
func (s *Set[T]) Union(other *Set[T]) *Set[T] {
result := NewSet[T]()
s.ForEach(func(item T) { result.Add(item) })
other.ForEach(func(item T) { result.Add(item) })
return result
}
// 交集:A ∩ B
func (s *Set[T]) Intersection(other *Set[T]) *Set[T] {
result := NewSet[T]()
// 遍历较小的集合以提高效率
smaller, larger := s, other
if s.Len() > other.Len() {
smaller, larger = other, s
}
smaller.ForEach(func(item T) {
if larger.Contains(item) {
result.Add(item)
}
})
return result
}
// 差集:A - B
func (s *Set[T]) Difference(other *Set[T]) *Set[T] {
result := NewSet[T]()
s.ForEach(func(item T) {
if !other.Contains(item) {
result.Add(item)
}
})
return result
}
// 对称差集:A △ B = (A - B) ∪ (B - A)
func (s *Set[T]) SymmetricDifference(other *Set[T]) *Set[T] {
return s.Difference(other).Union(other.Difference(s))
}
// 是否是子集
func (s *Set[T]) IsSubsetOf(other *Set[T]) bool {
if s.Len() > other.Len() {
return false
}
for k := range s.data {
if !other.Contains(k) {
return false
}
}
return true
}
// 实现 fmt.Stringer 接口
func (s *Set[T]) String() string {
return fmt.Sprintf("Set%v", s.ToSlice())
}
func main() {
// 整数 Set
s1 := NewSet(1, 2, 3, 4, 5)
s2 := NewSet(3, 4, 5, 6, 7)
fmt.Println("s1:", s1)
fmt.Println("s2:", s2)
fmt.Println("并集:", s1.Union(s2))
fmt.Println("交集:", s1.Intersection(s2))
fmt.Println("差集 s1-s2:", s1.Difference(s2))
fmt.Println("对称差集:", s1.SymmetricDifference(s2))
fmt.Println("s1 ⊂ s2?", s1.IsSubsetOf(s2))
// 字符串 Set
fruits := NewSet("apple", "banana", "cherry")
fruits.Add("date")
fruits.Remove("banana")
fmt.Println("\n水果集合:", fruits)
fmt.Println("包含 apple?", fruits.Contains("apple"))
fmt.Println("包含 banana?", fruits.Contains("banana"))
}47. 随机数种子
package main
import (
"crypto/rand"
"fmt"
"math/big"
mathrand "math/rand/v2" // Go 1.22+ 推荐使用 v2
)
func main() {
// ==================== math/rand/v2(Go 1.22+)====================
// Go 1.22+ 默认自动使用随机种子,不需要手动设置!
// 不再需要 rand.Seed(time.Now().UnixNano())
// 生成随机整数
fmt.Println("随机 int:", mathrand.IntN(100)) // [0, 100)
fmt.Println("随机 int:", mathrand.IntN(100)) // 每次不同
// 生成随机浮点数
fmt.Println("随机 float:", mathrand.Float64()) // [0.0, 1.0)
// 随机打乱切片
s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
mathrand.Shuffle(len(s), func(i, j int) {
s[i], s[j] = s[j], s[i]
})
fmt.Println("打乱后:", s)
// 如果需要可重复的随机序列(如测试),使用固定种子
src := mathrand.NewPCG(42, 0) // 固定种子
rng := mathrand.New(src)
for i := 0; i < 5; i++ {
fmt.Print(rng.IntN(100), " ") // 每次运行结果相同
}
fmt.Println()
// ==================== crypto/rand(密码学安全随机数)====================
// 用于密码、Token、加密密钥等安全场景
// 生成安全随机整数
n, _ := rand.Int(rand.Reader, big.NewInt(100))
fmt.Println("安全随机数:", n)
// 生成安全随机字节
token := make([]byte, 32)
rand.Read(token)
fmt.Printf("安全 Token: %x\n", token)
}48. 练习:生成随机字符串
package main
import (
"crypto/rand"
"fmt"
"math/big"
mathrand "math/rand/v2"
"strings"
)
// ==================== 方式1:math/rand(快速,非安全)====================
func randomString(length int, charset string) string {
var sb strings.Builder
sb.Grow(length)
for i := 0; i < length; i++ {
idx := mathrand.IntN(len(charset))
sb.WriteByte(charset[idx])
}
return sb.String()
}
// 预定义字符集
const (
CharsetLower = "abcdefghijklmnopqrstuvwxyz"
CharsetUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
CharsetDigit = "0123456789"
CharsetAlpha = CharsetLower + CharsetUpper
CharsetAlphaNum = CharsetAlpha + CharsetDigit
CharsetHex = "0123456789abcdef"
CharsetSpecial = "!@#$%^&*()-_=+[]{}|;:,.<>?"
)
// ==================== 方式2:crypto/rand(安全随机)====================
func secureRandomString(length int, charset string) (string, error) {
var sb strings.Builder
sb.Grow(length)
charsetLen := big.NewInt(int64(len(charset)))
for i := 0; i < length; i++ {
idx, err := rand.Int(rand.Reader, charsetLen)
if err != nil {
return "", err
}
sb.WriteByte(charset[idx.Int64()])
}
return sb.String(), nil
}
// ==================== 方式3:高效位掩码法 ====================
func randomStringMask(length int) string {
const charset = CharsetAlphaNum
const bits = 6 // 6 bits 可表示 0-63(覆盖 62 个字符)
const mask = 1<<bits - 1 // 0b111111 = 63
const maxPerInt = 63 / bits // 一个 int64 能产生几个字符
var sb strings.Builder
sb.Grow(length)
for i, remain := 0, 0; i < length; {
if remain == 0 {
remain = maxPerInt
}
idx := int(mathrand.Int64() & mask)
if idx < len(charset) {
sb.WriteByte(charset[idx])
i++
}
remain--
}
return sb.String()
}
// ==================== 实用函数 ====================
func generatePassword(length int) string {
// 确保至少包含各类字符
password := make([]byte, 0, length)
password = append(password, CharsetLower[mathrand.IntN(len(CharsetLower))])
password = append(password, CharsetUpper[mathrand.IntN(len(CharsetUpper))])
password = append(password, CharsetDigit[mathrand.IntN(len(CharsetDigit))])
password = append(password, CharsetSpecial[mathrand.IntN(len(CharsetSpecial))])
allChars := CharsetAlphaNum + CharsetSpecial
for len(password) < length {
password = append(password, allChars[mathrand.IntN(len(allChars))])
}
// 打乱顺序
mathrand.Shuffle(len(password), func(i, j int) {
password[i], password[j] = password[j], password[i]
})
return string(password)
}
func main() {
// 基本随机字符串
fmt.Println("字母数字:", randomString(16, CharsetAlphaNum))
fmt.Println("纯数字: ", randomString(6, CharsetDigit))
fmt.Println("十六进制:", randomString(32, CharsetHex))
// 安全随机字符串
token, _ := secureRandomString(32, CharsetAlphaNum)
fmt.Println("安全Token:", token)
// 密码生成
fmt.Println("密码: ", generatePassword(16))
// 高效版本
fmt.Println("位掩码法:", randomStringMask(20))
// 批量生成
fmt.Println("\n批量生成 5 个邀请码:")
for i := 0; i < 5; i++ {
code := randomString(8, CharsetUpper+CharsetDigit)
fmt.Printf(" %s-%s\n", code[:4], code[4:])
}
}49. 内存逃逸与垃圾回收
49.1 内存逃逸
package main
import "fmt"
// ==================== 什么是内存逃逸 ====================
// Go 编译器会决定变量分配在栈上还是堆上:
// - 栈:函数结束自动释放,速度快,无需 GC
// - 堆:需要 GC 回收,速度慢
//
// 当编译器发现变量的生命周期超出函数范围,就会"逃逸"到堆上
// 用命令查看逃逸分析:go build -gcflags="-m" main.go
// 案例1:返回局部变量指针 → 逃逸
func newUser(name string) *User {
u := User{Name: name} // u 逃逸到堆上(因为返回了指针)
return &u
}
type User struct {
Name string
}
// 案例2:不返回指针 → 不逃逸
func createUser(name string) User {
u := User{Name: name} // u 在栈上分配
return u // 值拷贝返回
}
// 案例3:interface{} 参数 → 逃逸
func printAnything(v interface{}) {
fmt.Println(v) // v 逃逸(fmt.Println 参数是 interface{})
}
// 案例4:闭包引用 → 逃逸
func makeGreeter() func() string {
name := "Alice" // name 逃逸(被闭包引用,生命周期超出函数)
return func() string {
return "Hello, " + name
}
}
// 案例5:切片扩容 → 可能逃逸
func growSlice() {
s := make([]int, 0)
for i := 0; i < 100; i++ {
s = append(s, i) // 扩容时可能逃逸
}
}
// 案例6:大对象 → 逃逸
func bigObject() {
_ = make([]byte, 1<<16) // 64KB,太大了,逃逸到堆
}
func main() {
// 编译器会自动决定,程序员通常不需要手动管理
// 但了解逃逸可以帮助写出更高效的代码
u1 := newUser("Alice") // 堆分配
u2 := createUser("Bob") // 栈分配(更快)
fmt.Println(u1.Name, u2.Name)
}
// ==================== 减少逃逸的技巧 ====================
// 1. 能返回值就不返回指针(小结构体)
// 2. 预分配切片/map 容量:make([]T, 0, expectedSize)
// 3. 减少 interface{} 参数(考虑泛型替代)
// 4. 使用 sync.Pool 复用大对象
// 5. 避免在循环中创建闭包捕获外部变量49.2 垃圾回收
package main
import (
"fmt"
"runtime"
"runtime/debug"
)
func main() {
// ==================== Go 的 GC:三色标记 + 并发回收 ====================
//
// 三色标记法:
// 白色:未访问的对象(GC 后会被回收)
// 灰色:已访问但子对象未扫描完
// 黑色:已访问且子对象已扫描完(存活)
//
// GC 过程(简化):
// 1. 标记开始(STW 极短)→ 开启写屏障
// 2. 并发标记(与程序同时运行)
// 3. 标记终止(STW 极短)→ 关闭写屏障
// 4. 并发清扫(回收白色对象)
//
// GC 触发条件:
// - 堆内存增长到上次 GC 后的 2 倍(GOGC=100 时)
// - 手动调用 runtime.GC()
// - 超过 2 分钟未 GC
// 查看 GC 统计
var stats runtime.MemStats
runtime.ReadMemStats(&stats)
fmt.Printf("堆内存: %d KB\n", stats.HeapAlloc/1024)
fmt.Printf("GC 次数: %d\n", stats.NumGC)
fmt.Printf("GC 暂停总时长: %d ms\n", stats.PauseTotalNs/1_000_000)
// 手动触发 GC
runtime.GC()
runtime.ReadMemStats(&stats)
fmt.Printf("GC 后堆内存: %d KB\n", stats.HeapAlloc/1024)
// ==================== GC 调优 ====================
// GOGC:控制 GC 频率(默认 100)
// GOGC=100 → 堆增长到 2 倍时触发 GC
// GOGC=200 → 堆增长到 3 倍时触发(GC 频率低,内存用多)
// GOGC=50 → 堆增长到 1.5 倍时触发(GC 频率高,内存用少)
old := debug.SetGCPercent(200)
fmt.Println("旧 GOGC:", old)
// GOMEMLIMIT(Go 1.19+):设置内存软上限
// 比 GOGC 更精确地控制内存使用
debug.SetMemoryLimit(1 << 30) // 1 GB
// ==================== sync.Pool:对象复用 ====================
// 减少 GC 压力的利器
// 详见标准库 sync.Pool
// ==================== 性能提示 ====================
// 1. 运行时加 GODEBUG=gctrace=1 查看每次 GC 的详细信息
// 2. 用 go tool pprof 分析内存分配热点
// 3. 减少小对象分配(如 string 拼接改用 strings.Builder)
// 4. 预分配容量(make([]T, 0, n))
// 5. 复用大对象(sync.Pool)
// 6. 注意意外的内存保持(如切片引用大数组的一小段)
}总结
🎯 学习建议
- 动手实践:每个代码示例都尝试运行和修改
- 理解本质:Go 的核心是值语义和组合,不是 OOP
- 关注工程实践:error 处理、接口设计、并发模式是 Go 的核心竞争力
- 阅读标准库:Go 标准库是最好的学习材料(
io,net/http,sync) - 使用工具:
go vet,golangci-lint,go test -race帮你发现问题
📚 推荐学习资源
| 资源 | 链接 |
|---|---|
| Go 官方教程 | go.dev/tour |
| Effective Go | go.dev/doc/effective_go |
| Go by Example | gobyexample.com |
| The Go Programming Language(书) | gopl.io |
| Go 标准库文档 | pkg.go.dev/std |