# Day02 数组切片映射

# 本节关键词

  • 数组 := [3]int{1, 2, 3}
  • 变量需要初始化 切片 := make([]int, 0, 10)
  • 变量需要初始化 映射 : = make(map[string]int, 0))
  • 复合数据类型 -- 想到 --> 基础数据类型(字符串、整型、浮点型、布尔型)

# 数组 array

数组是同一种数据类型元素的集合,可修改数组成员,但是数组大小不可变化

# 数组的声明

var a [3]int64	// 定义一个长度为3元素元素类型为int64的数组a
var b [2]bool	// 定义一个长度为2元素类型为bool的数组b
var c [10]string // 定义一个长度为10元素类型为string的数组c

注意事项

  1. 数组的长度必须是常量,并且长度是数组类型的一部分
  2. 数组支持索引访问 a[1]c[7],索引的合法范围:0~len(array)-1不支持负数索引

# 数组初始化

var testArray [3]int      // 数组会初始化为int类型的零值
var x = [3]int{1, 2, 3}
var y = [...]bool{true, false, true}	// 自行推断个数
var z = [100]int{99:1}   // 指定某个索引的值,对应index, value := range z {}
var zz = [...]int{99:1}  // 最大索引+自行推断个数
  • {} 一大作用类型的实例化

# 数组的遍历

func main() {
	var zz = [5]int{5, 3, 2, 1, 4: 4}

	// 1. i自增表示索引,zz[i]
	for i := 0; i < len(zz); i++ {
		fmt.Println(i, zz[i])
	}

	// 2. 一个值得到是索引
	for i := range zz {
		fmt.Println(i, zz[i])
	}

	// 3. 同时遍历索引和值
	for index, value := range zz {
		fmt.Println(index, value)
	}

	// 4. 只要值,不要索引
	for _, value := range zz {
		fmt.Println(value)
	}
}

# 多维数组

这里以二维数组为例,三维数组、四维数组类似。多维数组可以理解为内部整体为最外层数组定义的元素

# 二维数组的定义

var xx = [3][2]string{
		{"北京", "石家庄"},
		{"上海", "苏州"},
		{"成都", "重庆"}, // 注意:最外层的花括号换行则这里必须加,
	}

# 二维数组的遍历

索引遍历

for i := 0; i < len(xx); i++ {
		tmp := xx[i]
		// 第一层
		fmt.Printf("xx[%v]:%v\n", i, xx[i])
		// 第二层
		for j := 0; j < len(tmp); j++ {
			fmt.Printf("\t xx[%v][%v]:%v\n", i, j, tmp[j])
		}
	}

for range遍历

for i1, v1 := range xx {
		fmt.Printf("xx[%v]: %v\n", i1, v1)
		// 第二层
		for i2, v2 := range v1 {
			fmt.Printf("\txx[%v][%v]:%v\n", i1, i2, v2)
		}
	}

# 数组值类型

  • 赋值、函数传参都是拷贝,修改副本不影响原值;
  • Go语言中全部都是值拷贝(深拷贝),Go语言是通过传递指针实现修改原来的值;
func bar(y [3]int) { // y = x 数组值拷贝
	y[0] = 100
}

func main() {

	var x = [3]int{1, 3, 5}
	y := x // 数组值拷贝
	y[0] = 100
	fmt.Println(x) // x = [1 3 5],y不会影响x的值
	fmt.Println(y) // y = [100 3 5]

	bar(x)
	fmt.Println(x) // x = [1 3 5]
}
  • 数组支持 "=="、"!=" 操作符,因为内存总是被初始化过的
  • [n]*T表示指针数组,*[n]T表示数组指针

# 课后练习

image-20220109123831341

// 练习1:求元素的和
func arraySum() {
	var a = [...]int{1, 3, 5, 7, 8, 10}
	sum := 0
	for _, value := range a {
		sum = sum + value
		// sum += value  // 简写
	}

	fmt.Println(sum)
}

// 练习2:求数组中元素和为8的元素的下标(索引)
// 1 + 7 = 8;索引(0,3)
// 3 + 5 = 8;索引(1,2)
// 1.拿到数组中的每一个元素 :遍历数组
// 2.找到元素和为8的那两个元素 : 数学运算和比较运算
// 3.把符合要求的索引打印出来  : fmt.Println()
func arraySum2() {
	var a = [...]int{1, 3, 5, 7, 8, 10}

	// 正向思路
	for i := 0; i < len(a); i++ {
		for j := i + 1; j < len(a); j++ {
			if a[i]+a[j] == 8 {
				// 找到啦
				fmt.Println(i, j)
			}
		}
	}

	// 反向思路
	for i, v := range a {
		x := 8 - v // 要找的目标值
		for j := i + 1; j < len(a); j++ {
			if a[j] == x {
				fmt.Println(i, j)
			}
		}
	}
}

# 切片 slice

因为数组的长度是固定的并且数组长度属于类型的一部分,所以数组有很多的局限性

func arraySum(x [3]int) int{
    sum := 0
    for _, v := range x{
        sum = sum + v
    }
    return sum
}
  • 求和函数 arraySum 只能接收 [3]int 类型,即个数为3元素类型为int的值
  • 另外 a := [3]int{1, 2, 3} 已经有三个元素不能再添加了

# 切片声明

  • 切片(slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装,非常灵活,支持自动扩容
  • 切片(slice)是一个引用类型,它的内部结构包含地址长度容量,切片一般用于快速操作一块数据集合
func main() {
	// 声明切片类型
	var a []string              //声明一个字符串切片
	var b = []int{}             //声明一个整型切片并初始化
	var c = []bool{false, true} //声明一个布尔切片并初始化
	var d = []bool{false, true} //声明一个布尔切片并初始化
	fmt.Println(a)              //[]
	fmt.Println(b)              //[]
	fmt.Println(c)              //[false true]
	fmt.Println(a == nil)       //true
	fmt.Println(b == nil)       //false
	fmt.Println(c == nil)       //false
	// fmt.Println(c == d)   //切片是引用类型,不支持直接比较,只能和nil比较
}
  • 使用内置的len()函数求长度,使用内置的cap()函数求切片的容量

# 切片表达式

切片的底层就是一个数组,我们可以基于数组通过切片表达式得到切片

func main() {
	a := [5]int{3, 4, 5, 6, 7}
	s := a[1:5]	// s := a[low:high] (左包含,右不包含)
	fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s))

	// 为了方便起见,可以省略切片表达式中的任何索引
	a[2:]  // 等同于 a[2:len(a)]
	a[:3]  // 等同于 a[0:3]
	a[:]   // 等同于 a[0:len(a)]
}
  • 切片长度=high-low,容量等于得到的切片的底层数组的容量
  • 对切片再执行切片表达式时(切片再切片),high的上限边界是切片的容量cap(a),而不是长度
func main() {
	a := [5]int{1, 2, 3, 4, 5}
	s := a[1:3]  // s := a[low:high]
	fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s))
	s2 := s[3:4]  // 索引的上限是cap(s)而不是len(s)
	fmt.Printf("s2:%v len(s2):%v cap(s2):%v\n", s2, len(s2), cap(s2))
}

/* 输出结果
s:[2 3] len(s):2 cap(s):4
s2:[5] len(s2):1 cap(s2):1
*/

切片表达式中的数字都是指的 索引 !!!

# 切片简短表达式

对字符串和数组:0 <= low < high <= len

对切片:0 <= low < high <= cap

# 切片完整表达式

func slice3() {
	// 默认切片的容量是从切片的开始索引到数组的最后
	// max: 影响切片的容量
	// max:想象成high能取到的最大值
	// 最终切片的容量:max-low
	a := []int{1, 2, 3, 4, 5}
	// a[low:high:max]
	s1 := a[1:2:3]                    // 0 <= low <= high <= max <= cap(a)
	fmt.Println(s1, len(s1), cap(s1)) // [2] 2 2
}

# 切片初始化

# 字面量初始化

// 字面量初始化(花括号)
func slice4() {
	s1 := []int{1, 2, 3}
	fmt.Println(s1)  // [1 2 3]
	s2 := []int{99: 1}
	fmt.Println(s2)
}

# make初始化切片

切片声明之后需要使用内置的make函数做初始化!make([]T, size, cap)

// 使用make函数初始化
	// make([]T, len, cap) cap可以省略,cap=len
	// s = make([]int, 2) // len = cap = 2
	// s = make([]int, 2, 4) // len = cap = 2
	s = make([]int, 2, 4)          // len =2, cap = 4
	fmt.Println(s, len(s), cap(s)) // [0 0] 2 4
	fmt.Println(s == nil)          // false

	s1 := make([]int, 0)
	fmt.Println(s1 == nil) // false

	s2 := make([]int, 0, 15)          // 一次把内存申请到位
	fmt.Println(s2, len(s2), cap(s2)) // [] 0 15
	// 如果你确定一个切片中最终要存储的元素个数,那么你最好一次把内存申请到位

# 切片的比较

断一个切片是否是空的,要是用len(s) == 0来判断,不应该使用s == nil来判断

var s1 []int         //len(s1)=0;cap(s1)=0;s1==nil
s2 := []int{}        //len(s2)=0;cap(s2)=0;s2!=nil
s3 := make([]int, 0) //len(s3)=0;cap(s3)=0;s3!=nil

# 切片的拷贝

# 切片的赋值拷贝

func main() {
	s1 := make([]int, 3) //[0 0 0]
	s2 := s1             //将s1直接赋值给s2,s1和s2共用一个底层数组
	s2[0] = 100
	fmt.Println(s1) //[100 0 0]
	fmt.Println(s2) //[100 0 0]
}
  • 拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容

# 切片copy复制

使用内置的copy函数完成复制

func copyDemo2() {
	a := []int{1, 2, 3}
	// var b = make([]int, 0, len(a))
	b := make([]int, len(a)) // 直接按目标切片的长度进行初始化
	copy(b, a)               // 把切片a中的值拷贝到切片b中
	fmt.Println(b)           // ?
	b[1] = 200

	fmt.Println(a) // ?
	fmt.Println(b) // [1 200 3]
}

使用copy函数要注意,事先初始化好切片的长度

func copyDemo() {
	a := []int{1, 2, 3}
	// var b = make([]int, 0, len(a))
	b := make([]int, 0)
	copy(b, a)     // 把切片a中的值拷贝到切片b中
	fmt.Println(b) // [] 因为b申请的长度就是0
}

# 切片 append

使用append函数时必须接收返回值!!!

func appendDemo() {
	var s = []string{"北京"}
	// append函数可能触发切片的扩容
	// 切片扩容之后会有一个新的底层数组,需要更新变量s
	s = append(s, "上海")
	fmt.Println(s) // [北京 上海]

	s2 := []string{"广州", "深圳"}
	s = append(s, s2...) // ...表示将s2拆开一个一个元素追加
	fmt.Println(s)       // [北京 上海 广州 深圳]

	// 零值切片可以直接在append中使用
	var s3 []int // nil
	fmt.Println(s3 == nil)
	s3 = append(s3, 1)
	s3 = append(s3, 2, 3, 4)
	fmt.Println(s3)
}

# append触发扩容

// appendDemo2 使用append函数触发扩容
// 导致意想不到的事情发生
func appendDemo2() {
	var s = []string{"北京"}
	_ = append(s, "上海", "广州", "深圳")
	fmt.Println(s) // [北京]
}

// appendDemo3 append函数导致切片扩容示例
func appendDemo3() {
	var s = []string{"北京"}
	fmt.Println(len(s), cap(s)) // len = cap = 1
	s1 := append(s, "上海", "广州", "深圳")
	fmt.Println(len(s1), cap(s1)) // 4 4
	fmt.Println(s)              // [北京]
}

# 删除切片的元素

// deleteSlice 删除切片中的元素
func deleteSlice(idx int) {
	idx = 1
	var s = []int{1, 2, 3}
	s = append(s[:idx], s[idx+1:]...)
	fmt.Println(s)
}

# 映射 map

map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用

  • map类型的变量默认初始值为nil,需要使用make()函数来分配内存,如make(map[KeyType]ValueType, [cap])
  • cap表示map的容量,该参数虽然不是必须的,但是我们应该在初始化map的时候就为其指定一个合适的容量

# map函数的原理

map是key-value结构的数据类型,类似于其他语言中的hash table,dict等

key必须是可hash的值,是一个确定的值(key的值不能设置了之后又发生了改变)

map存储的时候 hash(key) --> 固定的值 --> 把value放到对应的位置保存

map[key]: hash(key) --> 得到值 --> 取对应的value

# 初始化 map

func mapDemo1() {
	var m map[string]int

	fmt.Println(m == nil)    // true
	m = make(map[string]int) // 只要初始化就可以赋值
	fmt.Println(m == nil)    // false

	m["linda"] = 300 // 设置值
	fmt.Println(m)
	weight := m["linda"] // 取值
	fmt.Println(weight)

	// 字面量初始化
	m2 := map[string]string{
		"username": "linda",
		"password": "1234", // 花括号换行则此处必须加逗号
	}
	fmt.Printf("%#v\n", m2)

	name := m2["name"]
	fmt.Println(name)
	// v, ok 取值  ; ok是一个变量名,只不过大家约定成俗在这里用ok
	// ok = true表示map中有这个key, ok=false表示map没有这个key
	// 如果没有这个key,此时v=对应类型的零值
	v, ok := m2["name"] // 类似与for range 可以用一个变量也可以两个变量接收
	fmt.Println(v, ok)

	_, ok = m2["name"]
	fmt.Println(ok)
}

# 遍历 map

// map的遍历(map是无序的)
func mapDemo2() {
	m := map[string]int{
		"jade": 300,
		"ddd":  180,
		"嚯嚯嚯":  160,
	}

	for k, v := range m {
		fmt.Println(k, v)
	}

	// 只取map中的key
	for k := range m {
		fmt.Println(k)
	}

	// 只取map中的value
	for _, v := range m {
		fmt.Println(v)
	}
}

# map中删除键值对

// 从map中删除键值对
delete(m, "linda") // 没有返回值
fmt.Println(m)

# 判断key是否存在

// 判断map中是否存在某个key
_, ok := m["小盆子"]
fmt.Println(ok)

# 示例map+slice

map+slice 组成复杂一点的数据类型

// 元素类型是map的切片
func sliceMapDemo1() {
	// []int []string []map[string]int
	var mapSlice = make([]map[string]string, 3)

	for index, value := range mapSlice {
		fmt.Printf("index:%d value:%v\n", index, value)
	}
	fmt.Println("after init")
	// 对切片中的map元素进行初始化
	mapSlice[0] = make(map[string]string, 10)
	mapSlice[0]["name"] = "linda"
	mapSlice[0]["password"] = "123456"
	mapSlice[0]["address"] = "BJ"
	for index, value := range mapSlice {
		fmt.Printf("index:%d value:%v\n", index, value)
	}
}

func mapSliceDemo2() {
	// map[string]int  map[string][]int
	var sliceMap = make(map[string][]string, 3)

	fmt.Println(sliceMap)
	fmt.Println("after init")
	key := "中国"
	value, ok := sliceMap[key]
	if !ok {
		value = make([]string, 0, 2)
	}
	value = append(value, "北京", "上海")
	sliceMap[key] = value
	fmt.Println(sliceMap)
}

# 课后作业

image-20220109184640634

func ex1() {
	// 写一个程序,统计一个字符串中每个单词出现的次数。
	// 比如:”how do you do”中how=1 do=2 you=1。
	s := "how do you do"

	// 1.用map存数据,key是单词,value是单词出现的次数
	// 2.将字符串分成一个一个的单词
	// 3.把上一步得到的单词挨个存放到map里
	// 4.遍历map打印结果

	// 迎刃而解
	var m map[string]int
	m = make(map[string]int)
	s1 := strings.Split(s, " ") // 切片
	for _, v := range s1 {
		// m[v] = 1  // m["do"] = 1
		num := m[v]
		m[v] = num + 1

		// m[v]++

		// if ok{
		// 	m[v] = num+1
		// }else{
		// 	m[v] = 0+1
		// }
	}

	for k, v := range m {
		fmt.Println(k, v)
	}
}
func ex2() {
	// 对数组var a = [...]int{3, 7, 8, 9, 1}进行排序
	var a = [...]int{3, 7, 8, 9, 1}
	s := a[:]
	fmt.Println("before sort:", s)
	// 按什么规则对s排序
	sort.Slice(s, func(i, j int) bool {
		return s[i] < s[j]
	})
	fmt.Println("after sort:", s)
}
func ex3() {
	m := make(map[string][]int) // 声明并初始化了一个map变量m
	s := []int{1, 2}            // 声明一个切片变量s
	s = append(s, 3)            // 向s追加一个元素
	fmt.Printf("%+v\n", s)      // [1 2 3]

	m["linda"] = s
	s3 := s

	// 用append从切片删除索引为1的元素 append(s[:idx], s[idx+1]...)
	s = append(s[:1], s[2:]...) // ...表示拆开切片 s = append(s[:1], 3)  [1 3]  s[1] = 3
	// s[:1]     [1]
	// s[2:]...  [3]
	s[1] = 100 // 修改了底层数组

	fmt.Printf("s:%+v\n", s)                      // s:[1 100]
	fmt.Printf(`m["linda"]:%+v`+"\n", m["linda"]) // m["linda"]:[1 100 3]
	fmt.Printf("s3:%+v\n", s3)                    // s3:[1 100 3]

	fmt.Println(len(s), len(m["linda"])) // 2 3
}
上次更新: 1/10/2023, 12:41:52 PM