文章

类型系统

类型系统

值类型 vs 引用类型

类型分类赋值行为零值典型代表
int/uint/float值类型复制0计数、数学运算
bool值类型复制false条件判断
string值类型复制(底层不可变)""文本
array值类型复制整个数组元素零值固定长度集合
struct值类型复制所有字段字段零值自定义数据结构
slice引用类型复制 header(共享底层数组)nil动态长度集合
map引用类型复制指针(共享数据)nil键值对
channel引用类型复制指针nilgoroutine 通信
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"`  // 多标签
}
标签作用示例
jsonencoding/jsonJSON 字段名和控制json:"name,omitempty"
xmlencoding/xmlXML 字段名xml:"name,attr"
dbsqlx/gorm数据库列名db:"user_name"
gormgormGORM 映射gorm:"column:name;type:varchar(100)"
validatego-playground/validator字段验证validate:"required,min=3,max=50"
yamlyaml.v3YAML 字段名yaml:"server_port"
mapstructureviper配置解析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.18cap < 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
是否新类型
类型转换需要显式转换直接赋值
可附加方法否(不能给别名加方法)
编译后存在否(编译时替换)
典型用途领域类型、封装代码迁移、兼容