# 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
注意事项
- 数组的长度必须是常量,并且长度是数组类型的一部分
- 数组支持索引访问
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
表示数组指针
# 课后练习
// 练习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)
}
# 课后作业
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
}