Map(哈希表)

Go 的内置关联容器,键值对存储,底层是哈希表 一、概述 map 是 Go 的内置数据结构,用于存储键值对。它等价于 C++ 的 std::unordered_map——基于哈希表实现,不是红黑树(Go 没有 std::map 那样的有序容器)。 核心特性: 引用类型,赋值和传参共享底层数组 键必须支持 == 比较(可哈希) 遍历顺序不确定(哈希表特性) 并发读写会触发 fatal(不是 data race,直接崩程序) 二、C++ 对照速查表 Go C++ 说明 map[K]V 类型声明 std::unordered_map<K,V> 类型声明 声明一个 map 类型 m := map[string]int{} auto m = unordered_map<string,int>(); 字面量初始化 m := make(map[string]int) auto m = unordered_map<string,int>(); make 初始化 m := make(map[string]int, 64) auto m = unordered_map<string,int>(64); 预分配容量 m["key"] = 42 m["key"] = 42; 插入/修改 delete(m, "key") m.erase("key"); 删除键 val, ok := m["key"] auto it = m.find("key"); it != m.end() 查找(双返回值) len(m) m.size() 获取大小 nil map nullptr 的 map 未初始化的 map for k, v := range m for (auto& [k, v] : m) 遍历 无 std::map<K,V> Go 没有有序 map 三、声明与初始化 3.1 字面量初始化(最常用) package main import "fmt" func main() { // 字面量初始化:声明 + 赋值一步到位 ages := map[string]int{ "Alice": 30, "Bob": 25, "Carol": 28, } fmt.Println(ages) // map[Alice:30 Bob:25 Carol:28] // 空 map empty := map[string]int{} fmt.Println(len(empty)) // 0 } 3.2 make 初始化 // make 创建空 map,可选预分配容量(提高性能) scores := make(map[string]int) // 零值容量 scores2 := make(map[string]int, 64) // 预分配 64 个槽位 // 适用场景:已知大致大小,预分配可减少扩容次数 users := make(map[int]string, 1000) 3.3 nil map(未初始化) var m map[string]int // nil map,值为 nil fmt.Println(m) // map[] fmt.Println(len(m)) // 0 — len 对 nil map 返回 0 3.4 C++ 对照 Go C++ ages := map[string]int{"Alice": 30} auto ages = unordered_map<string,int>{{"Alice", 30}}; m := make(map[string]int) auto m = unordered_map<string,int>(); m := make(map[string]int, 64) auto m = unordered_map<string,int>(64); var m map[string]int (nil) unordered_map<string,int>* m = nullptr; 四、增删改查 4.1 增 / 改(赋值) m := map[string]int{} // 不存在的 key → 插入 m["apple"] = 5 m["banana"] = 3 // 已存在的 key → 修改 m["apple"] = 10 fmt.Println(m) // map[apple:10 banana:3] 4.2 查(双返回值 ok 模式) m := map[string]int{"x": 1, "y": 2} // 双返回值:值 + 是否存在(ok 模式) val, ok := m["x"] fmt.Println(val, ok) // 1 true val2, ok2 := m["z"] fmt.Println(val2, ok2) // 0 false(零值,不存在) // C++ 对比: // auto it = m.find("x"); // if (it != m.end()) { /* 找到 */ } 为什么需要 ok 模式? 如果值类型是 int,零值是 0,无法区分"值为 0"和"不存在"。ok 布尔值解决了这个问题。 ...

2026 年 10 月 13 日

pprof 性能调优

C++ 里要弄 4~5 套工具(perf / valgrind / gprof / TSan);Go 把 CPU、内存、goroutine、锁的剖面图都装进了一个 pprof 一、简述 性能调优第一步永远不是"猜",是"量"。Go 的 pprof(profile)机制提供一套统一的剖析框架:CPU、堆内存、分配、goroutine、锁竞争、阻塞延时的剖面数据,既能离线抓(runtime/pprof、go test -cpuprofile),也能在线实时抓(net/http/pprof)。配合 go tool pprof 的 top / list / peek / web 交互和火焰图,你能在几分钟内定位 CPU 热点、内存黑洞、goroutine 泄漏。对用惯 perf/valgrind/gprof 的 C++ 开发者来说,这是一次"多工具合一"的升级。 核心要点:pprof 是采样式剖析:按频率打断程序,记录当时正在执行的函数调用栈,最后统计"每个函数在多少样本里出现"。flat 是"函数自己耗的",cum 是"含它调用的所有子函数"——读懂这两个数字是使用 pprof 的核心。 二、C++ 对照速查表 概念 C++ Go 关键差异 CPU 剖析 perf record/report runtime/pprof + cpu.prof Go 内置采样器,无需 perf_event 权限 堆内存剖析 valgrind --tool=massif / heaptrack memprofile + heap view pprof 同时给 inuse/alloc 两维度 线级热点 perf annotate go tool pprof -list 直接给源码行统计 火焰图 perf script + 第三方 FlameGraph go tool pprof -http(内链火焰图) 一命令内建 死锁/锁竞争 手写 + helgrind mutex/block profile 并发调优必备 调用图 gprof(编译期插桩) pprof 自动采样 Go 无插桩成本,直接采样 内存泄漏排查 valgrind --leak-check goroutine profile + alloc_objects Go 看"分配来源"而非"泄漏点" 运行中抓取 需 perf 权限/SAMPLING net/http/pprof 一键开 线上服务开端口即可 三、逐主题详解 3.1 profile 是什么:采样式剖析 时间轴 ──────────────────────────────────────────► 采样点:每隔 t(如 100 次/秒 = 10ms)触发 SIGPROF 每次记录:当前 goroutine 的完整调用栈(PC → 函数名) ┌────┐┌────┐┌────┐┌────┐ │foo ││bar ││foo ││baz │ 每个采样点保留栈帧 │ main││ foo││ main││ foo │ 最后统计: foo 出现 3/4 次 └────┘└────┘└────┘└────┘ CPU profile:默认每 10ms(100Hz)采样一次正在执行的 G 的栈。只有"在 CPU 上运行"的 G 会被采到——所以纯 IO/阻塞的 goroutine 在 CPU profile 里很低。 heap profile:不是采样,是快照。记录"当前存活对象"的分配点(用于看内存滞留)或"累计分配"(-alloc_space/-alloc_objects,用于看分配大山)。 goroutine profile:每个 goroutine 当前栈(含状态),看泄漏。 mutex/block profile:记录锁等待与阻塞事件(需要 runtime.SetBlockProfileRate / SetMutexProfileFraction 开启)。 3.2 离线抓取:runtime/pprof 与 go test -profile 方式一:go test 直接出谱(最常用) ...

2026 年 10 月 13 日

select 多路复用

一个 goroutine 同时监听多个 channel 的「事件循环」:C++/epoll 监听多个 fd 的那个心智模型,Go 把它内建成了 select 语句 一、简述 select 是 Go 语法层面的多路复用(multiplexing)原语:它让一个 goroutine 同时等待多个 channel 上的收发操作,只要其中一个就绪,就走对应的分支。 对于 C++ 开发者,最贴切的类比是 Linux epoll / poll:你不想为每个 socket 各开一个线程去阻塞读,而是用一个线程同时监听一堆 fd,哪个有事件处理哪个。select 就是「 goroutine 版本的 epoll」——但它更进一步:它还能同时做非阻塞尝试(default)、定时超时(time.After)、动态禁用分支(nil channel)。 核心要点:select 是「组合 channel 为复杂同步逻辑」的胶水。没有 select,一个 goroutine 只能死等一个 channel;有了 select,你才能写超时、写取消、写事件循环、写多路汇流。 二、C++ 对照速查表 概念 C++ Go 关键差异 多路监听 epoll_wait(fds, ...) / poll() select { case <-ch1: ... } Go 是对 channel 通道复用,C++ 是对 fd 复用 同时等两个事件 一个线程一个 fd 各睡一觉 / epoll 一个 goroutine 同时等多个 case select 内建,代码直白得多 随机公平选择 epoll 按就绪顺序返回 多 case 就绪时随机选一个 Go 保证公平:随机而非先后 非阻塞尝试 poll(fd, timeout=0) select { case ...: default: } default = 都不就绪立即走 超时等待 epoll_wait 的 timeout 参数 select + time.After(1s) 超时是普通 channel,不是特殊机制 收发都监听 读 / 写事件分别注册 一个 select 里既有发送又有接收 case 语法上收发对称 监听无限器 服务端 accept 线程循环 for { select { ... } } 事件循环惯用法 特定事件暂时屏蔽 取消注册 fd case 的 channel 置 nil → 永久阻塞 nil channel 妙用 三、逐主题详解 3.1 select 基本语法 package main import ( "fmt" "time" ) func main() { ch1 := make(chan string) ch2 := make(chan string) // 两个 goroutine 各自往不同 channel 发 go func() { time.Sleep(100 * time.Millisecond); ch1 <- "来自 ch1" }() go func() { time.Sleep(300 * time.Millisecond); ch2 <- "来自 ch2" }() // select 会阻塞,直到某个 case 就绪 select { case msg := <-ch1: fmt.Println("收到:", msg) // 大概率先到 ch1(100ms 更快) case msg := <-ch2: fmt.Println("收到:", msg) } } 要点: ...

2026 年 10 月 13 日

sync 包(WaitGroup / Mutex / Once)

C++ 的 std::thread::join、std::mutex、std::once_flag,Go 用 sync 包给了一套轻量且更安全的等价物 一、简述 sync 包是 Go 标准库的同步原语仓库,核心成员: 类型 用途 C++ 对应 sync.WaitGroup 等一批 goroutine 全部结束 std::thread::join(一次等多个) sync.Mutex 互斥锁 std::mutex sync.RWMutex 读写锁 std::shared_mutex sync.Once 只执行一次(懒加载 / 单例) std::once_flag + std::call_once sync.Map 并发安全的 map(特化场景) 手写 lock + std::map / 无直接对应 sync.Cond 条件变量 std::condition_variable 核心要点:Go 的哲学是「尽量用 channel」,但同步原语(锁 / 分组等待 / 只跑一次)仍然有明确位置。与 C++ 的锁相比,Go 的锁不可拷贝、有 defer 配合的极简用法、内置检测工具,心智负担小得多。本笔记重点讲 WaitGroup、Mutex / RWMutex、Once,并简述 errgroup。 二、C++ 对照速查表 概念 C++ Go 关键差异 等待线程结束 t.join()(单个) wg := sync.WaitGroup{}; wg.Add(1); defer wg.Done(); wg.Wait() WaitGroup 一次等一批,Add 要提前 互斥锁 std::mutex m; m.lock(); m.unlock(); var mu sync.Mutex; mu.Lock(); defer mu.Unlock() Go 惯例用 defer 解锁,防漏 锁唯一性 lock 传递没意义 结构体字段还是值?拷贝锁 = 复制已锁状态 → 编译常见错 Go 锁内含 noCopy,拷贝会被提示 只初始化一次 std::once_flag + std::call_once var once sync.Once; once.Do(func(){...}) Go 写进一个 Do,天然幂等 读写锁 std::shared_mutex(C++17) sync.RWMutex + RLock()/RUnlock() 读读并发、写写互斥、写独占 可重入 std::recursive_mutex 才行 Mutex 不可重入,同 goroutine 再锁 = 自锁死 Go 明确拒绝重入,倒逼更好的设计 条件变量 std::condition_variable + predicate 循环 sync.Cond(少见)或直接 channel Go 社区倾向 channel 代替 cond 分组错误传播 手写 std::future 收集异常 errgroup.Group(golang.org/x/sync) Go 有现成库,等全部且带首个错误 三、逐主题详解 3.1 sync.WaitGroup:等一批 goroutine package main import ( "fmt" "sync" "time" ) func worker(id int, wg *sync.WaitGroup) { defer wg.Done() // 结束前把计数器减 1(defer 保证即使中途 panic 也会减,避免死锁) fmt.Printf("worker %d 开始\n", id) time.Sleep(100 * time.Millisecond) // 模拟干活 fmt.Printf("worker %d 结束\n", id) } func main() { var wg sync.WaitGroup for i := 1; i <= 3; i++ { wg.Add(1) // ⚠️ Add 必须在 go 之前!计数器先 +1 go worker(i, &wg) // 注意传指针:WaitGroup 内部要改计数 } wg.Wait() // 阻塞直到所有 Done,相当于「等这一批全部 join」 fmt.Println("所有 worker 都结束了") } WaitGroup 规则: ...

2026 年 10 月 13 日

unsafe 与 cgo 互操作

C++ 的 reinterpret_cast + extern "C" 在 Go 里被装进了两个官方包:unsafe(打破类型/GC 规则)和 cgo(跨语言边界) 一、简述 Go 追求内存安全,但现实里有三类需求绕不开"不安全":解析二进制布局、优化热路径、调用 C 库。Go 给的两把钥匙:unsafe 包的 unsafe.Pointer 允许你绕过类型系统(但仍受 GC 语义约束),cgo 允许你直接调用 C 函数(import "C" + //export 双向互操作)。本章把两者的语义、内存规则、坑、风险一次性讲透——很多线上故障就是从"玩 unsafe 没算清 GC"或"cgo 调用频繁导致线程放大"开始的。 核心要点:unsafe.Pointer 像"元指针",可被 GC 追踪;uintptr 只是整数,GC 不追踪——所以不允许"把指针存成 uintptr 长期持有"。cgo 让你调 C,但每次调用都伴随 goroutine 栈切换与可能的线程膨胀,且 C.CString 分配的内存必须自己 C.free。CGO_ENABLED=0 是纯静态交叉编译的开关,代价是放弃 cgo。 二、C++ 对照速查表 概念 C++ Go 关键差异 无类型转换 reinterpret_cast<T*> unsafe.Pointer 转换 Go 的三步规则更受限,且有 GC 约束 类型大小 sizeof(T) unsafe.Sizeof(x) 一样,但 Go 的 Sizeof 返回 uintptr 对齐要求 alignof(T) unsafe.Alignof(x) 语义相同 成员偏移 offsetof unsafe.Offsetof(s.F) Go 对嵌套/嵌入结构有规则限制 指针算术 p + n(任意) uintptr(p) + n 转回 Go 用 uintptr 防 GC 丢对象(坑) 跨语言符号 extern "C" //export / 声明 Go 还自动处理了 C 的 ABI 与参数传值 头文件 #include // #include <...> 注释块 + import "C" cgo 注释必须紧跟 import C 字符串生命周期 std::string/手管 char* C.CString → C.free Go 里 C 内存在你的掌控(GC 不管) 跨语言回调 函数指针显式传 //export 导出给 C 回调时 cgo 会切换刻意绑的线程 交叉编译 工具链编译 C 代码 CGO_ENABLED=0 纯 Go 纯 Go 无 cgo 才能一路交叉到底 三、逐主题详解 3.1 unsafe 包三件套:Sizeof / Alignof / Offsetof package main import ( "fmt" "unsafe" ) type Header struct { Magic uint16 // 2 字节 Flags byte // 1 字节 Body uint32 // 4 字节(需要对齐) } func main() { h := Header{} fmt.Println("Sizeof:", unsafe.Sizeof(h)) // 8(不是 2+1+4=7,有 padding) fmt.Println("Alignof:", unsafe.Alignof(h)) // 4(最大成员对齐) fmt.Println("Offsetof Magic:", unsafe.Offsetof(h.Magic)) // 0 fmt.Println("Offsetof Flags:", unsafe.Offsetof(h.Flags)) // 2 fmt.Println("Offsetof Body:", unsafe.Offsetof(h.Body)) // 4(3 处 padding 1 字节填在中间) } Offsetof 要求字段是可寻址的(s.F 形式,不能是未导出字段)。 这些原语从 1.0 至今,官方 docs 明确:unsafe 不提供兼容性承诺,后续版本可能变——但在已知版本内是可靠的。 C++ 对照:sizeof/alignof/offsetof 早已内建,返回 size_t;Go 返回 uintptr。布局算法的直觉(对齐、padding)两者完全一致——你可以把你对 C++ 结构体内存布局的知识直接迁移过来。 3.2 unsafe.Pointer 与 uintptr 的区别(本章最核心) 特性 unsafe.Pointer uintptr 本质 指针(与 *T 同族) 整数(地址的编号) GC 追踪 ✅ 是(对象可达性参与) ❌ 不是(就是个数字) 可做算术 ❌ 不能直接 +n ✅ 可以 可长期保存 ✅(Go 1.17+ 合法) ❌ 存久了=可能悬垂 转换 *T ↔ unsafe.Pointer ↔ *U uintptr(unsafe.Pointer(...)) p := unsafe.Pointer(&x) // &x 是 *int → unsafe.Pointer(合法) up := uintptr(p) // 转成整数(此时"失去指针身份") _ = up // ⚠️ 一旦 p 指向的对象被 GC 移动/回收,up 就是"悬垂整数" // Go 的 GC 不移动对象,但会回收;up 长期保存=访问已释放内存(未定义行为) 官方三步规则(Go 文档的『Conversion』/『Restrictions』,一共 7 条),核心是三种合法模式: ...

2026 年 10 月 13 日

Web 框架 Gin / Echo

从 C++ 的 Crow / Drogon 到 Go:Gin 是什么、何时该用它,以及它替你省下的那些 net/http 样板代码 一、简述 上一篇我们把 net/http 摸了一遍——纯标准库完全能写服务,但路由到参数、JSON 绑定校验、中间件这些「每个项目都要重复一遍」的活,写得多了就烦。Gin 和 Echo 是 Go 生态里最主流的两个 Web 框架,它们把「路由分组 + 参数绑定 + 校验 + 中间件」整合成统一 API。Gin 以高性能(基于 radix tree 的路由器)和极简 API 出名,Echo 结构更规整、前后端感触更「框架味」。 核心要点:框架不改变 Go 的 Web 本质——底层还是 http.Handler;Gin/Echo 只是给你一个更顺手的 c *gin.Context / e echo.Context。「参数绑定 + binding tag 校验」是 C++ 框架(如 Drogon)很难优雅做到的事,Go 用结构体标签声明式解决,这是最大的体验提升点。 二、C++ 对照速查表 概念 C++ Go 关键差异 Web 框架 Crow / Drogon / cpp-httplib Gin / Echo Gin 性能强、Echo 规整 路由注册 CROW_ROUTE(app, "/x") r.GET("/x", fn) 链式+分组,表达力更强 路径参数 CROW_ROUTE(app,"/<int>") 回调参数 c.Param("id") 参数进 Handler 上下文 路由分组 手动前缀 r.Group("/api") 组内共享中间件/前缀 JSON 返回 app.route(...).post(...) 手动拼 c.JSON(200, obj) 一行返回+自动 Content-Type 参数绑定 手写解析字段 c.ShouldBindJSON(&req) 结构体 tags 声明式映射 参数校验 手写 if 判断 binding:"required" tag 驱动,绑定即校验 中间件 手动包装 / 框架回调 r.Use(mw) 全局/组/路由三级粒度 panic 恢复 手动 try/catch gin.Recovery() 内置 框架默认就带 三、逐主题详解 3.1 Gin 起步 —— 一个能跑的 Hello 安装并写最小服务: ...

2026 年 10 月 13 日

包与导入

Go 的"包"是组织代码的唯一单元。一个目录就是一个包,没有头文件,没有 .cpp/.h 分离。 理解包的规则,是读懂 Go 项目结构的第一步。 1. 核心概念速览 package 声明 每个 .go 文件的第一行必须是 package 声明,且同一目录下所有文件必须声明相同的包名。 // 文件: mathutil/add.go package mathutil // 同目录的 subtract.go 也必须是: // package mathutil func Add(a, b int) int { return a + b } package main — 程序入口 package main 是特殊约定,表示可执行程序的入口,不是库包。Go 没有独立的入口函数声明——入口就是 package main 里的 func main()。 package main import "fmt" func main() { fmt.Println("Hello, Go!") } C++ 对照: package main ≈ 包含 int main() 的那个编译单元。 2. C++ 对照速查表 Go 概念 C++ 对应 差异说明 package namespace + #include 一个 package 同时提供命名空间和导入功能,不需要头文件 import #include import 加载的是编译好的包,不是文本包含 首字母大写 = 导出 public / protected 基于命名规则,不是关键字;小写 = 包内可见 首字母小写 = 私有 private 同上,没有 private 关键字 一个目录一个包 一个模块/目录 同目录必须同 package,不可混装 init() 全局变量的构造函数 更可靠,保证在 main() 之前执行 import . "fmt" using namespace std; 同样不推荐,污染命名空间 循环导入 循环 #include 两者都是编译错误 internal/ C++ 无直接对应 类似 friend 的简化版,限制子包的可见性 go.mod 依赖管理 CMake / Conan / vcpkg Go 原生支持,无需第三方工具 3. 导入语法 单行导入 package main import "fmt" func main() { fmt.Println("单行导入") } 分组导入(推荐) package main import ( "fmt" "os" "strings" // 标准库放一组 ) func main() { fmt.Println(strings.Join(os.Args[1:], " ")) } C++ 对照: Go 的 import 会自动加载包内所有导出符号,不需要逐个 include 头文件。 ...

2026 年 10 月 13 日

包与模块

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

2026 年 10 月 13 日

变量、常量与类型

从 C++ 到 Go:变量声明、常量、基本类型与类型系统的核心差异 一、简述 Go 的类型系统比 C++ 更简单、更严格:没有隐式类型转换,没有函数重载,没有模板/泛型(Go 1.18 后有泛型但用法不同)。这看似"退步",实际上大幅降低了代码的心智负担——编译器替你做了大量约束。 对于 C++ 开发者,最需要适应的三个变化: 所有未使用的变量都会报错(unused variable) 类型转换必须显式(没有 int x = 3.14 这种事) string 是值类型(赋值和传参都会拷贝,不像 std::string 有引用计数) 二、C++ 对照速查表 概念 C++ Go 备注 变量声明 int x; var x int Go 声明与赋值分离 类型推断 auto x = 42; x := 42 := = 声明 + 推断 + 赋值 常量 const int X = 10; const X = 10 Go 常量不需要写类型 编译期常量 constexpr int X = 10; const X = 10 Go 的 const 全部是编译期常量 枚举 enum Color { RED, GREEN }; const ( RED = iota; GREEN ) Go 用 iota 实现枚举 隐式转换 double d = 42;(合法) 不允许 Go 必须显式:float64(42) 字符串 std::string s = "hi";(引用语义) s := "hi"(值类型) Go string 赋值是拷贝 字符 char c = 'A'; var c byte = 'A' 或 var c rune = 'A' Go 没有 char 类型 多变量声明 int a, b; var a, b int 或 a, b := 1, 2 零值 未初始化(UB/随机值) 自动零值 Go 所有变量声明后都有零值 类型别名 using MyInt = int; type MyInt int Go 叫"类型定义",不是别名 打印 std::cout << x; fmt.Println(x) Go 用 fmt 包格式化输出 三、变量声明 3.1 var 声明 package main import "fmt" func main() { // 基本语法:var 变量名 类型 var name string // 声明但不赋值,零值为 "" var age int // 零值为 0 var active bool // 零值为 false // 声明并赋值 var score int = 100 var lang string = "Go" // 类型推断:省略类型,编译器从右侧推断 var version = 2 // 推断为 int var pi = 3.14 // 推断为 float64(Go 默认浮点类型) var greeting = "hello" // 推断为 string fmt.Println(name, age, active, score, lang, version, pi, greeting) } C++ 对照: ...

2026 年 10 月 13 日

变量与类型

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

2026 年 10 月 13 日