Go基础

2026-10-19 16:18:54 GO学习笔记

相比于其他语言的好处?

  • 做了并发优化,支持kmp模型
  • 语言具有可读性
  • 垃圾回收效率更高

什么是协程?

切片和数组

切片:动态扩缩容,指针传递

数组:固定容量,值传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func modify(arr [3]int) {
arr[0] = 100
}

a := [3]int{1, 2, 3}
modify(a)
fmt.Println(a) // [1 2 3] 原数组没变!


func modify(s []int) {
s[0] = 100
}

a := []int{1, 2, 3}
modify(a)
fmt.Println(a) // [100 2 3] 被修改了!

make初始化会把长度的元素都站住,初始化值为0,这个时候append会扩容,因为初始化的len和cap相等

len当前长度;cap容量;

1
2
3
4
5
6
7
8
9
10
s := make([]int, 5)
// len=5, cap=5
// 底层数组: [0][0][0][0][0]
// └─── len=5 ───┘
// └─── cap=5 ───┘

s = append(s, 1)
fmt.Println(s) // [0 0 0 0 0 1]
fmt.Println(len(s)) // 6
fmt.Println(cap(s)) // 10 (扩容了)

一般切片初始化写法:

1
2
make([]int, 5)      → [0 0 0 0 0]       len=5 cap=5
make([]int, 0, 5) → [] len=0 cap=5

使用for range 的时候,它的地址会发生变化吗?

1.22之前地址不会变,每次都是同一个变量,共享同一个空间,

1.22之后,每一个迭代内容都是新变量,

1
for index, value := range collection {// ...}

这里 value 是一个副本。在每次迭代中,collection 中的当前元素值会被复制到 value 这个变量中。Go 编译器通常会为 value 分配一块固定的内存地址,然后在每次迭代时,将当前元素的值覆盖到这块内存中。所以,当你打印 &value 时,你会发现它的内存地址在整个循环过程中都是保持不变的。

**优化点:**牺牲少量内存换取代码的安全性

1
2
3
4
5
6
7
// go1.21 及之前
nums := []int{1,2,3}
for _, v := range nums {
go func() {
fmt.Println(v) // 所有协程读到的都是同一个v变量
}()
}

闭包是指函数直接修改外部的变量

defer关键字

不论包含 defer 语句的函数是通过 return 正常结束,还是由于 panic 导致的异常结束,defer在注册成功的前提下都会执行。

你可以在一个函数中执行多条 defer 语句,它们的执行顺序与声明顺序相反。自下而上传递

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
defer func() {
fmt.Println("defer1")
}()
defer func() {
fmt.Println("defer2")
}()
fmt.Println("end")
}

end
defer2
defer1

空struct{}作用

struct{}占用空间为0

  1. 可以在map中当作value去模拟set
1
2
3
4
5
6
7
type Set map[string]struct{}func main() {
set := make(Set)for _, item := range []string{"A", "A", "B", "C"} {
set[item] = struct{}{}}
fmt.Println(len(set)) // 3
if _, ok := set["A"]; ok {
fmt.Println("A exists") // A exists
}}
  1. channel传递信号
1
2
3
4
5
6
7
8
done := make(chan struct{})

go func() {
doWork()
done <- struct{}{} // 通知:我完成了
}()

<-done // 等待
1
2
3
4
ch <- struct{}{}
└┬┘ └──┬┘
│ └── 要发送的值(一个零字节的东西)
└── 发送操作符,把右边的值送进 channel

init执行顺序

执行顺序:import –> const –> var –>init()–>main()

interface{}是什么

它是空接口——可以理解为”任意类型”,any是interface的别名

1
2
3
4
interface{} = { type, value }
└─┬─┘ └──┬──┘
│ └── 实际值的指针/数据
└────────── 实际类型信息
1
2
3
4
5
6
var x interface{}
x = 42
// x = { type: int, value: 42 }

x = "hi"
// x = { type: string, value: "hi" }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func describe(v interface{}) {
switch t := v.(type) {
case int:
fmt.Println("int:", t)
case string:
fmt.Println("string:", t)
case bool:
fmt.Println("bool:", t)
default:
fmt.Printf("其他类型: %T\n", t)
}
}

describe(42) // int: 42
describe("hi") // string: hi
describe(3.14) // 其他类型: float64

Go语言中”_“的作用

  1. 忽略返回值
  2. 忽略包内容,但是执行init

go内存泄漏

  1. Go goroutine泄漏

携程无法退出,内部的引用变量全部无法释放

  • **场景:**无缓冲channel读/写,对方不发消息,阻塞在条件变量一直等不到信号
  • 示例:ch := make(chan int); go(func(){<-ch})(),没人往 ch 发数据,协程永久挂起。 ✨排查:pprof goroutine查看大量阻塞协程栈
  1. 无用对象无法释放
  2. HTTP 请求不关闭 resp.Body

resp.Body没有defer resp.Body.Close(),底层连接和 buffer 无法回收,连接池堆积、内存上涨。