# Golang 基础语法 ## 问题 1. Go 的值类型和引用类型有哪些? 2. Go 的切片和数组有什么区别? 3. Go 的 map 是线程安全的吗?如何实现线程安全? 4. Go 的 defer 执行顺序和作用是什么? 5. Go 的接口(interface)是如何实现的?鸭子类型是什么? 6. Go 的 struct 可以比较吗?什么时候可以比较,什么时候不可以? 7. Go 的 new 和 make 有什么区别? --- ## 标准答案 ### 1. 值类型 vs 引用类型 #### **值类型** - 基本类型:`int`、`float`、`bool`、`string` - 数组:`[n]int` - 结构体:`struct` **特点**:直接存储值,拷贝时复制整个值 #### **引用类型** - 切片:`[]T` - 映射:`map[K]V` - 通道:`chan T` - 接口:`interface{}` - 指针:`*T` **特点**:存储引用地址,拷贝时只复制引用 **示例**: ```go // 值类型 a := 1 b := a // 拷贝值 b = 2 fmt.Println(a) // 1(不受影响) // 引用类型 s1 := []int{1, 2, 3} s2 := s1 // 拷贝引用(指向同一个底层数组) s2[0] = 999 fmt.Println(s1) // [999 2 3](受影响) ``` --- ### 2. 切片 vs 数组 #### **数组(Array)** ```go // 数组:固定长度 var arr [5]int // 长度为 5 的数组 arr := [3]int{1, 2, 3} arr2 := [...]int{1, 2, 3} // 自动推导长度 // 数组是值类型 arr3 := arr arr3[0] = 999 fmt.Println(arr) // [1 2 3](不受影响) ``` **特点**: - 长度固定(类型的一部分) - 值类型(拷贝时复制整个数组) - 很少直接使用 --- #### **切片(Slice)** ```go // 切片:动态长度 s := []int{1, 2, 3} s = append(s, 4) // 动态追加 // 切片的三部分 // 1. 指针:指向底层数组 // 2. 长度:当前元素个数 // 3. 容量:底层数组的总大小 // 切片的底层结构 type slice struct { array unsafe.Pointer // 指向底层数组 len int // 长度 cap int // 容量 } ``` **切片扩容机制**: ```go s := make([]int, 0, 3) // len=0, cap=3 s = append(s, 1) // len=1, cap=3 s = append(s, 2) // len=2, cap=3 s = append(s, 3) // len=3, cap=3 s = append(s, 4) // len=4, cap=6(扩容!容量翻倍) ``` **扩容策略**: - 容量 < 1024:翻倍 - 容量 ≥ 1024:增长 1.25 倍 --- ### 3. Map 的线程安全问题 #### **Go 的 map 不是线程安全的** ```go var m = make(map[int]int) // ❌ 并发写入会 panic go func() { m[1] = 1 }() go func() { m[2] = 2 }() // panic: concurrent map writes ``` --- #### **解决方案 1:加锁** ```go import "sync" var ( m = make(map[int]int) mu sync.Mutex ) func write(key, value int) { mu.Lock() m[key] = value mu.Unlock() } func read(key int) int { mu.Lock() defer mu.Unlock() return m[key] } ``` --- #### **解决方案 2:sync.Map** **适用场景**: - 读多写少 - Key 的集合稳定(少量增删) ```go var m sync.Map // 写入 m.Store(1, "one") m.Store(2, "two") // 读取 if val, ok := m.Load(1); ok { fmt.Println(val.(string)) } // 删除 m.Delete(1) // 遍历 m.Range(func(key, value interface{}) bool { fmt.Println(key, value) return true // 继续遍历 }) ``` **sync.Map 底层实现**: - `read`:只读 map(无锁,atomic) - `dirty`:读写 map(加锁) - `misses`:统计 read 未命中次数,达到阈值时提升 dirty 到 read --- ### 4. defer 的执行顺序 #### **特点** 1. **defer 后进先出(LIFO)** 2. **在函数返回前执行** 3. **可以修改返回值** --- #### **示例 1:执行顺序** ```go func example() { defer fmt.Println("defer 1") defer fmt.Println("defer 2") defer fmt.Println("defer 3") fmt.Println("main") } // 输出: // main // defer 3 // defer 2 // defer 1 ``` --- #### **示例 2:修改返回值** ```go func add(a, b int) (result int) { defer func() { result += 10 // 修改返回值 }() result = a + b return result } func main() { r := add(1, 2) fmt.Println(r) // 13(1 + 2 + 10) } ``` --- #### **示例 3:defer 与 panic** ```go func example() { defer func() { if err := recover(); err != nil { fmt.Println("recover:", err) } }() defer fmt.Println("defer 1") panic("error") defer fmt.Println("defer 2") // 不会执行 } // 输出: // defer 1 // recover: error ``` --- ### 5. 接口(Interface) #### **接口的定义** ```go type Speaker interface { Speak() string } type Dog struct { Name string } func (d Dog) Speak() string { return "汪汪" } type Cat struct { Name string } func (c Cat) Speak() string { return "喵喵" } ``` --- #### **鸭子类型(Duck Typing)** ```go // Go 不需要显式声明实现了接口 // 只要实现了接口的所有方法,就自动实现了该接口 func MakeSound(s Speaker) { fmt.Println(s.Speak()) } func main() { dog := Dog{Name: "旺财"} cat := Cat{Name: "咪咪"} MakeSound(dog) // 汪汪 MakeSound(cat) // 喵喵 } ``` --- #### **接口的零值** ```go var s Speaker fmt.Println(s == nil) // true // 接口内部存储:(type, value) // type = nil, value = nil 时,接口才等于 nil ``` --- #### **类型断言** ```go func checkType(i interface{}) { switch v := i.(type) { case int: fmt.Println("int:", v) case string: fmt.Println("string:", v) case Dog: fmt.Println("Dog:", v.Name) default: fmt.Println("unknown type") } } ``` --- ### 6. Struct 的比较 #### **可比较的 Struct** ```go type Point struct { X int Y int } p1 := Point{X: 1, Y: 2} p2 := Point{X: 1, Y: 2} fmt.Println(p1 == p2) // true // 条件: // 1. 所有字段都是可比较的 // 2. 字段类型相同 // 3. 字段值相同 ``` --- #### **不可比较的 Struct** ```go type Foo struct { m map[string]int // map 不可比较 } f1 := Foo{m: make(map[string]int)} f2 := Foo{m: make(map[string]int)} // f1 == f2 // 编译错误:map 不可比较 // 其他不可比较类型: // - slice // - map // - function // - 包含这些类型的 struct ``` --- ### 7. new vs make #### **new** ```go // new: 分配内存,返回指针 p1 := new(int) fmt.Println(p1) // 0xc0000140a0(地址) fmt.Println(*p1) // 0(零值) // 等价于 var p2 int fmt.Println(&p2) // 0xc0000140b0 ``` **适用**:所有类型(基本类型、结构体) --- #### **make** ```go // make: 只用于创建 slice、map、chan // 返回初始化后的值(非指针) // 切片 s := make([]int, 3, 5) // len=3, cap=5 // map m := make(map[string]int) // channel ch := make(chan int, 10) ``` **适用**: - `slice` - `map` - `channel` --- #### **对比** | 特性 | new | make | |------|-----|------| | **返回类型** | 指针 | 值 | | **适用类型** | 所有类型 | slice、map、chan | | **初始化** | 零值 | 初始化后可用 | --- ### 8. 阿里 P7 加分项 **深度理解**: - 理解 slice 的底层实现(数组、指针、长度、容量) - 理解 map 的扩容机制和哈希冲突解决 - 理解 interface 的动态派发和类型断言 **实战经验**: - 有处理并发 map 问题的经验 - 有 defer 导致的性能问题(defer 循环中的大锁) - 有接口设计的最佳实践 **性能优化**: - 理解如何减少内存分配(对象池、sync.Pool) - 理解如何优化 slice 的扩容(预分配容量) - 理解如何减少锁竞争(分片锁、无锁编程)