# day02 数组array、切片slice、映射map

# 内容回顾

  1. Go语言的开发环境搭建
    1. 装go的时候安装到一个你能找到的目录
    2. go env
    3. VsCode的安装及注意事项
      1. 设置GOPROXY
      2. 一个VsCode、Goland窗口打开一个Go项目
  2. 变量
    1. 变量的概念
    2. 匿名变量: _
    3. go语言推荐驼峰命名
  3. 常量
    1. iota
  4. 基本数据类型
    1. 字符串
    2. 整数、浮点数、复数
    3. 布尔值
    4. 字符(单引号包裹)
    5. byte、rune
  5. 运算符
  6. 流程控制语句
    1. if else
    2. for
    3. switch case
    4. goto/continue/break/return
  7. Go语法注意事项
    1. 变量声明之后必须使用(非全局)
    2. go源码文件中必须以关键字开头

# 数组 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}  // 最大索引+自行推断个数
  • {} 一大作用类型的实例化

# 数组的遍历

image-20220109114437897

# 多维数组

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

# 二维数组的定义

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{"北京"}
	// apend函数可能触发切片的扩容
	// 切片扩容之后会有一个新的底层数组,需要更新变量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
	s = append(s, "上海", "广州", "深圳")
	fmt.Println(len(s), cap(s)) // 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

上次更新: 9/10/2022, 5:57:39 PM