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类型的值不能相互比较(编译错误)

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函数执行顺序
    1. 同一文件:按出现顺序
    2. 同一包:按文件名字典序
    3. 不同包:被依赖的先执行
    4. 完整顺序:导入包→包级变量→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 []*int
    for _, 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
Go 基础知识
https://fuwari.vercel.app/posts/go_basics/
作者
Jarrett
发布于
2025-08-12
许可协议
CC BY-NC-SA 4.0