切片
Go 语言的切片是对数组的抽象
可以理解为动态数组, 切片的长度不固定, 可以追加元素, 在追加时可能使切片容量增大
切片本身没有任何数据, 他们只是对现有数组的引用 (底层为数组, 指向数组, 即内存地址)
package main
import "fmt"
// 定义切片
func main() {
var arr1 = [4]int{1, 2, 3, 4}
var s1 []int // 长度可变
fmt.Println(s1)
if s1 == nil {
fmt.Println("s1为空切片~")
}
s2 := []int{1, 2, 3, 4, 5}
fmt.Println(s2)
fmt.Printf("%T,%T\n", arr1, s2) // 查看变量的类型 打印结果: [4]int,[]int
}
make 创建
使用make 可以创建切片 map ...
创建的时候可以指定大小and容量~ 容量可以理解为最大长度
package main
import "fmt"
func main() {
// make来创建切片
s1 := make([]int, 2, 3)
fmt.Println(s1) // int默认值为0 打印结果 [0 0]
fmt.Println(len(s1)) // 2
fmt.Println(cap(s1)) // 3
// 容量为10, 长度为5, 不可以存放6个数据 s1[6] = x 会出错
// 追加 扩容 -> append
s2 := make([]int, 0, 5)
// 切片扩容 append
s2 = append(s2, 1, 2)
fmt.Println(s2) // [1 2]
s2 = append(s2, 2, 3, 23, 2, 3, 2, 2)
fmt.Println(s2) // 可以自动扩容 [1 2 2 3 23 2 3 2 2 100 200 300]
s3 := []int{100, 200, 300}
// 当然了 也可以追加切片 但需要解构其中的数据 (可以有多个以逗号分隔)
// 如 s3... = {100, 200, 300}
s2 = append(s2, s3...)
fmt.Println(s2) // [1 2 2 3 23 2 3 2 2 100 200 300]
// 遍历切片与数组一样
}
扩容的内存分析
package main
import "fmt"
// 每个切片引用了一个底层的数组, 切片本身不存储数据, 是底层的数组来存储的 (切片相当于指针, 指向数组)
// 自动扩容: cap 0 ~ 1024 是 成倍增加 1024之后是 增加1.2倍
func main() {
s1 := []int{1, 2, 3}
fmt.Println(s1)
fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:3,cap:3
fmt.Printf("%p\n", s1) // 查看地址 0xc000018150
s1 = append(s1, 4, 5)
fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:5,cap:6
fmt.Printf("%p\n", s1) // 查看地址 0xc000012570 发现新增数据后切片容量翻倍 and 地址变了
s1 = append(s1, 6, 7, 8)
fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:8,cap:12
fmt.Printf("%p\n", s1) // 0xc0000165a0
s1 = append(s1, 9, 10, 11)
fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:11,cap:12
fmt.Printf("%p\n", s1) // 0xc0000165a0 (容量变了, 地址也变了; 容量不变, 地址不变)
s1 = append(s1, 12, 13)
fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:13,cap:24 (每次扩容 cap是成倍增加的)
fmt.Printf("%p\n", s1) // 0xc0000165a0
}
package main
import "fmt"
func main() {
// 数组
arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
// 通过数组创造切片 [start,end)
s1 := arr[:5]
s2 := arr[3:8] // 截取 4 5 6 7 8
s3 := arr[4:] // 从第5个元素 一直到最后
s4 := arr[:] // 数组的全部~
fmt.Println("s1:", s1)
fmt.Println("s2:", s2)
fmt.Println("s3:", s3)
fmt.Println("s4:", s4)
// len是按照截取的长度而定, cap是从截取的开头到(数组的)结尾
fmt.Printf("s1, len=%d, cap=%d\n", len(s1), cap(s1)) // len=5, cap=10
fmt.Printf("s2, len=%d, cap=%d\n", len(s2), cap(s2)) // len=5, cap=7
fmt.Printf("s3, len=%d, cap=%d\n", len(s3), cap(s3)) // len=6, cap=6
fmt.Printf("s4, len=%d, cap=%d\n", len(s4), cap(s4)) // len=10, cap=6
// 查看内存地址
fmt.Printf("%p\n", &arr) // 原始数组
fmt.Printf("%p\n", s1) // 指向原数组 0xc00001e370 (不影响顺序)
fmt.Printf("%p\n", s2) // 0xc00001e388
fmt.Printf("%p\n", s3) // 0xc00001e390 中间截断之后 开辟另一块内存空间(生成新的数组)
fmt.Printf("%p\n", s4) // 0xc00001e370
// 切片扩容
s1 = append(s1, 11, 12, 13, 14, 15, 15, 16, 17)
fmt.Println("扩容后")
fmt.Println("arr:", arr)
fmt.Println("s1:", s1)
fmt.Println("s2:", s2)
fmt.Println("s3:", s3)
// 查看内存地址
fmt.Printf("%p\n", &arr) // 原始数组 0xc0001280f0
fmt.Printf("%p\n", s1) // 当超过cap 需要扩容时,扩容会导致底层数组产生拷贝 -> 新的数组(已经不一样了) 打印出的地址 0xc0001280f0
}
引用类型
package main
import "fmt"
func main() {
// 数组是值传递, 是拷贝了一份数据 然后存放在另一个内存空间
arr1 := [4]int{1, 2, 3, 4}
arr2 := arr1
arr2[1] = 5
fmt.Println(arr1, arr2) // arr1:0xc0000141e0, arr2:0xc000014200
fmt.Printf("arr1:%p, arr2:%p\n", &arr1, &arr2) // 地址不同 arr1:0xc0000141e0, arr2:0xc000014200
// 切片是引用类型, 指向的是一块内存空间
slice1 := []int{2, 3, 4, 5}
slice2 := slice1
slice2[2] = 99
fmt.Println(slice1, slice2) // [2 3 99 5] [2 3 99 5]
fmt.Printf("slice1:%p, slice2:%p\n", slice1, slice2) // slice1:0xc000014280, slice2:0xc000014280 => 指向同一块内存
}
深拷贝, 浅拷贝
深拷贝: 拷贝的是数据本身
- 值类型的数据, 默认都是深拷贝 array, int, float, string, bool, struct, ...
浅拷贝: 拷贝的是数据地址, 会导致多个变量指向同一块内存
- 引用类型的数据: slice, map ...
切片如何实现 深拷贝? -> copy(目的,源)
package main
import "fmt"
// 切片实现深拷贝
func main() {
// 将原来切片中固定数据拷贝到新切片中
s1 := []int{1, 2, 3, 4}
s2 := make([]int, 0) // len:0,cap:0(没设置默认为0)
fmt.Println(s2)
for i := 0; i < len(s1); i++ {
s2 = append(s2, s1[i])
}
fmt.Println(s2)
fmt.Println(s1)
s1[1] = 999
fmt.Println(s1)
fmt.Println(s2) // s1 数据变了, 而s2的数据没有变
// copy
s3 := []int{8, 9, 6}
// 将s3的数据拷贝到s2中
copy(s2, s3)
fmt.Println(s2) // 直接覆盖了... 变成 [8 9 6 4]
fmt.Println(s3) // [8 9 6]
// 将s2的数据拷贝到s3中
copy(s3, s2)
fmt.Println(s2) // 与上面分开执行(可以注释下.) 不然会看得比较乱
fmt.Println(s3)
}
函数中参数传递
按照数据的存储特点来划分:
-
值类型的数据: 操作的是数据本身, int, string, bool, float64, array
-
引用类型的数据: 操作的是数据的地址, slice, map, chan...
package main
import "fmt"
func main() {
arr1 := [4]int{1, 2, 3, 4}
fmt.Println("arr1:", arr1)
update(arr1) // 可见数组是值类型
fmt.Println("update函数结束后,arr1", arr1)
}
func update(arr [4]int) {
fmt.Println("update函数内修改前,arr", arr)
arr[0] = 100
fmt.Println("update函数内修改后,arr", arr)
}
// 数组是值类型 -> 函数内修改数组并不是真正修改数组的内容 若想真正改变数组的内容就传数组的&地址吧(如下) 调用函数 update(&数组名)
//func update(arr *[4]int) { // 使用数组指针
// fmt.Println("update函数内修改前,arr", *arr)
// arr[0] = 100
// fmt.Println("update函数内修改后,arr", *arr)
//}
map
map 是一种无序的键值对集合, 通过key来快速检索数据 map[key]=value
map 是无序的, 无法决定它的返回顺序。map是引用类型, 长度不固定
map的key可以是所有可比较的类型 如: string bool int float ...
map的定义
package main
import "fmt"
// map 集合
func main() {
// 创建map 数据类型是map map[key]value
var map1 map[int]string // 只是声明了, 为nil 没初始化 不能使用 没有 var map1 = map[int]string 这种写法
if map1 == nil {
fmt.Println("map1==nil")
}
// 更多时候是用 make 方式来创建
var map2 = make(map[string]string) // 创建map
fmt.Println(map2) // map[]
// 创建并初始化
var map3 = map[string]int{"Go": 100, "Java": 90}
fmt.Println(map3) // map[Go:100 Java:90]
fmt.Printf("%T\n", map3) // 打印类型 map[string]int
}
map的使用
package main
import "fmt"
func main() {
var map1 map[int]string // 这里只是声明, 不能使用(还没初始化 nil)
// 创建 & 初始化map1
map1 = make(map[int]string) // 已经声明了
map1[1] = "Go"
map1[1] = "Sam" // 如果key重复, 就会覆盖原来的value, 一个key只能对一个 value
map1[2] = "Tom" // 如果数据存在, 修改它; 如果不存在, 就会创建对应的 k-v
// 获取数据 map[key]
fmt.Println(map1)
fmt.Println(map1[1])
fmt.Println(map1[2])
fmt.Println(map1[3]) // 不存在的话, 只是输出默认值 string -> ""
if map1[3] == "" {
fmt.Println("none!")
}
// 所以一般map取值的时候 需要先去判断那个key是否存在
value, ok := map1[100] // 隐藏的返回值 ok -idiom (可选参数)
if ok {
fmt.Println("map key 存在,value:", value)
} else {
fmt.Println("map key 不存在的")
}
map1[3] = "Tonny"
fmt.Println(map1)
// 删除数据
delete(map1, 1) // 两个参数 map, key
fmt.Println(map1)
fmt.Println(len(map1)) // 查看map大小
}
map的遍历
package main
import "fmt"
// 遍历map
func main() {
var map1 = map[string]int{"Go": 100, "Java": 99, "C": 98}
// 循环遍历 只能用 for range
for k, v := range map1 { // 顺序随机 <- map是无序的 所以不能通过index获取
fmt.Println(k, v)
}
}
map结合slice使用
package main
import "fmt"
// map 结合 slice 来使用
/*
1. 创建map来存储人的信息, name,age,sex,addr
2. 每个map保存一个信息
3. 将这些map存入切片中
4. 打印信息
*/
func main() {
user := make(map[string]string) // value 可以指定为any (任意数据类型)
user["name"] = "Sam"
user["age"] = "19"
user["sex"] = "male"
user["addr"] = "GuangDong"
user2 := make(map[string]string)
user2["name"] = "Tony"
user2["age"] = "29"
user2["sex"] = "male"
user2["addr"] = "XiAn"
user3 := map[string]string{"name": "Tom", "age": "26", "sex": "female", "addr": "ZhaoQing"}
fmt.Println(user3)
// 用户信息存在切片中 (长度需要指定)
userData := make([]map[string]string, 0, 3)
userData = append(userData, user, user2, user3)
for index, value := range userData {
fmt.Println(index, value) // 当然了 可以继续遍历 value
//for mapValue := range value {
// fmt.Println(mapValue)
//}
}
}