Skip to content

集合类型

固定长度,在Go中数组是值类型:

// 声明数组
var arr [5]int // [0, 0, 0, 0, 0]
arr[0] = 10
// 初始化
arr2 := [5]int{1, 2, 3, 4, 5}
// 自动计算长度
arr3 := [...]int{1, 2, 3} // [3]int{1, 2, 3}
// 指定索引初始化
arr4 := [5]int{0: 1, 2: 3} // [1, 0, 3, 0, 0]

数组是值类型:

arr1 := [3]int{1, 2, 3}
arr2 := arr1 // 复制整个数组
arr2[0] = 100
fmt.Println(arr1) // [1, 2, 3](未变)
fmt.Println(arr2) // [100, 2, 3]

Python列表是引用类型:

arr1 = [1, 2, 3]
arr2 = arr1
arr2[0] = 100
print(arr1) # [100, 2, 3](原数组被修改)

动态大小,是数组的视图。Go最常用的集合类型:

// 创建切片
slice := []int{1, 2, 3} // 字面量
slice := make([]int, 5) // [0, 0, 0, 0, 0],len=5, cap=5
slice := make([]int, 0, 10) // len=0, cap=10
// 从数组创建切片
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4] // [2, 3, 4](arr[1], arr[2], arr[3])
// 省略起始/结束索引
slice := arr[:3] // [1, 2, 3]
slice := arr[2:] // [3, 4, 5]
slice := arr[:] // [1, 2, 3, 4, 5]
slice := []int{1, 2, 3}
// append添加元素
slice = append(slice, 4) // [1, 2, 3, 4]
slice = append(slice, 5, 6) // [1, 2, 3, 4, 5, 6]
// len和cap
fmt.Println(len(slice)) // 6
fmt.Println(cap(slice)) // 6(或更大,取决于实现)
// 遍历
for i, v := range slice {
fmt.Printf("%d: %d\n", i, v)
}
# 列表基本操作
lst = [1, 2, 3]
lst.append(4) # [1, 2, 3, 4]
len(lst) # 4
# 切片
lst[1:3] # [2, 3]
lst[:3] # [1, 2, 3]
lst[2:] # [3, 4]

切片是三个字段的结构体:

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

当对切片进行操作时:

  • len表示可见元素数量
  • cap表示底层数组容量
  • 当len==cap时,append会分配新数组
slice := make([]int, 0, 3) // len=0, cap=3
slice = append(slice, 1) // len=1, cap=3(还有空间)
slice = append(slice, 2) // len=2, cap=3(还有空间)
slice = append(slice, 3) // len=3, cap=3(刚好满)
slice = append(slice, 4) // len=4, cap=6(扩容,新建数组)

扩容策略:

  • 小切片(cap小於1024):约1.5倍增长
  • 大切片(cap大於等於1024):约1.25倍增长

多个切片可以共享同一个底层数组:

arr := [5]int{1, 2, 3, 4, 5}
s1 := arr[0:3] // [1, 2, 3]
s2 := arr[1:4] // [2, 3, 4]
s1[1] = 20 // 修改底层数组
fmt.Println(arr) // [1, 20, 3, 4, 5]
fmt.Println(s1) // [1, 20, 3]
fmt.Println(s2) // [20, 3, 4](受影响)
src := []int{1, 2, 3}
dst := make([]int, len(src))
// 复制元素
copy(dst, src)
// 修改src不影响dst
src[0] = 100
fmt.Println(dst) // [1, 2, 3]

1. 重复append导致多次分配

// 不好:每次append可能扩容
var slice []int
for i := 0; i < 1000; i++ {
slice = append(slice, i)
}
// 好:预估容量
slice := make([]int, 0, 1000)
for i := 0; i < 1000; i++ {
slice = append(slice, i)
}

2. 在函数中修改切片

func badAppend(slice []int) {
slice = append(slice, 100) // 修改的是副本
}
func goodAppend(slice *[]int) {
*slice = append(*slice, 100) // 修改原切片
}
// make:创建切片、map、channel(返回的是这些类型本身)
s := make([]int, 10) // len=10, cap=10, 元素为0
// new:分配内存,返回指针(用于任何类型)
p := new([]int) // *[]int,值为nil
p2 := new([5]int) // *[5]int,指向新数组

// 使用make
m := make(map[string]int)
// 字面量初始化
m := map[string]int{
"Alice": 30,
"Bob": 25,
"Carol": 35,
}
// 空map
var m map[string]int // nil map(不能直接操作)
m = make(map[string]int) // 初始化后才能使用
m := map[string]int{
"Alice": 30,
"Bob": 25,
}
// 添加/修改
m["Dave"] = 40
// 读取
age := m["Alice"] // 30
age = m["Eve"] // 0(键不存在,返回零值)
// 检查键是否存在
age, ok := m["Eve"]
if ok {
fmt.Println("存在:", age)
} else {
fmt.Println("不存在")
}
// 删除
delete(m, "Bob")
// 检查长度
fmt.Println(len(m)) // 3
m = {"Alice": 30, "Bob": 25}
# 添加/修改
m["Dave"] = 40
# 读取
age = m["Alice"] # 30
age = m.get("Eve", 0) # 0(默认值)
# 删除
del m["Bob"]
# 检查键
if "Eve" in m:
print(m["Eve"])
# 长度
len(m) # 3
var m map[string]int // nil map
// 读取nil map是安全的
fmt.Println(m["key"]) // 0
// 写入nil map会panic
// m["key"] = 1 // panic
// 检查
if m == nil {
fmt.Println("map是nil,需要初始化")
m = make(map[string]int)
}

多个goroutine同时读写map需要加锁:

var mu sync.RWMutex
var m = make(map[string]int)
// 读(可以并发)
mu.RLock()
age := m["Alice"]
mu.RUnlock()
// 写(需要独占)
mu.Lock()
m["Bob"] = 30
mu.Unlock()

Go 1.9+有sync.RWMutex,Go 1.21+实验性sync.Map(适合读多写少场景)。


slice := []int{1, 2, 3}
// 索引和值
for i, v := range slice {
fmt.Printf("%d: %d\n", i, v)
}
// 仅索引
for i := range slice {
fmt.Println(i)
}
// 仅值(索引丢弃)
for _, v := range slice {
fmt.Println(v)
}
m := map[string]int{
"Alice": 30,
"Bob": 25,
"Carol": 35,
}
// 键和值
for k, v := range m {
fmt.Printf("%s: %d\n", k, v)
}
// 仅键
for k := range m {
fmt.Println(k)
}
// 仅值(需要用map[key]获取)
for _, v := range m {
fmt.Println(v)
}

遍历顺序不确定:Map是无序的,每次遍历顺序可能不同。

// 排序遍历
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Printf("%s: %d\n", k, m[k])
}
str := "Hello世界"
// 索引和rune
for i, r := range str {
fmt.Printf("%d: %c (U+%04X)\n", i, r, r)
}
// 输出:
// 0: H (U+0048)
// 1: e (U+0065)
// 2: l (U+006C)
// 3: l (U+006C)
// 4: o (U+006F)
// 6: 世 (U+4E16)
// 9: 界 (U+754C)

注意:中文占3字节,所以下一个字符的索引是6而不是5。

str := "Hello世界"
fmt.Println(len(str)) // 13(字节数)
fmt.Println(utf8.RuneCountInString(str)) // 8(字符数)
// 逐字节遍历
for i := 0; i < len(str); i++ {
fmt.Printf("%x ", str[i]) // 48 65 6c 6c 6f e4 b8 96 e7 95 8c
}
// 逐rune遍历
for _, r := range str {
fmt.Printf("%c ", r) // H e l l o 世 界
}
numbers := []int{1, 2, 3, 4, 5, 6}
// 过滤偶数
result := []int{}
for _, n := range numbers {
if n%2 == 0 {
result = append(result, n)
}
}
fmt.Println(result) // [2, 4, 6]
// 使用append的简洁写法
result = result[:0] // 清空slice但保留底层数组
for _, n := range numbers {
if n%2 == 0 {
result = append(result, n)
}
}

PythonGo
listslice([]T)
[1, 2, 3][]int{1, 2, 3}
arr[1:3]arr[1:4](索引是结束位置+1)
list.append(x)slice = append(slice, x)
dictmap[K]V
d["key"] / d.get("key")m["key"] / age, ok := m["key"]
del d["key"]delete(m, "key")
for i, v in enumerate(lst):for i, v := range lst { }
for k, v in d.items():for k, v := range m { }

  1. 创建切片,使用append添加10个元素
  2. 写函数从切片中间删除一个元素
  3. 创建map,练习添加、读取、删除操作
  4. 实现一个统计单词频率的程序
  5. 写函数过滤切片中的负数,返回新切片