初识 Go 与开发环境搭建
初识 Go 与开发环境搭建 Go 是 Google 在 2007 年立项、2009 年 11 月开源、2012 年 3 月发布 1.0 版本的编程语言。它由 Robert Griesemer、Rob Pike、Ken Thompson 三人设计,目标是解决大规模工程中"编译慢、语言特性多、并发难写"这三类问题。 这篇笔记先建立对 Go 的整体认识,然后把开发环境搭好,最后跑通第一个程序,并说清楚源文件的一些基本约定。 Go 的设计目标 Go 的核心设计目标可以归纳为下面几条,后面的所有语言特性几乎都是围绕它们展开的: 编译型语言:源码编译为机器码后直接执行,没有虚拟机或解释器参与运行。 静态类型:所有变量的类型在编译期确定,类型不匹配在编译期就报错。 原生并发:goroutine(由运行时管理的轻量执行单元)和 channel(用于在 goroutine 之间传递数据的管道)是语言级特性,不是库。 语法简洁:关键字只有 25 个,没有类继承、没有异常、没有隐式类型转换。 编译速度快:大型项目也能在几秒内完成构建,这是明确的设计指标。 内置工具链:格式化、...
变量、常量与基本类型
需要预先了解: 初识 Go 与开发环境搭建 变量、常量与基本类型 上一篇把环境搭好并跑通了第一个程序,这篇开始进入语法。Go 的变量声明有几种固定写法,const 的规则比大多数语言严格,而基本类型里 rune、byte 和 int 的宽度是最容易搞混的几个点。 变量声明:var var 是最基础的声明关键字,有下面几种形式: var a int = 10 // 完整写法:类型 + 初值 var b = 20 // 省略类型,由右边的值推断 var c, d = 1, 2 // 一次声明多个,类型分别推断 var e int // 只声明不赋值,取零值 类型推断只在有初值的时候生效,var e 这种没有初值的写法必须写类型,否则编译器无法知道 e 是什么。 变量也可以成组声明,适合把相关的变量放在一起: var ( name string = "一撮灰" age int = 3 ) 零值 **零值(zero value)**是变量声明后未显式赋值时自动获得的值。Go 没有"未初始化变量...
运算符与流程控制
需要预先了解: 变量、常量与基本类型 运算符与流程控制 Go 的流程控制关键字只有 if、for、switch、select(并发场景用),没有 while、没有 do...while。这套精简的设计带来了一些与直觉不同的细节:if 可以带初始化语句、switch 默认不穿透、++ 是语句而不是表达式。 运算符 算术运算符 运算符 含义 示例 结果 + 加法 1 + 2 3 - 减法/取负 5 - 3 2 * 乘法 2 * 3 6 / 除法 7 / 2 3 % 取余 7 % 2 1 整数除法会截断小数部分,取余的结果符号与被除数一致: fmt.Println(7/2, 7%2, -7/2, -7%2) 3 1 -3 -1 除数为常量 0 是编译错误,除数为变量 0 会在运行时 panic(integer divide by zero)。 位运算符 运算符 含义 示例 结果 & 按位与 5 & 3 1 | 按位或 5 | 3 7 ^ 按位异或 5 ^ 3 6 &^ 位清除 5 &a...
数组与切片
需要预先了解: 运算符与流程控制 数组与切片 数组和切片是 Go 里最常用的两种序列容器,但它们的语义差别很大:数组是值类型,赋值和传参会整份拷贝;切片是一个包含"指针 + 长度 + 容量"的表头,拷贝表头后两个切片仍指向同一块底层数组。理解切片的表头结构,是理解后面所有"共享底层数组"陷阱的前提。 数组 数组的声明方式是 [长度]元素类型: var a [3]int // 零值数组:[0 0 0] b := [3]int{1, 2, 3} // 带初值 c := [...]int{1, 2, 3, 4} // 用 ... 让编译器数长度 d := [5]int{0: 1, 4: 5} // 指定下标初始化:[1 0 0 0 5] 长度是类型的一部分 [3]int 和 [5]int 是两个不同的类型,不能互相赋值: var x [3]int = [5]int{} // 编译错误 cannot use [5]...
map 与字符串
需要预先了解: 数组与切片 map 与字符串 map 是 Go 内置的键值容器,string 是 UTF-8 编码的只读字节序列。两者都是使用频率极高的类型,也各有几个容易踩坑的地方:nil map 不能写入、map 的遍历顺序是随机的、字符串的 len 返回的是字节数而不是字符数。 map 的基本操作 声明与初始化 var m map[string]int // 声明,零值是 nil,不能直接写入 m = make(map[string]int) // 用 make 初始化 m2 := map[string]int{"a": 1, "b": 2} // 字面量 m3 := make(map[string]int, 16) // 预分配容量(提示,不是硬上限) 增、删、改、查 m := map[string]int{"a": 1, "b": 2} m["c"] = 3 // 增 m["a"] = 100 // 改 delete(m, "b") // 删 fmt.Println(m, l...
函数
需要预先了解: map 与字符串 函数 Go 的函数有两个显著特点:参数和返回值的类型都写在名字后面,以及支持多返回值。除此之外,函数在 Go 里是一等公民——可以赋值给变量、作为参数传递、作为返回值,这也让闭包成为日常写法。这篇还会讲清楚一个容易被忽略的事实:Go 的参数传递永远是值拷贝。 定义与调用 func add(a int, b int) int { return a + b } func 是关键字; add 是函数名; 参数列表里,名字在前、类型在后;相邻参数类型相同时可以合并写成 a, b int; 返回值类型写在参数列表之后。 没有返回值时省略返回类型: func greet(name string) { fmt.Println("你好,", name) } 多返回值 函数可以返回多个值,这是 Go 里处理"结果 + 错误"的标准形式: func divmod(a, b int) (int, int) { return a / b, a % b } q, r...
结构体与指针
需要预先了解: 函数 结构体与指针 **结构体(struct)**是把一组字段打包成一个整体,指针用来在函数之间共享同一个对象。Go 的指针比大多数语言的指针"干净":不能做算术运算,不能随意转换类型。这一篇把两者的用法和几个反直觉的规则讲清楚。 指针 & 取地址,* 解引用: a := 10 p := &a // p 的类型是 *int fmt.Println(a, p, *p) // 10 0x104d44a300e0 10 *p = 20 // 通过指针修改 a fmt.Println(a) // 20 指针的零值是 nil: var np *int fmt.Println("np == nil:", np == nil) // true 对 nil 指针解引用会 panic(invalid memory address or nil pointer dereference),使用前要判空。 没有指针算术 指针不能加减、不能自增: p++ invalid operation: p++...
方法
需要预先了解: 结构体与指针 方法 **方法(method)**是带接收者的函数。接收者写在 func 和函数名之间,把方法和某个类型绑定起来。方法看起来只是多了一个参数,但"值接收者还是指针接收者"这个选择会直接影响方法能否修改对象,以及该类型能否满足某个接口。 定义 type Counter struct { n int } func (c Counter) Get() int { return c.n } (c Counter) 就是接收者。调用时用点号: c := Counter{n: 5} fmt.Println(c.Get()) // 5 方法名不能与字段名冲突。 值接收者 vs 指针接收者 值接收者拿到的是调用者的副本,修改副本不影响原对象;指针接收者拿到的是地址,可以修改原对象: func (c Counter) IncrVal() { c.n++ } // 改副本 func (c *Counter) IncrPtr() { c....
接口
需要预先了解: 方法 接口 **接口(interface)**只描述"能做什么",不关心"是什么"。调用方依赖接口,具体实现可以是任意类型——这就是把"变化的实现"和"稳定的调用逻辑"隔离开的方式。Go 的接口有两个特点:实现是隐式的,不需要显式声明;接口值本身由"动态类型 + 动态值"两部分组成,后者是理解 nil 接口陷阱的关键。 从需求引入 假设要打印一批图形的面积。如果函数参数写成具体类型,每加一种图形就要改一次函数签名: func printArea(r Rect) { /* ... */ } func printArea2(c Circle) { /* ... */ } 调用方其实只关心"这个对象能不能算出面积"。把这件事抽象出来: type Shape interface { Area() float64 Name() string } 任何拥有这两个方法的类型都自...
错误处理
需要预先了解: 接口 函数 错误处理 Go 把"可预期的失败"当作普通的返回值处理,而不是语言级的异常机制。这套设计的核心是 error 接口加 if err != nil 惯例。与之相对,panic 只用于"程序不该继续运行"的情况。这一篇把 error 的包装与解包、defer 的完整机制、panic/recover 的边界都讲清楚。 error 接口 error 是标准库里的一个接口,只有一个方法: type error interface { Error() string } 创建错误有两种常用方式: err1 := errors.New("资源不存在") err2 := fmt.Errorf("打开文件 %s 失败", "a.txt") errors.New 返回一个固定文本的错误;fmt.Errorf 可以格式化文本。两者都满足 error 接口。 if err != nil 惯例 Go 的错误处理惯例是"检查并提前返回",而不是层层嵌套: func find(i...



