Go 切片&map

78 阅读7分钟

切片

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)
		//}
	}
}