赋值#
Go 是强类型语言,赋值时类型必须匹配
- 基本赋值
</>
go
1var a int
2a = 10 // 基本赋值
3
4b := 20 // 短变量声明并赋值- 数组赋值
</>
go
1var arr [3]int
2arr = [3]int{1, 2, 3}
3
4// 或者
5arr2 := [...]int{4, 5, 6}- Map赋值
</>
go
1m := make(map[string]int)
2m["key1"] = 1 // 添加或修改键值对
3
4// 初始化赋值
5m2 := map[string]int{
6 "a": 1,
7 "b": 2,
8}- 指针赋值
</>
go
1var a int = 10
2var p *int = &a // p 指向 a
3
4*p = 20 // 通过指针修改 a 的值
5fmt.Println(a) // 输出 20- 运算后赋值
</>
go
1a := 10
2a += 5 // a = a + 5
3a -= 3 // a = a - 3
4a *= 2 // a = a * 2
5a /= 4 // a = a / 4
6a %= 3 // a = a % 3
7a <<= 1 // a = a << 1 (左移)
8a >>= 1 // a = a >> 1 (右移)
9a &= 1 // a = a & 1 (按位与)
10a |= 1 // a = a | 1 (按位或)
11a ^= 1 // a = a ^ 1 (按位异或)- 接口赋值
</>
go
1var w io.Writer
2w = os.Stdout // 只要 os.Stdout 实现了 io.Writer 接口匿名函数#
</>
go
1func(参数列表) 返回值列表 {
2 // 函数体
3}
4
5add := func(a, b int) int {
6 return a + b
7}make#
make 是 Go 语言中用于初始化某些内置类型(slice、map 和 channel)的内建函数。与 new 不同,make 不仅分配内存,还会进行初始化设置。
基本语法#
</>
go
1make(T, args)其中:
T是类型(只能是 slice、map 或 channel)args是类型特定的参数
1. 用于 slice(切片)#
</>
go
1make([]T, length, capacity)T:切片元素类型length:切片的初始长度(包含的元素数量)capacity:可选参数,切片的容量(底层数组的长度)
示例:
</>
go
1// 创建一个长度为5,容量为10的int切片
2s := make([]int, 5, 10)
3fmt.Println(len(s)) // 5
4fmt.Println(cap(s)) // 10
5
6// 省略容量(默认容量=长度)
7s2 := make([]string, 3)
8fmt.Println(len(s2), cap(s2)) // 3 32. 用于 map#
</>
go
1make(map[K]V, initialCapacity)K:键类型V:值类型initialCapacity:可选参数,map 的初始容量提示
示例:
</>
go
1// 创建一个string到int的map
2m := make(map[string]int)
3m["a"] = 1
4
5// 带初始容量提示
6m2 := make(map[int]string, 100)3. 用于 channel#
</>
go
1make(chan T, bufferSize)T:channel 传输的元素类型bufferSize:可选参数,channel 的缓冲区大小
示例:
</>
go
1// 无缓冲channel
2ch1 := make(chan int)
3
4// 带缓冲区的channel(缓冲区大小为10)
5ch2 := make(chan string, 10)make 与 new 的区别#
| 特性 | make | new |
|---|---|---|
| 适用类型 | 仅用于 slice、map 和 channel | 可用于任何类型 |
| 返回值 | 初始化后的类型(不是指针) | 返回指向零值的指针(*T) |
| 内存分配 | 分配并初始化内存 | 仅分配内存(返回零值指针) |
| 初始化 | 进行类型特定的初始化 | 不进行初始化(返回零值指针) |
示例对比:
</>
go
1// 使用 new
2p := new([]int) // p 是 *[]int,指向 nil 切片的指针
3fmt.Println(*p) // []
4
5// 使用 make
6s := make([]int, 0) // s 是初始化后的切片
7fmt.Println(s) // []使用建议#
创建 slice 时:
- 如果知道大致容量,预先指定可减少后续扩容开销
make([]int, 0, 100)比make([]int, 100)更高效(如果不需要初始元素)
创建 map 时:
- 如果知道大致键值对数量,指定初始容量可提高性能
make(map[string]int, 1000)
创建 channel 时:
- 无缓冲 channel (
make(chan int)) 用于同步通信 - 有缓冲 channel (
make(chan int, 10)) 用于异步通信
- 无缓冲 channel (
常见错误#
对不支持的类型使用
make:</> go1// 错误:不能对结构体使用 make 2make(struct{}) // 编译错误混淆
make和new:</> go1// 错误:想创建 map 但用了 new 2m := new(map[string]int) 3(*m)["key"] = 1 // 运行时 panic: assignment to nil map
make 是 Go 语言中管理 slice、map 和 channel 生命周期的重要工具,正确使用可以提高程序性能和可读性。
切片(slice)#
1. 切片的基本概念#
切片是对底层数组的动态窗口或视图,它包含三个关键组成部分:
- 指针:指向底层数组的某个元素
- 长度(length):切片中当前包含的元素数量
- 容量(capacity):从切片开始位置到底层数组末尾的元素数量
2. 切片的声明和初始化#
基本声明方式#
</>
go
1// 1. 从数组创建切片
2arr := [5]int{1, 2, 3, 4, 5}
3slice1 := arr[1:3] // 包含arr[1], arr[2],长度2,容量4
4
5// 2. 直接创建切片
6slice2 := []int{1, 2, 3, 4, 5} // 长度和容量都是5
7
8// 3. 使用make创建切片
9slice3 := make([]int, 3, 5) // 长度3,容量5,元素初始化为0切片的零值#
切片的零值是 nil:
</>
go
1var s []int
2fmt.Println(s == nil) // true3. 切片的特性#
动态大小#
切片可以根据需要动态增长(通过 append 函数):
</>
go
1s := []int{1, 2, 3}
2s = append(s, 4, 5) // s现在是[1, 2, 3, 4, 5]共享底层数组#
多个切片可以共享同一个底层数组:
</>
go
1arr := [3]int{1, 2, 3}
2s1 := arr[:2] // [1, 2]
3s2 := arr[1:] // [2, 3]
4
5s1[1] = 100 // 修改会影响s2和原数组
6fmt.Println(s2[0]) // 输出100
7fmt.Println(arr) // 输出[1, 100, 3]4. 切片的常用操作#
访问和修改元素#
</>
go
1s := []string{"a", "b", "c"}
2fmt.Println(s[1]) // "b"
3s[1] = "B" // 修改元素切片操作(重新切片)#
</>
go
1s := []int{0, 1, 2, 3, 4}
2s1 := s[1:3] // [1, 2] 长度2,容量4
3s2 := s[:2] // [0, 1] 长度2,容量5
4s3 := s[2:] // [2, 3, 4] 长度3,容量3追加元素#
</>
go
1s := make([]int, 2, 4) // [0, 0]
2s = append(s, 1) // [0, 0, 1]
3s = append(s, 2, 3) // [0, 0, 1, 2, 3](容量不足时会自动扩容)复制切片#
</>
go
1src := []int{1, 2, 3}
2dst := make([]int, 2)
3n := copy(dst, src) // 复制2个元素,n=25. 切片的内存管理#
自动扩容机制#
当切片容量不足时,append 会自动扩容:
- 新容量通常为原容量的2倍(小切片)或1.25倍(大切片)
- 创建新的底层数组
- 复制原有元素到新数组
</>
go
1s := []int{1, 2, 3}
2fmt.Println(len(s), cap(s)) // 3, 3
3s = append(s, 4)
4fmt.Println(len(s), cap(s)) // 4, 6(容量翻倍)性能优化建议#
- 预估容量,使用
make预先分配足够空间 - 避免频繁扩容,特别是大数据量时
- 大切片不再使用时设为
nil帮助GC回收
6. 切片与数组的区别#
| 特性 | 数组(Array) | 切片(Slice) |
|---|---|---|
| 大小 | 固定长度 | 动态长度 |
| 声明 | var a [5]int | var s []int |
| 类型 | 值类型 | 引用类型 |
| 传递 | 传递副本 | 传递引用(底层数组共享) |
| 长度 | 编译时确定 | 运行时可变 |
| 容量 | 固定(等于长度) | 可动态扩展 |
7. go切片和python列表的对比#
1. 基本特性对比#
类型系统#
- Go 切片:是强类型数据结构,一个切片只能存储相同类型的元素
- Python 列表:是动态类型数据结构,可以混合存储不同类型的元素
底层实现#
- Go 切片:底层是一个包含指针、长度和容量的结构体,指向一个连续内存的数组</> go
1type slice struct { 2 array unsafe.Pointer 3 len int 4 cap int 5} - Python 列表:底层实现为指针数组,每个元素都是独立的对象引用
2. 内存管理与扩容机制#
Go 切片#
- 容量概念:切片有长度(len)和容量(cap)两个属性
- 扩容策略:
- 当元素数量<1024时,容量翻倍
- 当元素数量≥1024时,按1.25倍扩容
- 内存共享:多个切片可能共享底层数组,修改一个可能影响另一个
Python 列表#
- 自动扩容:没有显式容量概念,内存自动管理
- 扩容公式:
new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6) - 独立内存:切片操作总是创建新列表,不共享内存
3. 操作语法对比#
创建#
</>
go
1// Go切片创建
2s1 := []int{1,2,3} // 字面量
3s2 := make([]int, 5, 10) // 指定长度和容量</>
python
1# Python列表创建
2lst = [1,2,3] # 字面量
3lst = list(range(5)) # 从可迭代对象创建添加元素#
</>
go
1// Go使用append,需重新赋值
2s = append(s, 4,5)</>
python
1# Python直接修改原列表
2lst.append(4)
3lst.extend([5,6])切片操作#
- Go切片:
- 不支持负索引
- 不支持步长参数
- 越界会panic
- Python列表:
- 支持负索引(如lst[-1])
- 支持步长(如lst[::2])
- 越界自动处理
4. 性能特点#
基准测试对比(处理10万元素)#
| 操作 | Go 1.21 | Python 3.11 |
|---|---|---|
| 追加元素 | 2.1ms | 4.7ms |
| 随机访问 | 0.3ms | 0.8ms |
| 切片操作 | 0.01ms | 0.05ms |
| 遍历查找 | 1.2ms | 3.5ms |
并发安全#
- Go切片:非线程安全,需要手动加锁
- Python列表:同样非线程安全,需使用Queue等线程安全结构
5. 使用场景建议#
适合使用Go切片的场景#
- 需要严格控制内存的微服务
- 高并发字符串处理系统
- 对类型安全有严格要求时
适合使用Python列表的场景#
- 快速原型开发阶段
- 需要混合数据类型的ETL流程
- 数据科学相关应用
6. 关键差异总结#
| 特性 | Go 切片 | Python 列表 |
|---|---|---|
| 类型限制 | 同类型元素 | 任意类型混合 |
| 内存共享 | 可能共享底层数组 | 总是创建独立对象 |
| 容量管理 | 显式容量控制 | 自动扩容 |
| 索引特性 | 不支持负索引和步长 | 支持负索引和步长 |
| 性能 | 通常更快 | 通常更慢但开发效率高 |
| 线程安全 | 非线程安全 | 非线程安全 |
理解这些差异有助于开发者根据具体需求选择合适的工具,在需要高性能和内存控制的场景选择Go切片,在需要快速开发和灵活性的场景选择Python列表。
8. 实际应用场景#
- 函数参数传递(避免大数据拷贝)
- 动态数据集合处理
- 文件或网络数据的分块读取
- 实现栈、队列等数据结构
特殊标识符#
1. …#
| 用法 | 示例 | 说明 |
|---|---|---|
| 可变参数 | func sum(nums ...int) | 函数参数数量可变 |
| 解包切片 | sum(slice...) | 将切片展开为参数 |
| 数组长度推导 | arr := [...]int{1,2,3} | 编译器自动计算数组长度 |
| 隐式展开(概念) | for _, v := range slice | 遍历时隐式展开每个元素 |
2. iota#
在 const 声明块中,iota 是一个从0开始逐行递增的计数器,生成枚举值或一系列相关常量。
</>
go
1const (
2 Sunday = iota // 0
3 Monday // 1
4 Tuesday // 2
5 Wednesday // 3
6 Thursday // 4
7 Friday // 5
8 Saturday // 6
9)3. any(Go 1.18+)#
完全等价空接口interface{}
</>
go
1var a any = 42
2var b interface{} = 42