encoding/json 与配置文件

从 C++ 的 nlohmann/json 到 Go 的 encoding/json:结构体标签驱动的序列化 一、简述 Go 的 encoding/json 是标准库内置的 JSON 序列化/反序列化包。与 C++ 需要第三方库(nlohmann/json、rapidjson)不同,Go 通过**结构体字段标签(struct tag)**声明式地描述 JSON 映射关系,json.Marshal / json.Unmarshal 一键完成转换。 核心要点:Go 的 JSON 哲学是"结构体即 schema"——先用结构体定义数据形状,标签控制字段名、可选性、忽略规则。这也让配置文件读写(JSON/YAML)变得极其自然。 二、C++ 对照速查表 概念 C++ Go 关键差异 JSON 库 nlohmann/json 等第三方 encoding/json 标准库 开箱即用 对象映射 json j = json::parse(s) json.Unmarshal(data, &v) 需要目标结构体 序列化 j.dump() json.Marshal(v) 返回 []byte + error 字段改名 手动构造 struct tag `json:"name"` 声明式 忽略字段 手动排除 `json:"-"` 标签控制 可选字段 无 omitempty 零值不输出 动态 JSON json::object() / 数组 map[string]any / []any 用 any 容器 配置文件 Boost.PropertyTree / YAML 库 读 JSON 或第三方 YAML(gopkg.in/yaml.v3) JSON 为主 三、序列化与反序列化 3.1 基本用法 import ( "encoding/json" "fmt" ) type Person struct { Name string Age int } func main() { p := Person{Name: "Tom", Age: 30} // 序列化:结构体 → JSON data, err := json.Marshal(p) if err != nil { panic(err) } fmt.Println(string(data)) // {"Name":"Tom","Age":30} // 反序列化:JSON → 结构体 var p2 Person err = json.Unmarshal(data, &p2) if err != nil { panic(err) } fmt.Printf("%+v\n", p2) // {Name:Tom Age:30} } 3.2 结构体标签(关键) type Config struct { Server string `json:"server"` // 字段名映射为 server Port int `json:"port"` // 默认 0 Debug bool `json:"debug,omitempty"` // 零值时省略 Secret string `json:"-"` // 永远不参与 JSON Tags []string `json:"tags,omitempty"` // 空切片省略 } c := Config{Server: "localhost", Secret: "不输出"} data, _ := json.Marshal(c) fmt.Println(string(data)) // {"server":"localhost","port":0}(debug/tags 因 omitempty 省略,port 没有 omitempty 所以零值照样输出,secret 被 json:"-" 忽略) 标签规则: ...

2026 年 10 月 13 日

标准库核心包: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 日

错误处理: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 日

泛型 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 日

接口 interface

从 C++ 的虚函数/抽象类到 Go 的接口:鸭子类型、隐式实现与组合式设计 一、简述 Go 的 interface 是一组方法签名的集合,它定义了类型"能做什么",而不是"是什么"。与 C++ 的抽象类最大的不同:Go 接口是隐式实现——一个类型只要实现了接口里的所有方法,就自动满足该接口,不需要显式声明 implements。 核心要点:Go 接口让"面向接口编程"从设计原则变成了语言级强制。它催生了组合优于继承、依赖注入、mock 测试等一整套惯用法。 二、C++ 对照速查表 概念 C++ Go 关键差异 抽象接口 class Shape { virtual double area() = 0; } type Shape interface { Area() float64 } Go 只有方法签名,没有数据成员 实现 class Circle : public Shape { ... } func (c Circle) Area() float64 { ... } 隐式实现,无需继承声明 多态 虚函数表 vtable + 指针/引用 接口值(类型 + 数据指针) 运行时动态分派,机制不同 空接口 void* interface{} / any 表示任意类型,但不建议滥用 接口嵌套 多重继承(菱形问题) 接口嵌入接口 无菱形问题 类型断言 dynamic_cast<T*> v, ok := x.(T) Go 更安全:带 ok 判断 判断实现 typeid / 编译期检查 编译期自动检查 只要方法对上即可 三、接口的定义与实现 3.1 定义接口 // 定义接口:只声明方法,不实现 type Shape interface { Area() float64 Perimeter() float64 } 3.2 隐式实现 // Circle 不需要写 "implements Shape",只要方法对上就自动满足 type Circle struct { Radius float64 } func (c Circle) Area() float64 { return 3.14159 * c.Radius * c.Radius } func (c Circle) Perimeter() float64 { return 2 * 3.14159 * c.Radius } // Rectangle 同样自动实现 Shape 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) } 3.3 使用接口 func printShape(s Shape) { fmt.Printf("面积=%.2f, 周长=%.2f\n", s.Area(), s.Perimeter()) } func main() { c := Circle{Radius: 1.0} r := Rectangle{Width: 2, Height: 3} printShape(c) // 传入接口参数,自动多态 printShape(r) // 接口变量可以存任意实现类型 var s Shape = c fmt.Println(s.Area()) } 对照 C++:C++ 必须 class Circle : public Shape 显式继承;Go 只要方法签名对上就行,实现与接口完全解耦——甚至可以给别人的类型补上方法让它满足你的接口。 ...

2026 年 10 月 13 日

类型断言与 type-switch

从 C++ 的 dynamic_cast / typeid 到 Go 的类型断言、类型开关与反射初探 一、简述 接口变量在运行时存储了"动态类型"。当我们需要把接口值还原成具体类型、或按不同类型走不同逻辑时,就用类型断言(type assertion)和类型开关(type switch)。 核心要点:Go 类型断言是 x.(T) 语法——它是"安全的 dynamic_cast":带 ok 判断时永远不会 panic。类型开关 switch v := x.(type) 则是处理"未知类型集合"最优雅的方式。 二、C++ 对照速查表 概念 C++ Go 关键差异 向下转型 dynamic_cast<T*>(ptr) v, ok := x.(T) Go 更简洁,双返回值 不安全转型 static_cast / reinterpret_cast v := x.(T) Go 会 panic(不推荐) 类型判断 typeid(x) == typeid(T) x.(type)(type switch) 语法不同 类型名称 typeid(x).name() fmt.Sprintf("%T", x) 运行时类型名 反射 RTTI / <typeinfo> reflect 包 用法差异大 三、类型断言基础 3.1 语法与两种形式 var x any = 42 // 形式一:带 ok(推荐,安全) v, ok := x.(int) if ok { fmt.Println("是 int:", v) // 42 } else { fmt.Println("不是 int") } // 形式二:不带 ok(不推荐,失败直接 panic) v2 := x.(int) // ✅ 成功 // v3 := x.(string) // ❌ panic: interface conversion 规则:永远用带 ok 的形式。不带 ok 的写法相当于 C++ 的 static_cast——假设你知道类型,错了就崩。 ...

2026 年 10 月 13 日