标准库核心包:fmt / strings / os / io / time

从 C++ 的 iostream / string / fstream / chrono 到 Go 标准库核心包 一、简述 Go 标准库是"开箱即用"的典范——网络、JSON、加密、压缩、时间、文件全都在标准库里。本笔记聚焦日常开发最高频的 5 个包:fmt(格式化)、strings(字符串)、os(文件/环境)、io(读写抽象)、time(时间)。 核心要点:这些包配合 Go 的"接口即抽象"哲学(io.Reader / io.Writer),形成了统一的数据流处理模型——你写的函数只需要面向接口,就能处理文件、内存、网络任何来源。 二、C++ 对照速查表 概念 C++ Go 关键差异 格式化输出 std::cout << x fmt.Println(x) Go 内置格式动词 格式化字符串 sprintf(buf, "%d", x) fmt.Sprintf("%d", x) 返回 string,无缓冲区 字符串操作 std::string / <cstring> strings 包 函数式,无成员方法 字符串拼接 s1 + s2 strings.Join / strings.Builder 大量拼接用 Builder 文件读取 std::ifstream os.Open + io.ReadAll 接口化 文件写入 std::ofstream os.WriteFile 一键写整个文件 时间获取 std::chrono::system_clock::now() time.Now() 返回 Time 结构体 休眠 std::this_thread::sleep_for time.Sleep 用法相似 计时 std::chrono::duration time.Since(t) 返回 Duration 三、fmt —— 格式化 3.1 输出函数 fmt.Print("a", "b") // 无换行、无空格: ab fmt.Println("a", "b") // 空格分隔 + 换行: a b fmt.Printf("%s %d", "go", 1) // 格式化: go 1 // 返回字符串版本 s := fmt.Sprintf("%s-%d", "v", 3) // "v-3" 3.2 常用格式动词 动词 含义 示例 %v 默认格式(任意类型) fmt.Printf("%v", 42) %+v 结构体带字段名 fmt.Printf("%+v", p) → {Name:tom Age:18} %#v Go 语法表示 fmt.Printf("%#v", p) → main.Person{Name:"tom"} %T 类型 fmt.Printf("%T", 42) → int %d 十进制整数 42 %x 十六进制 2a %f / %.2f 浮点 / 保留两位 3.14 / 3.14 %s 字符串 "hello" %q 带引号字符串 "hello" %t 布尔 true %p 指针地址 0xc0000b2000 3.3 对齐与宽度 fmt.Printf("%-10s|%10s\n", "left", "right") // left | right fmt.Printf("%05d\n", 42) // 00042(前导零) 四、strings —— 字符串处理 Go 的 string 是不可变值类型,所有操作都返回新字符串。 ...

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 日

并发模式(worker pool / 扇入扇出 / pipeline)

C++ 里要自己造轮子的线程池、任务队列、流水线,Go 用 channel 组合出几种固定范式,一次学会受用终生 一、简述 「并发模式」不是语法,而是被反复验证有效的编排范式。Go 里它们几乎全由 channel + goroutine 组合而成:worker pool(工人池)、fan-out(扇出)、fan-in(扇入 / 汇聚)、pipeline(流水线)、限速器(rate limit)、错误传播。 对于 C++ 开发者,这些模式你多半见过、也手动实现过: 模式 C++ 里怎么做的 痛点 线程池 std::thread 数组 + std::queue 任务 + mutex 守卫 线程生命周期、任务派发、锁竞争都要自己管 扇出 把一个大任务拆 N 份发给 N 线程 怎么「等所有分片」+ 汇总 流水线 每个 stage 一个线程 + 队列 + condvar 关停时机难管 限速 令牌桶 / 手写信号量 又要造一个计数器 + 条件变量 Go 里这些平均不到 30 行,且语义全部显式可见。本笔记不讲抽象,直接给 6 个能 go run 的模板。 核心要点:所有模式都建立在「channel 即队列 + 同步」之上。模式 = 固定形状的 channel 拓扑 + 一组约定(谁 close、谁 range、谁 Wait)。 ...

2026 年 10 月 13 日

测试:单元、表格驱动、基准、覆盖率

从 C++ 的 GTest 到 Go testing 标准库:约定优于配置,表格驱动是 Go 的灵魂测试法 一、简述 Go 把测试框架内置为标准库 testing,零配置、约定驱动:文件以 _test.go 结尾,函数名 TestXxx(t *testing.T) 即被测对象。不需要 GTest 那样的宏和注册表,go test 一条命令跑全部测试。 C++ 开发者上手 GTest 要经历「链接 gtest、写 TEST 宏、配 CMake、起 runner」;Go 则是「建 _test.go、写 func TestXxx(t *testing.T)、go test」。**表格驱动测试(table-driven tests)**是 Go 社区最推崇的测试形态——本质是把断言与数据分离,让测试用例变成一张表。 核心要点:testing 包只给了「基础骨架」(t 的各种方法),断言与 mock 不内置——这是 Go 的极简哲学。但配合表格驱动 + 子测试 + 基准 + 覆盖率,能覆盖绝大多数项目需求。 二、C++ 对照速查表 概念 C++ Go 关键差异 测试框架 GoogleTest / Catch2(第三方) testing 标准库 零依赖、零配置,go test 即跑 测试文件 test_xxx.cpp xxx_test.go 命名约定即可被发现 测试用例 TEST(Suite, Name) func TestXxx(t *testing.T) 编译期自动收集,无宏无注册 断言 EXPECT_EQ / ASSERT_EQ t.Errorf / t.Fatal Go 里就是普通方法调用 致命 vs 非致命 ASSERT(终止)vs EXPECT(继续) t.Fatal(终止)vs t.Errorf(继续) 语义一一对应 子测试 SCOPED_TRACE 近似 t.Run("name", ...) 自带层级输出、可独立筛选运行 表格驱动 GTEST 参数化 P 复杂 切片 + for 循环 语言特性天然支持,无需框架 基准测试 Google Benchmark BenchmarkXxx(b *testing.B) + b.N benchmark 内建 覆盖率 -fprofile-arcs + gcov/lcov go test -cover 一条命令带 HTML 输出 数据竞态检测 TSan(需编译期开启) go test -race 运行时内建,跨平台 三、逐主题详解 3.1 单元测试最小形态 在 calc.go 旁边放 calc_test.go: ...

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 日

常用工具 gofmt/vet/doc/交叉编译

从 clang-format/clang-tidy/doxygen 到 Go 内建工具链:规范靠工具强制,而非风格文档 一、简述 Go 的工具链是「一个 go 命令 + 若干子命令」的整体。gofmt 统一代码格式、go vet 做静态检查、go doc 生成/查看文档、go build 配合环境变量实现交叉编译。与 C++ 需要 clang-format + clang-tidy + doxygen + 一堆脚本不同,Go 把规范内置——gofmt 的格式无人争论,因为唯一标准。 核心要点:gofmt 从 Go 项目诞生起就存在,go fmt 只是 go 工具链对它的子命令封装,两者格式化结果一致;真正的工程价值是让「风格讨论」从代码评审里消失——C++ 团队为空格和括号开过的会,在 Go 里不存在。 二、C++ 对照速查表 概念 C++ Go 关键差异 代码格式化 clang-format(需 .clang-format 配置) gofmt / go fmt 零配置,官方唯一风格 静态检查 clang-tidy / cppcheck go vet 内建,覆盖导入/函数调用等 文档生成 doxygen(注释标记 + 配置) go doc + godoc 注释即文档,无需单独构建 文档注释风格 ///、块注释 + doxygen 指令 成对注释(块首选) // 名称 描述 约定,导出符号必须注释 交叉编译 每平台一套交叉工具链/CI GOOS=linux GOARCH=amd64 go build 一条命令 + 环境变量 静态链接 -static / 静态库手动组合 CGO_ENABLED=0 默认即可产纯静态二进制 精简体积 -s strip 符号 -ldflags "-s -w" 编译期传递 linker 参数 依赖清理 CMake/vcpkg 手工重配 go mod tidy 与工具链一体 三、逐主题详解 3.1 gofmt / go fmt:官方格式 # 查看差异(不修改) gofmt -l . # 列出格式不达标的文件 gofmt -d file.go # 输出 diff 视角的差异 # 覆盖写文件 gofmt -w file.go go fmt ./... # 等价于 gofmt -w,作用于当前包全部 .go # gofmt 不影响语义:只重排缩进、对齐、换行、导入块 关键规则(你不需要背,工具会改): ...

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 日

错误处理:error / panic / recover

从 C++ 的 try/catch/throw 到 Go 的显式错误返回值、panic 与 defer+recover 一、简述 Go 的错误处理哲学和 C++ 完全相反:不抛异常,而是把错误当作普通返回值显式传递。函数签名里直接写明"可能返回错误",调用方必须处理。这套设计的核心目标:错误路径在代码里一目了然,绝不静默吞掉。 Go 提供三层错误机制: 机制 用途 类似 C++ error 返回值 可预期、应处理的错误(文件不存在、网络失败…) 返回码 / std::error_code panic 不可恢复的严重错误(越界、空指针…) throw recover 在 defer 中捕获 panic catch 核心要点:能用 error 就不要 panic。panic 是"程序无法继续"的最后手段,recover 只用于极少数场景(如防止 goroutine 崩溃拖垮整个程序)。 二、C++ 对照速查表 概念 C++ Go 关键差异 抛出错误 throw std::runtime_error("...") return errors.New("...") Go 是返回值,不是跳转 捕获错误 try { } catch (const std::exception& e) if err != nil { ... } Go 用 if 判断 错误类型 异常类层级 error 接口 Go 只有一个 error 接口 清理资源 RAII / 析构 defer 函数级,非作用域级 致命错误 std::terminate panic 都是不可恢复 兜底捕获 catch (...) defer func() { recover() }() recover 必须在 defer 中 错误包装 异常嵌套 fmt.Errorf("%w", err) %w 支持解包判断 三、error 接口 Go 的 error 是一个预定义接口: ...

2026 年 10 月 13 日

反射(reflection)

C++ 的 RTTI 只能拿到 typeid/dynamic_cast 少许信息;Go 的 reflect 能在运行时查看、遍历、修改任意类型与值 一、简述 反射(reflection):程序在运行时检查自身的类型与结构,并据此做动态操作。Go 里就是 reflect 包:给你 reflect.Type(类型信息)和 reflect.Value(一段具体值),能动态读字段、读 tag、调方法、甚至修改可设置的值。序列化库(encoding/json)、ORM、配置解析、测试断言统统建立在这套 API 之上。但反射有显著的性能代价与脆弱性——本章教你怎么用、什么时候值得用。 核心要点:反射的入口是 reflect.TypeOf / reflect.ValueOf,一切操作都围着这两个对象转;可设置性(CanSet) 是反射修值的前提(变量要可寻址);三大定律是官方给的使用边界。对 C++ 来说,typeid/dynamic_cast 只能做"类型识别",Go 反射还能"改值、建新值、动态调用",范围大得多,代价也大得多。 二、C++ 对照速查表 概念 C++ Go 关键差异 类型识别 typeid(x).name() reflect.TypeOf(x) C++ 只给名字,Go 给结构、字段、方法全图 运行时类型转换 dynamic_cast<T>(须多态) 首选类型断言 x.(T);reflect 侧有 Type.AssignableTo / ConvertibleTo / Implements 做判断、Value.Convert 做转换 Go 反射不要求继承,直接看类型;reflect 包没有 AssertableTo 方法 遍历字段 boost::pfr/手写 reflect.Value.NumField/Field Go 原生支持任意结构体遍历 读字段 tag 无标准机制 reflect.StructTag.Get Go 的 struct tag 是反射的主要消费场景 动态改值 *T + reinterpret_cast 危险操作 reflect.Value.Set* Go 有 CanSet 检查,越界即 panic 动态调用方法 std::invoke(C++17,仍要具体类型) reflect.Value.Method(i).Call(...) Go 无需编译期函数指针,纯运行时 创建新实例 T{} 写死 / 工厂模式 reflect.New(T) 反射可"凭空造值",C++ 做不到通用 性能 零开销(编译期类型) 每次反射调用开销 ~几百 ns 起 Go 反射充满动态分派与检查 应用场景 typeid 用于调试/序列化 序列化/ORM/测试/DI 反射生态在 Go 里是日常 类型断言 C++ 无 x.(T) 是内置语法 注意反射 TypeOf + 断言可以互相换算 三、逐主题详解 3.1 反射两大入口:Type 与 Value package main import ( "fmt" "reflect" ) type Person struct { Name string `json:"name"` Age int `json:"age"` } func main() { p := Person{Name: "小甲", Age: 18} t := reflect.TypeOf(p) // 类型信息 v := reflect.ValueOf(p) // 值信息 fmt.Println("Type:", t) // main.Person fmt.Println("Kind:", t.Kind()) // struct fmt.Println("Value:", v) // {小甲 18} fmt.Println("TypeOf(v):", v.Type()) // main.Person } reflect.Type:描述"类型是什么"——包名、名称、Kind、字段列表、方法集、是否可比较等。URI:t.NumField()、t.Field(i)、t.Method(i)。 reflect.Value:描述"这段值是什么"——包含了底层值,可读可设(受 CanSet 约束)、可调方法、可转 interface。 Kind():底层类别(Int、String、Slice、Struct、Map 等 26 种)。Type 是具体类型(main.Person),Kind 是粗类别(struct)——C++ 里没有直接对应,最接近 typeid 得不到 Kind;Go 里 Kind 用于判断"数据结构形状"。 3.2 从 Value 取回具体值:Interface() v := reflect.ValueOf(p) // 把 Value 还原成 interface{} i := v.Interface() // 再断言回具体类型 p2 := i.(Person) fmt.Println(p2.Name) // 小甲 老代码里 v.Interface() + 类型断言是组合拳;Go 1.x 有 v.CanInterface() 检查(未导出字段、flag 受限时不可 Interface())。 原则:反射只是"绕到类型系统后面看一眼",看完一定要回到具体类型继续干活。 3.3 对 struct 字段遍历与 tag 读取(json 的底层原理) func dumpStruct(s any) { v := reflect.ValueOf(s) t := v.Type() fmt.Printf("类型: %s\n", t) for i := 0; i < v.NumField(); i++ { field := t.Field(i) // reflect.StructField val := v.Field(i) // reflect.Value tag := field.Tag.Get("json") // 读 tag fmt.Printf(" 字段 %s kind=%v 值=%v tag=%q\n", field.Name, field.Type.Kind(), val.Interface(), tag) } } dumpStruct(Person{Name: "张三", Age: 22}) // 输出: // 类型: main.Person // 字段 Name kind=string 值=张三 tag="name" // 字段 Age kind=int 值=22 tag="age" 这就是 encoding/json 的底层机制:遍历 struct 字段 → 看 json:"xxx" tag 决定键名/跳过 → 按字段类型编码/解码。 StructTag.Get("json") 可以返回 name,omitempty 这样的组合串(Lookup 可区分"有但空"和"没有")。 字段可以是导出(大写)或未导出(小写);未导出字段反射读值会 panic,写值 CanSet=false。 3.4 可设置性(CanSet)与修改值 反射改值前提:值必须是可以寻址的(addressable)。reflect.ValueOf(p) 拿到的是拷贝的 p——改它改不到原对象。 ...

2026 年 10 月 13 日

泛型 generics

从 C++ 的 template 到 Go 1.18+ 的类型参数:更克制的泛型设计 一、简述 Go 1.18 正式引入泛型(类型参数 Type Parameters)。与 C++ 模板相比,Go 泛型刻意保持简单:没有特化、偏特化、模板元编程、运算符重载,只有"类型参数 + 约束(constraint)“两个核心概念。 核心要点:Go 泛型的定位是解决"写一遍、多个类型用"的容器与工具函数问题,不是用来做编译期计算的。能用接口解决的场景,优先接口;泛型是接口的补充而非替代。 二、C++ 对照速查表 概念 C++ Go 关键差异 声明 template <typename T> func F[T any](...) Go 用 [T] 方括号 类型约束 std::is_integral_v<T> / requires T int | float64 或 constraints.Ordered Go 用类型集(union) 特化 template<> struct X<int> ❌ 不支持 Go 没有特化 偏特化 template <typename T> struct X<T*> ❌ 不支持 Go 没有偏特化 默认参数 template <typename T = int> ❌ 不支持 无默认类型参数 泛型类 std::vector<T> type Vec[T any] []T 泛型类型 泛型函数 T max(T a, T b) func Max[T constraints.Ordered](a, b T) T 语法不同 编译期计算 模板元编程 / constexpr ❌ 无 Go 泛型是运行时类型,非代码生成 运算符重载 可对类型重载 ❌ 不支持 泛型内不能直接用 + 除非约束允许 三、泛型函数 3.1 基本语法 // [T any] 声明类型参数;T 在参数和返回值中可用 func Min[T any](a, b T) T { // ❌ 编译错误:any 没有约束,不能比较大小 // if a < b { return a } return b } 问题:any 意味着"任意类型”,编译器不知道 T 是否支持 <。必须用约束声明"T 是支持比较的有序类型": ...

2026 年 10 月 13 日