类型系统
值类型 vs 引用类型
| 类型 | 分类 | 赋值行为 | 零值 | 典型代表 |
|---|
| int/uint/float | 值类型 | 复制 | 0 | 计数、数学运算 |
| bool | 值类型 | 复制 | false | 条件判断 |
| string | 值类型 | 复制(底层不可变) | "" | 文本 |
| array | 值类型 | 复制整个数组 | 元素零值 | 固定长度集合 |
| struct | 值类型 | 复制所有字段 | 字段零值 | 自定义数据结构 |
| slice | 引用类型 | 复制 header(共享底层数组) | nil | 动态长度集合 |
| map | 引用类型 | 复制指针(共享数据) | nil | 键值对 |
| channel | 引用类型 | 复制指针 | nil | goroutine 通信 |
| pointer | 引用类型 | 复制指针值 | nil | 间接访问 |
| func | 引用类型 | 复制指针 | nil | 回调、策略 |
| interface | 引用类型 | 复制 (type, value) 对 | nil | 多态 |
关键区别:值类型复制
// 值类型:赋值 = 深拷贝
type Point struct{ X, Y int }
p1 := Point{X: 1, Y: 2}
p2 := p1 // 完整复制
p2.X = 10
fmt.Println(p1.X) // 1(p1 不受影响)
// 引用类型:赋值 = 浅拷贝(共享底层)
s1 := []int{1, 2, 3}
s2 := s1 // 共享底层数组
s2[0] = 99
fmt.Println(s1[0]) // 99(s1 也变了!)
指针
// 取地址
x := 42
p := &x // p 是 *int 类型
// 解引用
fmt.Println(*p) // 42
*p = 100 // 通过指针修改
fmt.Println(x) // 100
// new:分配零值并返回指针
p2 := new(int) // *int, 值为 0
*p2 = 42
// 指针的指针
pp := &p // **int
何时用指针
| 场景 | 用值 | 用指针 |
|---|
| 小结构体(< 64字节) | ✓ | 除非需要修改 |
| 大结构体 | | ✓(避免复制开销) |
| 需要修改原始值 | | ✓ |
| 方法接收者一致性 | 如果大部分方法用值,全用值 | 如果大部分方法用指针,全用指针 |
| map/slice/channel 本身 | ✓(已经是引用) | 不需要 |
| 接口满足 | 值类型自动满足 | 指针类型满足(如果方法用指针接收者) |
指针 vs 值方法接收者
type Counter struct{ count int }
// 值接收者:操作副本,不影响原始值
func (c Counter) Get() int { return c.count }
// 指针接收者:操作原始值
func (c *Counter) Inc() { c.count++ }
c := Counter{}
c.Inc() // Go 自动取地址 (&c).Inc()
c.Inc()
fmt.Println(c.Get()) // 2
规则:一个类型的所有方法接收者应该统一用值或统一用指针,不要混用。
结构体
// 定义
type User struct {
ID int `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Email string `json:"email,omitempty" db:"email"`
Age int `json:"age" validate:"min=0,max=150"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// 创建方式
u1 := User{ID: 1, Name: "小徐"} // 值
u2 := &User{ID: 2, Name: "小王"} // 指针
u3 := new(User) // 指针,零值
u3.ID = 3
u3.Name = "小李"
// 嵌套结构体
type Address struct {
City string
Country string
}
type Employee struct {
User // 匿名嵌入(组合,非继承)
Address // 匿名嵌入
Salary float64
}
emp := Employee{
User: User{ID: 1, Name: "小徐"},
Address: Address{City: "深圳", Country: "中国"},
Salary: 20000,
}
fmt.Println(emp.Name) // 直接访问嵌入字段:小徐
fmt.Println(emp.City) // 直接访问:深圳
结构体标签(struct tag)
type User struct {
ID int `json:"id"` // JSON 序列化
Name string `json:"name" validate:"required"` // JSON + 验证
Email string `json:"email,omitempty"` // omitempty: 零值时不输出
Age int `json:"age,string"` // 序列化为字符串
Pass string `json:"-"` // 不序列化
DB string `db:"db_name" gorm:"column:db_name"` // 多标签
}
| 标签 | 库 | 作用 | 示例 |
|---|
json | encoding/json | JSON 字段名和控制 | json:"name,omitempty" |
xml | encoding/xml | XML 字段名 | xml:"name,attr" |
db | sqlx/gorm | 数据库列名 | db:"user_name" |
gorm | gorm | GORM 映射 | gorm:"column:name;type:varchar(100)" |
validate | go-playground/validator | 字段验证 | validate:"required,min=3,max=50" |
yaml | yaml.v3 | YAML 字段名 | yaml:"server_port" |
mapstructure | viper | 配置解析 | mapstructure:"server_port" |
数组 vs Slice
// 数组:固定长度,值类型
arr1 := [3]int{1, 2, 3}
arr2 := [...]int{1, 2, 3, 4, 5} // 长度自动推断为 5
// [3]int 和 [5]int 是不同类型!
// Slice:动态长度,引用类型
s1 := []int{1, 2, 3}
s2 := make([]int, 5) // len=5, cap=5
s3 := make([]int, 5, 10) // len=5, cap=10
// Slice 三要素:指针、长度、容量
// ptr -> 底层数组的起始位置
// len -> 当前元素个数
// cap -> 底层数组从 ptr 开始的容量
Slice 操作
s := []int{0, 1, 2, 3, 4, 5}
// 切片
s[1:4] // [1, 2, 3] 左闭右开
s[:3] // [0, 1, 2]
s[3:] // [3, 4, 5]
s[:] // [0, 1, 2, 3, 4, 5]
// append
s = append(s, 6) // [0, 1, 2, 3, 4, 5, 6]
s = append(s, 7, 8, 9) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
s = append(s, []int{10, 11}...) // 展开追加
// copy
dst := make([]int, 3)
n := copy(dst, s) // n=3, dst=[0, 1, 2]
// 删除索引 i 的元素
i := 2
s = append(s[:i], s[i+1:]...) // 删除 s[2]
// 插入元素到索引 i
s = append(s[:i], append([]int{99}, s[i:]...)...)
// 或更高效的方式:
s = append(s, 0) // 扩容
copy(s[i+1:], s[i:]) // 后移
s[i] = 99 // 插入
Slice 扩容机制
| Go 版本 | 扩容规则 |
|---|
| < 1.18 | cap < 1024 时翻倍;>= 1024 时增长 1.25 倍 |
| >= 1.18 | 小 slice 翻倍;大 slice 按 1.25 倍,但会考虑类型大小做更精细的计算 |
// 预分配容量避免频繁扩容
// ❌ 慢
var s []int
for i := 0; i < 10000; i++ {
s = append(s, i) // 多次扩容 + 复制
}
// ✓ 快
s := make([]int, 0, 10000)
for i := 0; i < 10000; i++ {
s = append(s, i) // 无扩容
}
Map
// 创建
m1 := map[string]int{"a": 1, "b": 2}
m2 := make(map[string]int) // 空 map
m3 := make(map[string]int, 100) // 预分配 hint=100
// var m4 map[string]int // nil map(不能写入!)
// 操作
m1["c"] = 3 // 写入
v := m1["a"] // 读取(不存在返回零值)
v, ok := m1["x"] // 读取 + 存在检查
delete(m1, "b") // 删除
// 遍历(顺序随机!)
for k, v := range m1 {
fmt.Printf("%s=%d\n", k, v)
}
// 有序遍历:先取 key 排序
keys := make([]string, 0, len(m1))
for k := range m1 {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Printf("%s=%d\n", k, m1[k])
}
Map 并发安全
// ❌ 标准map不是并发安全的,并发读写会 panic
// m := make(map[string]int)
// go func() { m["a"] = 1 }()
// go func() { _ = m["a"] }()
// ✓ 方案1:sync.RWMutex
type SafeMap struct {
mu sync.RWMutex
m map[string]int
}
func (s *SafeMap) Get(key string) (int, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
v, ok := s.m[key]
return v, ok
}
func (s *SafeMap) Set(key string, val int) {
s.mu.Lock()
defer s.mu.Unlock()
s.m[key] = val
}
// ✓ 方案2:sync.Map(读多写少场景)
var sm sync.Map
sm.Store("key", "value")
v, ok := sm.Load("key")
sm.Delete("key")
| 方案 | 读性能 | 写性能 | 适用场景 |
|---|
| map + RWMutex | 中 | 中 | 读写均衡 |
| sync.Map | 快 | 慢 | 读多写少、key 稳定 |
| 分片 map + RWMutex | 快 | 快 | 高并发读写 |
类型转换与类型断言
// 类型转换(编译时确定,兼容类型之间)
var i int = 42
var f float64 = float64(i)
var s string = strconv.Itoa(i)
// 类型断言(运行时,从 interface{} 提取具体类型)
var val interface{} = "hello"
// 方式1:直接断言(失败会 panic)
str := val.(string)
// 方式2:带 ok 检查(失败不 panic)
str, ok := val.(string)
if !ok {
// 处理类型不匹配
}
// type switch
switch v := val.(type) {
case string:
fmt.Println("string:", v)
case int:
fmt.Println("int:", v)
case []byte:
fmt.Println("bytes:", v)
default:
fmt.Printf("unknown type: %T\n", v)
}
自定义类型 vs 类型别名
// 自定义类型:完全新的类型,需要显式转换
type Celsius float64 // 摄氏度
type Fahrenheit float64 // 华氏度
func CToF(c Celsius) Fahrenheit {
return Fahrenheit(c*9/5 + 32) // 需要显式转换
}
// 类型别名:完全等价,编译时替换
type MyInt = int // MyInt 就是 int,无需转换
| 对比 | 自定义类型 type T U | 类型别名 type T = U |
|---|
| 是否新类型 | 是 | 否 |
| 类型转换 | 需要显式转换 | 直接赋值 |
| 可附加方法 | 是 | 否(不能给别名加方法) |
| 编译后存在 | 是 | 否(编译时替换) |
| 典型用途 | 领域类型、封装 | 代码迁移、兼容 |