1844 字
9 分钟
Go 基础知识
Go 基础知识
一、Go 语言特点
- 原生的并发编程支持
- 通过channel实现goroutine之间通信,简单且安全
- defer机制:函数return/panic时执行,避免资源泄露
- 独特的异常处理方式:提高代码健壮性
- 函数可以返回多个值
- 提供丰富便捷的网络编程接口
- 将编程规范集成到了语言本身
二、基础类型与数据结构
2.1 基础类型
-
数值类型溢出
- 编译时常量运算溢出会报错
- 运行时整数溢出行为:
- 无符号整型:循环回绕(uint8: 255+1=0)
- 有符号整型:回绕(int8: 127+1=-128)
- 有符号转无符号:负数变成大正数
- 浮点数溢出:结果为+Inf或-Inf
- 浮点数不支持位移操作(编译错误)
- 类型转换时可能截断或溢出
-
iota计数器
- const中每新增一行常量声明将使iota计数一次
- iota在const关键字出现时将被重置为0
- iota的值基于它在const块中的行号(从0开始)
-
nil值特性
- 不同类型nil值的大小(64位系统):
- slice: 24字节(指针8+len 8+cap 8)
- interface: 16字节(type 8+data 8)
- map/channel/pointer/func: 8字节
- nil值比较规则:
- 只有相同类型的nil才能比较
- slice/map/func类型的值不能相互比较(编译错误)
- 不同类型nil值的大小(64位系统):
2.2 数组和切片
-
数组和切片的区别
- 相同点:
- 元素类型必须相同
- 元素在内存中连续存储
- 不同点:
- 零值:数组是元素零值,切片是nil
- 长度:数组固定,切片可变
- 底层:数组直接存储,切片是结构体(指针+len+cap)
- 传递:数组是值传递,切片传递结构体(类似引用)
- 比较:相同长度数组可比较,切片不可比较(只能与nil比较)
- 相同点:
-
slice和map的区别
- 存储:slice连续,map散列
- 访问:都是O(1),但map更慢(hash计算)
- key类型:map需要可比较类型
- 稀疏数据:map更节省内存
-
容量和长度
- 支持cap:数组、切片、channel
- 支持len:数组、切片、channel、map、string
- 数组:len==cap且不可变
- 切片:可通过append扩容间接修改
2.3 Map特性
- map的key要求
- 必须是可比较类型
- 不可作为key:slice、map、func、包含这些类型的struct/array
- float可以但不推荐(精度问题、NaN!=NaN)
2.4 结构体和接口
-
内嵌字段规则
*T
可以作为内嵌字段,条件:- T是类型名(非字面量)
- T不是指针类型
- T不是接口类型
-
interface比较
- 接口值可以用==比较
- 比较规则:
- 先比较动态类型
- 再比较动态值
- 注意:动态类型不可比较会panic
-
使结构体不可比较
- 添加不可比较字段
- 常用:
_ [0]func()
(零开销)
-
不同结构体类型比较
- 条件:
- 字段顺序、名称、类型完全相同
- 所有字段可比较
- 至少一个是非定义类型
- 条件:
-
空结构体应用
- 不占内存(size为0)
- 用作map的值实现set
- 用作channel信号
2.5 指针
-
不可取地址的值
- map的元素
- 字符串的字节
- 常量
- 包级函数
- 字面量(10、“hello”)
- 表达式结果
-
指针限制
- 不支持算术运算
- 不能随意转换类型,除非底层类型相同且至少一个是未定义类型
- 指针只能与相同类型的指针或 nil 比较,比较的是地址值是否相等
三、内存管理
3.1 内存分配
-
new和make的区别
- new:分配零值内存,返回指针(*T),适用所有类型
- make:分配并初始化,返回值(T),仅用于slice/map/channel
- new([]int)返回*[]int指向nil切片
- make([]int, 0)返回[]int空切片
-
触发内存分配的操作
- 声明变量
- make/new
- 字面量初始化
- 字符串拼接(非常量)
- []byte和string互转
- 数字转字符串
- append扩容
- map扩容
3.2 栈和堆
-
内存分配位置
- 栈:函数局部变量(未逃逸)
- 堆:逃逸的变量、大对象
- 编译器决定(逃逸分析)
-
逃逸场景
- 返回局部变量地址
- 闭包引用外部变量
- 接口类型赋值
- 超过栈大小的对象(大于64kb)
- 不定长参数(…interface{})
-
值部概念
- 直接值部:一个内存块(基本类型、数组、结构体)
- 间接值部:多个内存块(slice、map、channel、interface、string)
四、包管理和项目组织
4.1 包管理演进
- GOPATH(1.5-):统一路径,无版本控制
- Go Vendor(1.5+):项目级依赖
- Go Modules(1.11+):版本管理
4.2 GOPATH模式
- 包管理实现
- 统一依赖包下载路径实现包管理,但是不支持版本控制
- 目录结构
- src:源代码
- pkg:编译包
- bin:可执行文件
- 环境变量
- GOROOT:Go安装目录
- GOPATH:工作空间
- 命令
- go get:下载+安装
- go build:编译
- go install:编译+安装到bin
4.3 Go Vendor
- vendor目录存放依赖
- vendor.json记录版本
- 优点:稳定、离线编译
- 缺点:代码冗余、更新困难
4.4 Go Modules
-
依赖存储:GOPATH/pkg/mod
-
核心文件
- go.mod:模块定义和依赖版本
- go.sum:依赖校验hash
-
常用命令
- go mod init:初始化模块
- go mod tidy:整理依赖
- go mod download:下载依赖
- go mod vendor:导出到vendor
- go mod graph:依赖图
-
go.sum校验
- 每个依赖两行记录
- .zip文件hash
- go.mod文件hash(/go.mod后缀)
- 跳过校验:
- GOPRIVATE私有包
- vendor目录
- GOSUMDB=off
4.5 内部包
- internal包:限制导入权限,只能被父目录及其子目录导入
- 私有包使用
- 本地:replace指令
- 私有仓库:配置GOPRIVATE
4.6 初始化顺序
- init函数执行顺序
- 同一文件:按出现顺序
- 同一包:按文件名字典序
- 不同包:被依赖的先执行
- 完整顺序:导入包→包级变量→init()→main()
4.7 路径获取
// 当前工作目录filepath.Abs("./")os.Getwd()
// 可执行文件os.Args[0] // 启动命令os.Executable() // 绝对路径
// 源文件路径_, file, _, _ := runtime.Caller(0)
五、常用操作
5.1 格式化输出
- %v:值
- %+v:值+字段名
- %#v:Go语法表示
- %T:类型
5.2 标准输入
fmt.Scan(&v) // 空格分隔fmt.Scanf("%d", &v) // 格式化fmt.Scanln(&v) // 读一行
reader := bufio.NewReader(os.Stdin)line, _ := reader.ReadString('\n')
5.3 字符串操作
- Trim系列区别
- TrimRight/Left:删除字符集合中的字符
- TrimSuffix/Prefix:删除特定字符串(一次)
strings.TrimRight("test!!!", "!") // "test"strings.TrimSuffix("test!!!", "!") // "test!!"
5.4 类型字面量
- 支持组合字面量:struct、slice、array、map
- 不支持:基本类型、channel、interface、func
六、比较和反射
6.1 比较操作
-
DeepEqual vs bytes.Equal
- DeepEqual:区分nil和空切片
- bytes.Equal:nil和空切片相等
-
DeepEqual vs ==
- 不同类型:DeepEqual返回false
- 指针:==比较地址,DeepEqual比较指向的值
- 循环引用:DeepEqual返回true避免死循环
6.2 反射
- 判断方法是否存在
// 方法1:类型断言if v, ok := obj.(interface{ Method() }); ok {v.Method()}// 方法2:反射v := reflect.ValueOf(obj)m := v.MethodByName("Method")if m.IsValid() {m.Call(nil)}
七、常见陷阱
7.1 for range陷阱
-
问题
- 循环变量是副本
- 循环变量地址复用(Go 1.22前)
// Go 1.21及之前的问题var ptrs []*intfor _, v := range []int{1,2,3} {ptrs = append(ptrs, &v) // 都指向同一地址}// Go 1.22修复:每次迭代创建新变量 -
优化建议
- 大数组用索引或指针遍历
arr := [1000000]int{...}// 避免复制for i := range arr {process(arr[i])}// 或for i := range &arr {process(arr[i])}
7.2 其他陷阱
- nil map可读不可写
- 未初始化的slice append正常工作
- defer参数即时求值
- 闭包延迟绑定
- interface{}(nil) != nil