go语言中的数组
在Go语言中数组是具有固定长度的同类型元素的序列。与其他语言不同在Go语言中,数组长度是类型的一部分。例如[5]int和[4]int 不是同一种类型,相同类型的数组可以直接比较是否相等,不需要跟其他语言一样遍历去比较值。
以下是一些数组的初始化方式及其使用方法:
数组的初始化
1.使用 var 声明并初始化
// 声明一个长度为5的int类型数组
var arr1 [5]int
fmt.Println("未初始化的数组:", arr1)
// 单独设置值
arr1[0] = 1
arr1[1] = 2
arr1[2] = 3
fmt.Println("设置值后的数组:", arr1)
2. 使用数组字面量初始化
// 使用数组字面量初始化
arr2 := [5]int{1, 2, 3, 4, 5}
fmt.Println("初始化的数组:", arr2)
3. 自动推断数组长度
// 让编译器自动推断数组长度 arr3 := [...]int{1, 2, 3, 4, 5}
fmt.Println("自动推断长度的数组:", arr3)
4. 部分初始化
// 只初始化部分元素,未初始化的元素将默认设置为零值
arr4 := [5]int{1, 2}
fmt.Println("部分初始化的数组:", arr4)
数组的使用
1. 访问和修改数组元素
arr := [5]int{1, 2, 3, 4, 5}
// 访问数组元素
fmt.Println("数组的第三个元素:", arr[2])
// 修改数组元素
arr[2] = 10
fmt.Println("修改后的数组:", arr)
2. 遍历数组
使用 for 循环
arr := [5]int{1, 2, 3, 4, 5}
// 使用索引遍历数组
for i := 0; i < len(arr); i++ {
fmt.Println("元素", i, ":", arr[i])
}
使用 for range 循环
arr := [5]int{1, 2, 3, 4, 5}
// 使用 range 遍历数组
for index, value := range arr {
fmt.Println("索引", index, "的值:", value)
}
多维数组
在Go语言中,多维数组可以使用嵌套数组的形式来实现
// 定义二维数组
var arr2D [3][3]int = [3][3]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
// 部分初始化的二维数组
var arr2D = [3][3]int{
{1, 2}, // 第三个元素默认为0
{4, 5, 6}, // 完全初始化
{}, // 所有元素默认为0
}
// 未初始化的二维数组
var arr2D [3][3]int
// 定义一个2x3的二维数组
var arr2D [2][3]int
arr2D[0][0] = 1
arr2D[0][1] = 2
arr2D[0][2] = 3
arr2D[1][0] = 4
arr2D[1][1] = 5
arr2D[1][2] = 6
// 遍历二维数组
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
fmt.Print(arr2D[i][j], " ")
}
fmt.Println()
}
数组比较
Go语言中的数组可以直接使用 == 运算符进行比较,但要注意:只有相同类型和相同长度的数组才能进行比较。
arr1 := [3]int{1, 2, 3}
arr2 := [3]int{1, 2, 3}
arr3 := [3]int{4, 5, 6} f
mt.Println("arr1 == arr2:", arr1 == arr2) // true
fmt.Println("arr1 == arr3:", arr1 == arr3) // false
}
go语言中的切片
在Go语言中,切片(slice)是一种比数组更灵活、更强大的数据类型,切片底层是一个结构体,结构体包含一个指向数组的指针,提供了更灵活且强大的接口,具有可变长度的精度。
切片的定义方式
1.切片的声明
声明一个切片不指定长度,此时只可以用append追加,不可以直接赋值 例如:s[1] = 10
var s []int
2.使用内置函数 make 构造切片
s := make([]int, 5) // 创建一个长度为5的切片,初始值为零值
s := make([]int, 5, 10) // 创建一个长度为5,容量为10的切片
3.通过字面量初始化切片
s := []int{1, 2, 3, 4, 5}
4.基于数组创建切片
在基于数组创建切片的时候,本质上切片和数组还是共用一块内存空间,如果对数组或者切片修改,另一方也会修改,除非切片追加发生扩容,扩容之后就不再共用一块内存空间。
arr := [5]int{1, 2, 3, 4, 5}
s := arr[1:4] // s的内容是[2 3 4]
切片的使用
1.访问和修改切片元素
s := []int{1, 2, 3}
fmt.Println(s[0]) // 访问第一个元素,输出: 1
s[1] = 10 // 修改第二个元素
fmt.Println(s) // 输出: [1 10 3]
2.追加元素
s := []int{1, 2, 3}
s = append(s, 4, 5) // 追加元素4和5
fmt.Println(s) // 输出: [1 2 3 4 5]
3.切片操作
这里新老切片会共用一块内存,除非有一方扩容了。
s := []int{1, 2, 3, 4, 5}
s1 := s[1:3] // 创建新切片,包含s的第1到第2个元素(不包括第3个元素)
fmt.Println(s1) // 输出: [2 3]
4.复制切片 这里的复制是真的底层复制,两个切片是分开指向不同的内存。
src := []int{1, 2, 3}
dest := make([]int, len(src))
copy(dest, src) // 将src复制到dest
fmt.Println(dest) // 输出: [1 2 3]
5.切片的删除 由于 Go 中的切片并没有提供直接的删除方法,所以我们通常通过切片的重新分片操作来实现元素的删除。
1.删除指定索引的元素
1.使用append函数
使用切片的重新分片和 append 函数来实现删除。
s = append(s[:index], s[index+1:]...)
... 是表示变参(variadic parameter),即把 s[index+1:] 切片中的所有元素都作为单独的参数传递给 append 函数。
2.使用copy函数
s := []int{1, 2, 3, 4, 5}
index := 2
copy(s[index:], s[index+1:])
s = s[:len(s)-1]
fmt.Println(s) // 输出: [1 2 4 5]
这种方法适合在性能敏感的场景,避免了 append 的开销
2.删除切片中的多个元素
1.删除连续的函数
假设我们要删除从索引 start 到 end (不包括 end) 的元素:
s := []int{1, 2, 3, 4, 5, 6, 7}
start, end := 2, 5
s = append(s[:start], s[end:]...)
fmt.Println(s) // 输出: [1 2 6 7]
2.删除不连续的元素 如果需要删除的元素不连续,可以遍历切片并生成一个新的切片:
s := []int{1, 2, 3, 4, 5, 6, 7}
toDelete := map[int]bool{1: true, 3: true, 5: true}
result := s[:0] // 保持相同的底层数组
for i, v := range s {
if !toDelete[i] {
result = append(result, v)
}
}
s = result
fmt.Println(s) // 输出: [1 3 5 7]
切片的重要特性和需要注意的点
1.切片的长度和容量
- 切片具有长度(len)和容量(cap)。长度表示切片中的元素个数,容量表示从切片起始位置到底层数组末尾的元素个数。
s := []int{1, 2, 3, 4, 5}
fmt.Println(len(s)) // 输出: 5
fmt.Println(cap(s)) // 输出: 5
2.切片共享底层数组
- 切片是对底层数组的引用,多个切片可以共享相同的底层数组。
arr := [5]int{1, 2, 3, 4, 5}
s1 := arr[1:4]
s2 := arr[2:5]
s1[1] = 10 // 修改s1也会影响s2,因为它们共享底层数组
fmt.Println(s1) // 输出: [2 10 4]
fmt.Println(s2) // 输出: [10 4 5]
3.切片在追加元素时可能引发重新分配
- 当使用
append函数向切片追加元素时,如果切片的容量不足,会导致底层数组重新分配,并复制原切片中的元素到新的数组中。
s := []int{1, 2, 3}
s = append(s, 4, 5, 6, 7, 8, 9)
fmt.Println(s) // 输出: [1 2 3 4 5 6 7 8 9]
4.避免切片的内存泄漏
- 因为切片持有对底层数组的引用,如果不小心引用了数组的大部分但只用了其中的一部分,可能会导致内存泄漏问题。
- 切片删除操作不会自动回收底层数组的空间,如果需要释放多余的内存,可以考虑将数据复制到新的切片中。
arr := make([]int, 10000)
largeSlice := arr[:1000]
smallSlice := arr[:3] // smallSlice 会引用整个底层数组,这样会影响垃圾回收
// 解决办法是复制需要的部分到新的切片中
smallSliceCopy := make([]int, len(smallSlice))
copy(smallSliceCopy, smallSlice)
go语言中的map
在 Go 语言中,map 是一种内建的数据类型,用于存储键值对。
定义和初始化方式
1.使用 make 函数初始化
m := make(map[string]int) // 创建一个键为 string 类型,值为 int 类型的 map
2.直接初始化
m := map[string]int{
"one": 1,
"two": 2,
}
m1:=map[string]int{}//初始化一个空的
3.声明而不初始化
需要注意的是,这种方式声明的 map 在使用之前必须先初始化,否则会发生panic错误。
var m map[string]int
基本使用方法
1.插入元素
m := make(map[string]int)
m["one"] = 1
m["two"] = 2
2.读取元素
val := m["one"]
3.检查键是否存在
val, ok := m["one"]
if ok {
fmt.Println("Key exists:", val)
} else {
fmt.Println("Key does not exist")
}
4.删除元素
delete(m, "one")
5.遍历 map
for key, value := range m {
fmt.Println(key, value)
}
注意事项
1.零值
声明但未初始化的 map 的零值是 nil。对未初始化的 map 进行读、写操作会导致运行时错误(panic)。
m["one"] = 1 // 运行时错误
2.并发读写 Go 中的 map 不是线程安全的。因此,如果在多个 goroutine 中读写同一个 map,必须使用同步机制(如 sync.Mutex)保护它。
var mu sync.Mutex
m := make(map[string]int)
go func() {
mu.Lock()
m["one"] = 1
mu.Unlock()
}()
go func() {
mu.Lock()
val := m["one"]
mu.Unlock()
fmt.Println(val)
}()
3.存储值类型
map 的值可以是任意类型,包括另一个 map、切片等复杂数据类型,但是要注意值类型的零值和默认初始化问题
4.不能作为 map 键的类型
以下类型不能作为 map 的键:
- 切片(slice)
- 映射(map)
- 函数(func)
这些类型都是引用类型,它们在 Go 中无法使用等号(==)进行比较,因此不能用作 map 的键。
go语言中的List
在 Go 语言中,list 数据结构没有像数组和切片那样的内建类型。但 Go 标准库中提供了 container/list 包,可以用来实现双向链表。
1.定义和初始化方式
1.作import声明
要使用 list,首先要导入 container/list 包:
import (
"container/list"
)
2.创建一个新列表
使用 list.New() 函数或直接声明并初始化 list.List 结构体:
var l list.List
2.基本使用方法
1.插入元素 在列表末尾插入元素:
l.PushBack("element")
在列表头部插入元素:
l.PushFront("element")
在指定元素后插入:
e := l.PushBack("first")
l.InsertAfter("second", e)
在指定元素后插入:
e := l.PushBack("first")
l.InsertAfter("second", e)
在指定元素前插入:
e := l.PushBack("first")
l.InsertBefore("second", e)
2.读取元素 遍历列表中的所有元素:
for e := l.Front(); e != nil; e = e.Next() {
fmt.Println(e.Value)
}
3.删除元素
删除列表中的指定元素需要持有元素的指针 *list.Element:
e := l.PushBack("element")
l.Remove(e)
4.访问头和尾元素 获取列表的头元素:
head := l.Front()
fmt.Println(head.Value)
获取列表的尾元素:
tail := l.Back()
fmt.Println(tail.Value)
注意事项
1.安全性
在遍历或修改列表时,需要注意并发安全性。如果一个列表会被多个 goroutine 并发访问,需要使用同步机制(如 sync.Mutex)保护它。
2.列表与切片选择
Go 语言内建的切片通常更高效,且更容易使用。如果不特别需要链表的特性(如在列表的两端频繁插入或删除),优先使用切片。
3.类型安全
container/list 是一个非类型安全的通用容器,因此 list.Element 的 Value 字段是 interface{} 类型。使用时需做好类型断言和检查,避免程序错误。
总结
数组(Array)
- 定义:固定长度的同类型元素集合。
- 初始化:长度在定义时就确定并且不能改变。
- 优点:内存连续分配,访问速度快。
- 缺点:长度固定,使用不够灵活。
- 适用场景:元素数量固定且不需要改变长度的场景。
切片(Slice)
- 定义:可变长度的数组视图,底层是数组。
- 初始化:可以基于数组、字面量或使用
make函数创建。 - 优点:灵活、动态调整长度,支持切片操作。
- 缺点:底层数组在扩容时可能会分配新的存储空间,引起性能开销。
- 适用场景:需要动态调整大小的集合集合操作。
Map
- 定义:键值对存储的哈希表。
- 初始化:使用
make函数或字面量创建。 - 优点:快速查找、插入和删除操作。
- 缺点:键必须是可比较类型,并发读写需要加锁。
- 适用场景:需要通过键快速查找和存储值的场景。
List(链表)
- 定义:双向链表(在
container/list包中实现)。 - 初始化:使用
list.New()函数创建。 - 优点:在已知位置插入和删除元素效率高。
- 缺点:顺序访问性能较低,元素类型非类型安全(存储为
interface{})。 - 适用场景:需要在头尾频繁插入和删除元素的场景。
切片 vs Map
- 访问方式:切片通过索引访问,保持顺序;Map 通过键访问,无序。
- 适用场景:切片适用于顺序数据;Map 适用于键值对存储。
提醒:
切片是值类型,不是引用类型,切片的底层是一个结构体,结构体包含一个指向数组的指针。 go语言只有值传递,没有引用传递。 当我们去获取一个不在map中的key时,得到的是value的零值。