Files
interview/questions/13-Golang语言/Golang基础语法.md
yasinshaw 7f3ab362b3 feat: rename Golang files to Chinese and supplement root files
Changes:
- Renamed all 10 Golang files from English to Chinese names
- Created 00-项目概述/项目概述.md with comprehensive project overview
- Created 08-算法与数据结构/算法与数据结构学习指南.md with detailed learning guide
- Created 12-面试技巧/面试准备进度.md with progress tracking
- Added .obsidian configuration for better markdown editing
- Updated Claude.MD with Chinese filename rule

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-03-01 00:33:32 +08:00

454 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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]
}
```
---
#### **解决方案 2sync.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) // 131 + 2 + 10
}
```
---
#### **示例 3defer 与 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 的扩容(预分配容量)
- 理解如何减少锁竞争(分片锁、无锁编程)