数组
- 数组的创建后,长度是固定的。
- 数组是值类型,数组的赋值都是拷贝。
- 数组的长度也是类型的一部分。[10]int,[20]int是不同的类型
go
package main
import (
"fmt"
)
func main() {
// 定义数组,数组是包含了数组长度和类型。
// 所以下面是两种不通的数组类型
var array1 [3]int
var array2 [4]int
// 数组的初始化
array1[0] = 10
array2[0] = 1
array2[1] = 2
// 使用数组的字面量创建数组
// 虽然长度是3,但是可以值给2个赋值,剩下的第三个元素默认为0
array3 := [3]int{100, 200}
// 可以推断数组长度,可以省略
array4 := [...]int{100, 200}
fmt.Println(array1, array2, array3, array4)
// 也可以根据索引,只给其中的某些指定的索引位赋值
array5 := [10]int{0: 12, 9: 100}
fmt.Println(array5)
// 如果自动推断数组长度,则根据最大的索引为数组的最大长度
// 这里最大的索引为3,则数组的长度为4
array6 := [...]int{0: 20, 3: 1}
fmt.Println(array6)
}
数组的遍历,多维数组
css
package main
import (
"fmt"
)
func main() {
// 遍历数组
var array1 = [10]int{}
for i := 0; i < 10; i++ {
array1[i] = i * 10
}
for i := 0; i < 10; i++ {
fmt.Println(array1[i])
}
// 多维数组
var array2 [3][2]int
// 这里大括号前面还得写类型
array2 = [3][2]int{{1, 2}, {2, 3}, {3, 4}}
for i := 0; i < 3; i++ {
for j := 0; j < 2; j++ {
fmt.Printf("%v", array2[i][j])
}
fmt.Println()
}
array3 := [3][2]int{{1, 2}, {2, 3}, {3, 4}}
for i := 0; i < 3; i++ {
for j := 0; j < 2; j++ {
fmt.Printf("%v", array3[i][j])
}
fmt.Println()
}
}
切片
go
package main
import (
"fmt"
)
func main() {
// 切片
// var name []type
var a1 []int
var a2 []string
// 声明切片并赋值
var a3 = []int{}
/*
0 true
0 true
0 false
*/
fmt.Println(len(a1), a1 == nil)
fmt.Println(len(a2), a2 == nil)
fmt.Println(len(a3), a3 == nil)
// 从数组或切片创建一个新的切片
// slice[beginIndex,endIndex]
var b1 = [5]int{1, 2, 3, 4, 5}
b2 := b1[1:2]
// 因为切片是引用类型,所以会修改原来数组的值
b2[0] = 100
fmt.Println(b1)
//如果不写beginIndex,则默认从开始
b3 := b1[:4]
// 如果不写endIndex,默认到最后
b4 := b1[1:]
// 如果都不写,则默认所有的值
b5 := b1[:]
/*
b3 [1 100 3 4]
b4 [100 3 4 5]
b5 [1 100 3 4 5]
*/
fmt.Printf("b3 %v\n", b3)
fmt.Printf("b4 %v\n", b4)
fmt.Printf("b5 %v\n", b5)
}
append 添加元素,返回新的切片
go
package main
import (
"fmt"
)
func main() {
// -------------- 第一段:触发扩容 → 互不影响 --------------
var a = []int{1, 2, 3} // len=3, cap=3
b := append(a, 20) // 触发扩容!新数组
b[0] = 100
fmt.Println("a =", a) // [1 2 3]
fmt.Println("b =", b) // [100 2 3 20]
fmt.Println("------------------------")
// -------------- 第二段:容量足够 → 共用底层数组 → 互相影响 --------------
var a1 = make([]int, 3, 10) // len=3, cap=10(容量非常足)
a1[0] = 1
a1[1] = 2
a1[2] = 3
// 不触发扩容!
b1 := append(a1, 20)
b1[0] = 100 // 修改的是**共用的底层数组**
fmt.Println("a1 =", a1) // [100 2 3] → 被改了!
fmt.Println("b1 =", b1) // [100 2 3 20]
}
内存机制:不扩容的时候,切片的地址还是之前的。扩容后,地址是会变的。
- 如果当前追加元素后,容量够,则不扩容
- 如果当前的容量不够,则容量不够,则容量翻倍。此时如果容量还不够,则直接用翻倍后的容量,再向上取2的幂的整数
- 如果之前的容量小于256,则容量直接翻倍,再向上取2的幂的整数。
- 如果之前的容量大于等于256,则每次增加(当前容量+ 3* 256)/4,一直到容量能够装下。再向上取2的幂的整数。
copy
go
package main
import "fmt"
func main() {
// 定义源切片 a,长度 4,容量 4
var a = []int{1, 2, 3, 4}
// 定义目标切片 b,长度 2,容量 2
b := make([]int, 2)
// 执行 copy:把 a 的元素复制到 b 中
// 规则:复制个数 = min(len(a), len(b)) = min(4,2) = 2 个
copy(b, a)
// 所以 b 只会拿到 a 的前 2 个元素:[1 2]
fmt.Println(b) // 输出:[1 2]
// ======================================
// 重点:copy 是深拷贝,两个切片底层数组完全独立
// ======================================
b[0] = 100 // 修改 b 的第一个元素
// a 不受任何影响,还是原来的数据
fmt.Println(a) // [1 2 3 4]
// b 已经被修改
fmt.Println(b) // [100 2]
}
删除
less
package main
import "fmt"
func main() {
// 初始切片 a:底层数组 [1,2,3,4],len=4,cap=4
var a = []int{1,2,3,4}
// ==============================================
// 1. 删除中间元素:append(a[:1], a[2:]...)
// ==============================================
// 内存行为:
// a[:1] len=1, cap=3(共享原数组)
// a[2:] 是切片,共享原数组
// append 发现 a[:1] 的容量足够(cap=3 >= 需要存3个元素)
// ✅ 【不分配新内存】【不创建新数组】,直接覆盖原数组!
// 原数组被覆盖成:[1,3,4,4]
b := append(a[:1], a[2:]...)
fmt.Println(a) // [1 3 4 4] 原数组被修改!
fmt.Println(b) // [1 3 4]
// 性能:O(k) k=复制元素数量,**无内存分配**,极快
// ==============================================
// 2. 重置切片,全新数组
// ==============================================
a = []int{1,2,3,4}
// ==============================================
// 删除索引=2的元素 3:a[:2 + copy(a[2:], a[3:])]
// ==============================================
// 内存行为:
// copy 是【原地覆盖】,把后面的元素往前挪
// 完全不创建新数组,不分配内存
// 数组变成:[1,2,4,4]
// copy 返回复制数量 1
// 最终切片取 a[:3],还是共享原数组
b1 := a[:2+copy(a[2:], a[3:])]
fmt.Println(a) // [1 2 4 4]
fmt.Println(b1) // [1 2 4]
// 性能:O(k) 【无内存分配】⭐⭐⭐⭐⭐
// Go 最快的【原地删除】写法,没有之一!
// ==============================================
// 重置切片
// ==============================================
a = []int{1,2,3,4}
// ==============================================
// 3. 删除开头:c := a[2:]
// ==============================================
// 内存行为:
// 只是创建新切片头,指针后移
// ✅ 【不复制数据】【不分配数组】
c := a[2:]
fmt.Println(c) // [3 4]
// 性能:O(1) 最快!⭐⭐⭐⭐⭐
// 缺点:共享底层数组,原切片大时可能内存泄漏
// ==============================================
// 4. 删除开头:append(a[:0], a[2:]...)
// ==============================================
// 内存行为:
// a[:0] len=0,cap=4(容量足够)
// append 直接【原地覆盖】原数组
// 不分配新数组!
// 数组变成 [3,4,3,4]
c1 := append(a[:0], a[2:]...)
fmt.Println(c1) // [3 4]
// 性能:O(k) 【无内存分配】,极快
// ==============================================
// 5. 删除开头:a[:copy(a, a[2:])]
// ==============================================
// 内存行为:
// copy 原地复制,【无任何新内存】
// 数组被覆盖成 [3,4,3,4]
c2 := a[:copy(a, a[2:])]
fmt.Println(c2) // [3 4]
// 性能:O(k) 【无内存分配】⭐⭐⭐⭐⭐
// 最推荐的安全、高效删除
// ==============================================
// 6. 删除尾部:d := a[:2]
// ==============================================
// 内存行为:
// 切片截取,【不修改底层数组】
d := a[:2]
fmt.Println(d) // [3 4]
// 性能:O(1)
}
映射map
go
package main
import (
"fmt"
)
func main() {
// 声明map
var map1 map[string]string
//初始化map
map1 = make(map[string]string)
map2 := map[string]string{"name": "libai", "addr": "china"}
fmt.Println(map1)
fmt.Println(map2)
// map的遍历
for index, value := range map2 {
fmt.Println(index, value)
}
// 删除元素
delete(map2, "name")
fmt.Println(map2)
}
列表list
go
package main
import (
"container/list"
"fmt"
)
func main() {
// 列表,内部实现是双链表
var list1 = list.New()
// 此时处于零值状态(延迟初始化)
var list2 list.List
// 【输出1】打印初始的空链表 list1 (指针类型,长度为0)
fmt.Println(list1)
// &{{0x5108dba780f0 0x5108dba780f0 <nil> <nil>} 0}
// 【输出2】打印零值状态的 list2 (非指针,内部全为nil,长度为0)
fmt.Println(list2)
// {{<nil> <nil> <nil> <nil>} 0}
hello := list1.PushBack("Hello") // 尾部插入 "Hello"
hi := list1.PushFront("hi") // 头部插入 "hi"
// 【输出3】打印插入两个元素后的 list1 (长度变为2)
fmt.Println(list1)
// &{{0x5108dba781b0 0x5108dba78180 <nil> <nil>} 2}
// 第一次遍历链表并打印元素的 Value
for i := list1.Front(); i != nil; i = i.Next() {
fmt.Println(i.Value)
}
// 【输出4】按插入顺序打印:先插的 hi 在头,后插的 Hello 在尾
// hi
// Hello
// 在 hello 节点之后插入 "China"
list1.InsertAfter("China", hello)
// 在 hi 节点之前插入 "你好"
list1.InsertBefore("你好", hi)
// 第二次遍历链表并打印元素的 Value
for i := list1.Front(); i != nil; i = i.Next() {
fmt.Println(i.Value)
}
// 【输出5】最终链表顺序:你好 -> hi -> Hello -> China
// 你好
// hi
// Hello
// China
}
删除元素
scss
//参数为element,每次插入会后返回这个element
list.Remove(hi)