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 日

VS 向导 vs CMake 手建工程:一次取舍记录

新建 C++ 项目时,用 VS2022 项目向导,还是手建目录 + CMake?核心结论:向导是「快」,CMake 手建是「稳」;长期维护的 Boost + OpenCV 项目走 CMake 手建路线。 一、「完全不利用 VS 向导」指什么 VS 新建项目时,向导会自动生成: 目录结构 .vcxproj / .sln 一些默认代码 默认的编译配置 不用向导,就是自己手建目录、手写 CMakeLists.txt、用 VS 的「打开文件夹」模式加载。 二、CMake 手建的好处 # 好处 说明 1 结构清晰、可控 向导生成的项目,目录和配置是向导定的;手建时 src/、include/、resources/、tests/ 怎么分,自己说了算 2 跨平台 CMakeLists.txt 是跨平台的:同一个项目,Windows 用 MSVC,WSL 用 GCC,不用改任何东西;向导生成的 .vcxproj 只能在 VS 里用 3 依赖管理统一 Boost、OpenCV 全走 find_package,环境变量一配,所有项目通用;向导的项目要手动一个个加 include、lib 路径 4 不污染仓库 .vcxproj、.sln、.vs/、out/ 都是本地文件,不进 Git;CMake 项目只需提交 CMakeLists.txt 和源码,干净 5 和工业界一致 大多数 C++ 开源项目、跨平台项目都用 CMake;养成这个习惯,以后参与别人的项目、或自己发布项目,都顺 6 配置一次、项目复用 环境变量和 CMakeLists.txt 模板定好后,新建项目就是复制模板、改项目名、加源文件。增量配置,不是每次从零配 三、CMake 手建的代价 初期配置复杂。第一次要理解:CMakeLists.txt 的语法、find_package 怎么找库、环境变量怎么设、CMakePresets.json 的格式。这些概念对新手是门槛。 VS 集成不如原生项目「顺滑」。向导项目是 VS 原生支持,调试、IntelliSense 开箱即用;CMake 项目通过「打开文件夹」或「CMake 项目」模式支持,功能也全,但偶尔会遇到配置识别问题。 调试和 IDE 功能偶有摩擦。断点、监视窗口、调用栈这些都没问题;但某些 VS 特有的功能(比如某些项目属性页面),在 CMake 项目里没有对应入口。 学习曲线。要同时学 CMake、学 Boost/OpenCV 的 CMake 配置。一次性投入大,但长期收益高。 四、取舍小结 整体体验是:结构清晰,配置相对复杂;不过做项目时配置一次,后续只是增量配置。 ...

2026 年 10 月 13 日

VS2022 打开 CMake 项目的调试说明:断点与 launch.vs.json

手建的 CMake 项目在 VS2022「打开文件夹」模式下的打开方式、自动生成的配置、断点调试与 launch.vs.json。核心结论:CMake 项目的调试体验与向导项目基本一致,唯一多出的一步是手动选启动目标。 一、怎么用 VS 打开手建的 CMake 项目 步骤: VS2022 → 文件 → 打开 → 文件夹。 选项目根目录,比如 <项目根目录>\MyProject。 VS 自动识别 CMakeLists.txt,切换到 CMake 项目模式。 VS 会自动做的事: 读 CMakeLists.txt,配置 CMake。 在项目根目录生成 .vs/,存 VS 自己的状态。 在 out/build/ 下生成构建产物。 底部状态栏出现 配置选择器(Debug/Release),和 CMake 目标选择器。 和向导项目一样,VS 该自动生成的还是会生成。 二、VS 自动生成的配置有哪些 和向导项目时一样: .vs/:VS 的工作状态、索引、窗口布局。不提交 Git。 out/:CMake 构建产物。不提交 Git。 CMakeUserPresets.json:如果在 VS 里改过预设,它可能生成这个。不提交 Git。 这些不影响项目结构本身,只是 VS 的本地缓存,删了能重建。 三、断点怎么用 CMake 项目的断点和向导项目完全一样。 基本流程 在代码行号左边点一下,出现红点,就是断点。 按 F5 启动调试(不是 Ctrl+F5,Ctrl+F5 是不调试直接运行)。 程序运行到断点处停下,VS 自动切换到调试界面。 底部出现调试工具栏:继续、单步跳过、单步进入、跳出。 左侧或底部窗口:局部变量、监视、调用栈、断点列表。 常用快捷键 操作 快捷键 启动调试 F5 停止调试 Shift + F5 单步跳过(不进入函数) F10 单步进入(进入函数) F11 跳出当前函数 Shift + F11 继续运行 F5 切换断点 F9 条件断点 右键断点 → 条件 CMake 项目特有的注意点 1. 启动目标 ...

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 日

Windows 上 Boost + OpenCV + CMake 的环境配置方案:只配两件事

从零配置的一套通用方案:环境变量 + CMakePresets.json。一次配好,所有项目通用。Windows 端只配 Boost + OpenCV,不配 Qt(Qt 开发放在 Linux 端)。 文中 <OpenCV安装目录>、<Boost根目录>、<项目根目录> 为占位符,替换成本机实际目录即可。 一、环境变量(系统变量) 在 Windows「系统属性 → 环境变量」里,系统变量新建: 变量名 值 作用 OpenCV_DIR <OpenCV安装目录>\build\x64\vc16\lib OpenCV 的 Config.cmake 所在目录 BOOST_ROOT <Boost根目录> Boost 根目录 PATH 追加(运行时找 dll): <OpenCV安装目录>\build\x64\vc16\bin Boost 不用加 PATH,静态库,运行时不需要 dll。 二、每个库的配置细节 OpenCV 关键文件(示例路径): <OpenCV安装目录>\build\x64\vc16\lib\OpenCVConfig.cmake <OpenCV安装目录>\build\x64\vc16\lib\opencv_world4100.lib <OpenCV安装目录>\build\x64\vc16\lib\opencv_world4100d.lib 变量: OpenCV_DIR = <OpenCV安装目录>\build\x64\vc16\lib CMake 里: find_package(OpenCV REQUIRED) target_include_directories(main PRIVATE ${OpenCV_INCLUDE_DIRS}) target_link_libraries(main PRIVATE ${OpenCV_LIBS}) Boost 关键文件(示例路径): <Boost根目录>\stage\lib\cmake\Boost-1.81.0\BoostConfig.cmake <Boost根目录>\stage\lib\libboost_filesystem-vc143-mt-x64-1_81.lib <Boost根目录>\stage\lib\libboost_filesystem-vc143-mt-gd-x64-1_81.lib 变量: BOOST_ROOT = <Boost根目录> CMake 里: set(Boost_NO_SYSTEM_PATHS ON) set(Boost_USE_STATIC_LIBS ON) set(Boost_USE_MULTITHREADED ON) find_package(Boost 1.81 REQUIRED COMPONENTS filesystem system thread) target_link_libraries(main PRIVATE Boost::filesystem Boost::system Boost::thread) 注意:Boost 大部分是 header-only,find_package 只对需要编译的库(filesystem、system、thread 等)起作用。header-only 的库直接 #include 就行,CMake 里不用 find_package。 ...

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 日