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

7.4 KiB
Raw Permalink Blame History

Golang 基础语法

问题

  1. Go 的值类型和引用类型有哪些?
  2. Go 的切片和数组有什么区别?
  3. Go 的 map 是线程安全的吗?如何实现线程安全?
  4. Go 的 defer 执行顺序和作用是什么?
  5. Go 的接口interface是如何实现的鸭子类型是什么
  6. Go 的 struct 可以比较吗?什么时候可以比较,什么时候不可以?
  7. Go 的 new 和 make 有什么区别?

标准答案

1. 值类型 vs 引用类型

值类型

  • 基本类型:intfloatboolstring
  • 数组:[n]int
  • 结构体:struct

特点:直接存储值,拷贝时复制整个值

引用类型

  • 切片:[]T
  • 映射:map[K]V
  • 通道:chan T
  • 接口:interface{}
  • 指针:*T

特点:存储引用地址,拷贝时只复制引用

示例

// 值类型
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

// 数组:固定长度
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

// 切片:动态长度
s := []int{1, 2, 3}
s = append(s, 4)  // 动态追加

// 切片的三部分
// 1. 指针:指向底层数组
// 2. 长度:当前元素个数
// 3. 容量:底层数组的总大小

// 切片的底层结构
type slice struct {
    array unsafe.Pointer  // 指向底层数组
    len   int             // 长度
    cap   int             // 容量
}

切片扩容机制

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 不是线程安全的

var m = make(map[int]int)

// ❌ 并发写入会 panic
go func() {
    m[1] = 1
}()

go func() {
    m[2] = 2
}()

// panic: concurrent map writes

解决方案 1加锁

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 的集合稳定(少量增删)
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执行顺序

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修改返回值

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

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

接口的定义

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 不需要显式声明实现了接口
// 只要实现了接口的所有方法,就自动实现了该接口

func MakeSound(s Speaker) {
    fmt.Println(s.Speak())
}

func main() {
    dog := Dog{Name: "旺财"}
    cat := Cat{Name: "咪咪"}

    MakeSound(dog)  // 汪汪
    MakeSound(cat)  // 喵喵
}

接口的零值

var s Speaker
fmt.Println(s == nil)  // true

// 接口内部存储:(type, value)
// type = nil, value = nil 时,接口才等于 nil

类型断言

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

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

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

// new: 分配内存,返回指针
p1 := new(int)
fmt.Println(p1)      // 0xc0000140a0地址
fmt.Println(*p1)     // 0零值

// 等价于
var p2 int
fmt.Println(&p2)     // 0xc0000140b0

适用:所有类型(基本类型、结构体)


make

// 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 的扩容(预分配容量)
  • 理解如何减少锁竞争(分片锁、无锁编程)