第八课 go语言基础-数组、切片和map
tags:
- golang
- 2019尚硅谷
categories:
- golang
- 数组
- 切片
- 二维数组
- map
文章目录
第一节 go语言的数组
1.1 数组定义
- 数组可以存放多个同一类型数据。数组也是一种数据类型,在Go中,数组是值类型。
- 数组的定义
- var 数组名 [数组大小]数据类型比如:var a [5]int
- 赋初值a[0]=1 a[1]=30 …
- 数组在内存布局(重要)
- 数组的地址可以通过数组名来获取&intArr
- 数组的第一个元素的地址,就是数组的首地址
- 数组的各个元素的地址间隔是依据数组的类型决定,比如int64->8 int32->4…
package main
import "fmt"
/*
[0 0 0]
[10 20 30]
intArr的地址=0xc0420520a0 intArr[0] 地址0xc0420520a0 intArr[1] 地址0xc0420520a8 intArr[2] 地址0xc0420520b0PS
*/
func main() {
var intArr [3]int //int占8个字节
//当我们定义完数组后,其实数组的各个元素有默认值0
fmt.Println(intArr)
intArr[0] = 10
intArr[1] = 20
intArr[2] = 30
fmt.Println(intArr)
fmt.Printf("intArr的地址=%p intArr[0] 地址%p intArr[1] 地址%p intArr[2] 地址%p",
&intArr, &intArr[0], &intArr[1], &intArr[2])
}
1.2 数组使用
- 访问数组元素 数组名[下标] 比如:你要使用a数组的第三个元素a[2]
- 四种初始化数组的方式
//四种初始化数组的方式
var numArr01 [3]int = [3]int{1, 2, 3}
fmt.Println("numArr01=", numArr01)
var numArr02 = [3]int{5, 6, 7}
fmt.Println("numArr02=", numArr02)
//这里的 [...]是规定的写法
var numArr03 = [...]int{8, 9, 10}
fmt.Println("numArr03=", numArr03)
var numArr04 = [...]int{1: 800, 0: 900, 2:999}
fmt.Println("numArr04=", numArr04)
//类型推导
strArr05 := [...]string{1: "tom", 0: "jack", 2:"mary"}
fmt.Println("strArr05=", strArr05)
- 数组的遍历
- 常规遍历
- for- range结构遍历
- 第一个返回值index是数组的下标
- 第二个value是在该下标位置的值
- 他们都是仅在for循环内部可见的局部变量
- 遍历数组元素的时候,如果不想使用下标index,可以直接把下标index标为下划线_
- index和value的名称不是固定的,即程序员可以自行指定,一般命名为index和value
for index, value := range array01 {
...
}
//演示for-range遍历数组
heroes := [...]string{"宋江", "吴用", "卢俊义"}
//使用常规的方式遍历
for i := 0; i < len(heroes); i++{
fmt.Printf("常规方式遍历heroes[%d]=%v\n", i, heroes[i])
}
//for-range遍历数组
for i, v := range heroes {
fmt.Printf("i=%v v=%v\n", i, v)
fmt.Printf("for-range遍历heroes[%d]=%v\n", i, heroes[i])
}
for _, v := range heroes {
fmt.Printf("for-range遍历元素的值=%v\n", v)
}
1.3 数组使用的注意事项和细节
- 数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的,不能动态变化
- var arr []int这时arr就是一个slice切片
- 数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用。
- 数组创建后,如果没有赋值,有默认值(零值)
- 使用数组的步骤
- 1.声明数组并开辟空间
- 2给数组各个元素赋值(默认零值)
- 3使用数组
- 数组的下标是从0开始的
- 数组下标必须在指定范围内使用,否则报panic:数组越界,比如var arr [5]int则有效下标为0-4
- Go的数组属值类型, 在默认情况下是值传递, 因此会进行值拷贝。数组间不会相互影响
- 如想在其它函数中,去修改原来的数组,可以使用引用传递(指针方式)
- 长度是数组类型的一部分,在传递函数参数时需要考虑数组的长度
arr := [3]int{11, 22, 33}
// 值传递
test(arr)
fmt.Println(arr)
// 把数组的地址传过来
test01(&arr)
fmt.Println(arr)
// 直接传递数组 值传递 复制一份数组
func test(arr [3]int) {
arr[0] = 88
}
// 把数组的地址传过来
func test01(arr *[3]int) {
(*arr)[0] = 88
}
第二节 go语言的切片
2.1 切片的基本介绍
- 切片的英文是slice
- 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。
- 切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度len(slice)都一样。
- 切片的长度是可以变化的,因此切片是一个可以动态变化数组。
- 切片定义的基本语法:
- var 切片名 []类型 比如: var a []int
- slice从底层来说,其实就是一个数据结构(struct结构体)
type slice struct {
ptr *[2]int
len int
cap int
}
- 切片的具体使用的三种方式
- 第一种方式: 定义一个切片,然后让切片去引用一个已经创建好的数组
- 第二种方式: 通过make来创建切片
- 基本语法: var 切片名 []type = make([]type, len, [cap])
- 参数说明: type:就是数据类型len: 大小cap :指定切片容量,可选,如果 你分配了cap,则要求cap>=len
- 第三种方式: 定义一个切片,直接就指定具体数组,使用原理类似make的方式
- 注意:
- 通过make方式创建切片可以指定切片的大小和容量
- 如果没有给切片的各个元素赋值,那么就会使用默认值[int, float=>0 string =>"" bool=>false]
- 通过make方式创建的切片对应的数组是由make底层维护,对外不可见,即只能通过slice去访问各个元素.
- 面试题:方式1和方式2
- 方式1是直接引用数组,这个数组是事先存在的,程序员是可见的。.
- 方式2是通过make来创建切片,make也会创建一个数组,是由切片在底层进行维护,程序员是看不见的。
package main
import "fmt"
func main() {
var arr [5]int = [...]int{1,2,3,4,5}
// 第一种方式: 定义一个切片,然后让切片去引用一个已经创建好的数组
var slice1 = arr[1:3]
fmt.Println("arr=", arr)
fmt.Println("slice1=", slice1)
fmt.Println("slice1 len =", len(slice1))
fmt.Println("slice1 cap =", cap(slice1))
//第二种方式: 通过make来创建切片
var slice2 []float64 = make([]float64, 5, 10)
slice2[1] = 10
slice2[3] = 20
//对于切片,必须make使用。
fmt.Println(slice2)
fmt.Println("slice2的size=", len(slice2))
fmt.Println("slice2的cap=", cap(slice2))
//第3种方式:定义一个切片,直接就指定具体数组,使用原理类似make的方式
var strslice []string = []string{"tom", "jack" , "mary"}
fmt.Println("strslice=", strslice)
fmt.Println("strslice size=", len(strslice)) //3
fmt.Println("strslice cap=", cap(strslice)) // 3
}
2.2 切片的遍历
- 切片的遍历和数组一样,也有两种方式
- for循环常规方式遍历
- for-range结构遍历切片
package main
import "fmt"
func main() {
//使用常规的for循环遍历切片
var arr [5]int = [...]int{10, 20, 30, 40, 50}
slice := arr[1:4] // 20,30,40
for i:=0; i<len(slice); i++{
fmt.Printf("slice[%v]=%v", i, slice[i])
}
fmt.Println()
//使用for-range方式遍历切片
for i, v := range slice {
fmt.Printf("i=%v v=%v \n", i, v)
}
}
2.3 切片的使用的注意事项和细节讨论
- 切片初始化时var slice = arr[startIndex : endIndex]
- 说明:从arr数组下标为statIndex,取到下 标为endIndex的元素(不含arr[ endIndex])。
- 切片初始化时,仍然不能越界。范围在[0-len(arr)] 之间,但是可以动态增长
- var slice = arr[0:end]可以简写var slice = arr[:end]
- var slice = arr[start:len(arr)]可以简写: var slice = arr[ start:]
- var slice = arr[0:len(arr)]可以简写: var slice = arr[:]
- cap 是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素。(会自动拓展)
- 切片定义完后,还不能使用,因为本身是一个空的,需要让其引用到一个数组,或者make 一个空间供切片来使用
- 切片可以继续切片
- 用append内置函数,可以对切片进行动态追加 对切片append操作的底层原理分析:
- 追加切片时,记得加上**…**: slice3 = append(slice3, slice…)
- 切片append操作的本质就是对数组扩容
- go底层会创建一下新的数组newArr(安装扩容后大小)
- 将slice原来包含的元素拷贝到新的数组newArr
- slice重新引用到newArr
- 注意newArr是在底层来维护的,程序员不可见
- 切片的拷贝操作:切片使用copy内置函数完成拷贝
- copy(para1, para2)参数的数据类型是切片
- 按照上面的代码来看,slice4和slice5的数据空间是独立
- 如果被复制的para2比para1长,那么就复制para1的长度的值就行,不会报错。看案例
- 切片是引用类型,所以在传递时,遵守引用传递机制。
package main
import "fmt"
func main() {
//使用常规的for循环遍历切片
var arr [5]int = [...]int{10, 20, 30, 40, 50}
slice := arr[1:4]
// 对切片进行切片
slice2 := slice[1:2] // slice[20, 30, 40]
slice2[0] = 100
//因为arr,slice 和slice2 指向的数据空间是同一个,因此slice2[0]=100,
fmt.Println("slice2=", slice2)
fmt.Println("slice=", slice)
fmt.Println("arr=", arr)
//用append内置函数,可以对切片进行动态追加
var slice3 []int = []int{100, 200, 300}
//通过append直接给slice3追加具体的元素
slice3 = append(slice3, 400, 500, 600)
fmt.Println("slice3", slice3) //100, 200, 300, 400, 500, 600
//通过append将切片slice3追加给slice3 把slice追加给slice3 都要加 ...
// 不能直接追加数组slice3 = append(slice3, arr)
slice3 = append(slice3, arr[1:2]...)
slice3 = append(slice3, slice...)
slice3 = append(slice3, slice3...) // 100, 200, 300,400, 500, 600 100, 200, 300, 4
fmt.Println("slice3", slice3)
//切片的拷贝操作
//切片使用copy内置函数完成拷贝,举例说明
fmt.Println()
var slice4 []int = []int{1, 2, 3, 4, 5}
var slice5 = make([]int, 10)
copy(slice5, slice4)
fmt.Println(" slice4=", slice4)// 1, 2, 3, 4, 5
fmt.Println("slice5=", slice5) // 1, 2, 3, 4, 5, 0,0 ,0,0,0
// a的长度大于slicea 把a复制过去不会报错
var a []int = []int {1,2,3,4,5}
var slicea = make([]int, 1)
fmt.Println(slicea) // [0]
copy(slicea, a)
fmt.Println(slicea)// [1]
}
2.4 string和slice
- string底层是一个byte数组,因此string也可以进行切片处理。
- 但是string是不可变的,也就说不能通过str[0]=‘z’ 方式来修改字符串。(和python中一样)
- 如果需要修改字符串,可以先将string-> []byte或者[]rune-> 修改-> 重写转成string
package main
import "fmt"
func main() {
//string底层是一一个byte数组,因此string也可以进行切片处理
str := "hel1o@atguigu"
//使用切片获取到atguigu
slice := str[6:]
fmt.Println("slice=", slice)
//string是不可变的,也就说不能通过str[0] = 'z’方式来修改字符串
//str[0] = 'z' [编译不会通过,报错,原因是string是不可变]
//如果需要修改字符串,可以先将string -> []byte /或者[]rune ->修改->重写转成string
// "hello@atguigu" =>改成”zello@atguigu"
arr1 := []byte(str)
arr1[0] = 'z'
str = string(arr1)
fmt.Println("str=", str)
//细节,我们转成[]byte后, 可以处理英文和数字,但是不能处理中文
//原因是[]byte 字节来处理,而一个汉字,是3个字节,因此就会出现乱码
//解快方法是将string 转成[]rune 即可,因为 []rune是按字符处理,兼容汉字
arr2 := []rune(str)
arr2[0] = '北'
str = string(arr2)
fmt.Println("str=", str)
}
第三节 go语言的二维数组
3.1 二维数组的定义和使用
- 使用方式如下。
// 使用方式1:先声明/定义,再赋值
// 语法: var数组名[大小][大小]类型,
var arr [2][3]int
// 使用方式2:直接初始化
// 语法: var数组名[大小][大小]类型= [大小][大小]类型{{初值..},{初值.}}
var 数组名 [大小][大小]类型 = [大小][大小]类型{{初值...},{初值...}}
var 数组名 [大小][大小]类型 = [...][大小]类型{{初值...},{初值...}}
var 数组名 = [大小][大小]类型{{初值...},{初值...}}
var 数组名 = [...][大小]类型{{初值...},{初值...}}
var a = [3][4]int{
{0, 1, 2, 3} , /* 第一行索引为 0 */
{4, 5, 6, 7} , /* 第二行索引为 1 */
{8, 9, 10, 11}, /* 第三行索引为 2 */
}
- 维数组在内存的存在形式
3.2 二维数组的遍历
- 双层for循环完成遍历
- for-range方式完成遍历
package main
import "fmt"
func main() {
//演示二维数组的遍历
var arr3 = [2][3]int{{1,2,3}, {4,5,6}}
//for循环来遍历
fmt.Println(arr3)
for i := 0; i < len(arr3); i++ {
for j := 0; j < len(arr3[i]); j++ {
fmt.Printf("%v\t", arr3[i][j] )
}
fmt.Println()
}
//for-range来遍历二维数组
for i, v := range arr3 {
for j,v2 := range v{
fmt.Printf("arr3[%v][%v]=%v \t",i, j, v2)
}
fmt.Println()
}
}
第四节 map介绍和使用
4.1 map的基本介绍
- map是key-value数据结构,又称为字段或者关联数组。类似其它编程语言的集合,在编程中是经常使用到
4.2 map的声明
- 基本语法: var map变量名 map[keytype]valuetype
- key可以是什么类型
- golang中的map的key可以是很多种类型,比如bool, 数字,string, 指针, channel,还可以是只包含前面几个类型的接口,结构体,数组。通常key为int、string
- 注意: slice,map 还有function 不可以,因为这几个没法用==来判断
- valuetype可以是什么类型
- valuetype的类型和key基本一样。通常为:数字(整数浮点数),string**,map,struct**
- map声明的举例
func main() {
var a map[string]string
var a map[string]int
var a map[int]string
var a map[string]map[string]string
}
- 注意:声明是不会分配内存的,初始化需要make,分配内存后才能赋值和使用。
- map在使用前一定要make
- map的key是不能重复,如果重复了,则以最后这个key-value为准
- map的value是可以相同的.
- map的key-value 是无序
- make内置函数
package main
import "fmt"
func main() {
//map的声明和注意事项
var a map[string]string
//在使用map前,需要先make,make的作用就是给map分配数据空间
a = make(map[string]string, 10)
a["no1"] = "宋江" //ok?
a["no2"] = "吴用" //ok?
a["no1"] = "武松" //ok?
a["no3"] = "吴用" //ok?
fmt.Println(a)
}
4.3 map的使用
package main
import "fmt"
func main() {
// 第一种使用方式
//map的声明和注意事项
var a map[string]string
//在使用map前,需要先make,make的作用就是给map分配数据空间
a = make(map[string]string, 10)
a["no1"] = "宋江" //ok?
a["no2"] = "吴用" //ok?
a["no1"] = "武松" //ok?
a["no3"] = "吴用" //ok?
fmt.Println(a)
//第二种 方式 不用给出个数
cities := make(map[string]string)
cities["no1"] = "北京"
cities["no2"] = "天津"
cities["no3"] = "上海"
fmt.Println(cities)
//第三种方式 最后的,别忘记
heroes := map[string]string{
"hero1" : "宋江",
"hero2" : "卢俊义",
"hero3" : "吴用",
}
heroes["hero4"] = "林冲"
fmt. Println("heroes=", heroes)
/*案例
课堂练习:演示个key-value 的value是map的案例
比如:我们要存放3个学生信息,每个学生有name和sex 信息
思路:
map[string]map[string]string
*/
studentMap := make(map[string]map[string]string)
studentMap["stu01"] = make(map[string]string, 3)
studentMap["stu01"]["name"] = "tom"
studentMap["stu01"]["sex"] = "男"
studentMap["stu01"]["address"] = "北京长安街~"
studentMap["stu02"] = make(map[string]string, 3) //这句话不能少!!
studentMap["stu02"]["name"] ="mary"
studentMap["stu02"]["sex"] = "女"
studentMap["stu02"]["address"] = "上海黄浦江~"
fmt.Println(studentMap)
fmt.Println(studentMap["stu02"])
fmt.Println(studentMap["stu02"]["address"])
}
4.4 map的增删改查和遍历
- map增加和更新:map[ “key”] = value
- 如果key还没有,就是增加,如果key存在就是修改。
- map 删除:
- delete(map,“key”) ,delete 是-一个内置函数,如果key存在,就删除该key-value,如果key不存不操作,但是也不会报错
- 如果我们要删除map的所有key,没有一个专门的方法一次删除。
- 可以遍历一下key, 逐个删除
- 或者map = make(…),make一个新的,让原来的成为垃圾,被gc回收
- map查找:
- val, ok := cities[“no2”]
- 说明:如果heroes这个map中存在"nol" ,那么findRes就会返回true,否则返回false
- map遍历
- map的遍历使用for-range的结构遍历
- map的长度len()
package main
import "fmt"
func main() {
cities := make(map[string]string)
cities["no1"] = "北京"
cities["no2"] = "天津"
cities["no3"] = "上海"
fmt.Println(cities)
//因为no3这个key已经存在,因此下面的这句话就是修改
cities["no3"]="上海~"
fmt.Println(cities)
//演示删除
delete(cities, "no1" )
fmt.Println(cities)
//当delete指定的key不存在时,删除不会操作,也不会报错
delete(cities, "no4")
fmt.Println(cities)
//如果希望一次性删除所有的key
//1.遍历所有的key,如何逐一删除[遍历]
//2.直接make一个新的空间
cities = make(map[string]string)
fmt.Println(cities)
//演示map的查找
val, ok := cities["no2"]
if ok{
fmt.Printf("有no1 key值为%v\n", val)
} else {
fmt.Printf("没有no1 key\n")
}
studentMap := make(map[string]map[string]string)
studentMap["stu01"] = make(map[string]string, 3)
studentMap["stu01"]["name"] = "tom"
studentMap["stu01"]["sex"] = "男"
studentMap["stu01"]["address"] = "北京长安街~"
studentMap["stu02"] = make(map[string]string, 3) //这句话不能少!!
studentMap["stu02"]["name"] ="mary"
studentMap["stu02"]["sex"] = "女"
studentMap["stu02"]["address"] = "上海黄浦江~"
// map的遍历
for k1, v1 := range studentMap {
fmt.Println("k1=", k1)
for k2, v2 := range v1 {
fmt.Printf("\t k2=%v v2=%v\n", k2, v2)
fmt.Println()
}
}
fmt.Println(len(studentMap))
}
4.5 map的切片
- 切片的数据类型如果是map,则我们称为slice of map, map切片,这样使用则map个数就可以动态变化了。
package main
import "fmt"
func main() {
//演示map切片的使用
/*
要求:使用一个map来记录monster的信息
name和age, 也就是说一个
monster对应个map 并且妖怪的个数 可以动态的增加=>map切片
*/
//1.声明一个map切片
var monsters []map[string]string
monsters = make([]map[string]string, 2) //准备放入两个妖怪
//2.增加第一个妖怪的信息
if monsters[0] == nil {
monsters[0] = make(map[string]string, 2)
monsters[0]["name"] = "牛魔王"
monsters[0]["age"] = " 500"
}
if monsters[1] == nil {
monsters[1] = make(map[string]string, 2)
monsters[1]["name"] = "玉兔精"
monsters[1]["age"] ="400"
}
/* 下面这个写法越界panic: runtime error: index out of range
if monsters[2] == nil {
monsters[2] = make(map[string]string, 2)
monsters[2]["name"] = "越界精"
monsters[2]["age"] ="400"
}
*/
//这里我们需要使用到切片的append函数,可以动态的增加monster
//1.先定义个monster信息
newMonster := map[string]string{
"name" : "新的妖怪~火云邪神",
"age" : "200",
}
monsters = append(monsters, newMonster)
fmt.Println(monsters)
}
4.6 map的排序
- golang中没有一个专门的方法针对 map的key进行排序
- golang中的map默认是无序的,注意也不是按照添加的顺序存放的,你每次遍历,得到的输出
可能不一样 - golang中map的排序,是先将key进行排序,然后根据key值遍历输出即可
package main
import (
"fmt"
"sort"
)
func main() {
//map的排序
map1 := make(map[int]int, 10)
map1[10] = 100
map1[1] = 13
map1[4] = 56
map1[8] = 90
fmt.Println(map1)
//如果按照map的key的顺序进行排序输出
//1.先将map的key 放入到切片中
//2.对切片排序
//3.遍历切片,然后按照key来输出map的值
var keys []int
for k,_ := range map1 {
keys = append(keys, k)
}
//排序
sort.Ints(keys)
fmt.Println(keys)
for _, k := range keys{
fmt.Printf("map1[%v]=%v\n", k, map1[k])
}
}
4.7 map使用细节
- map是引用类型,遵守引用类型传递的机制,在-一个函数接收map,修改后,会直接修改原来
的map。 - map的容量达到后,再想map增加元素,会自动扩容,并不会发生panic,也就是说map能动
态的增长键值对(key-value) - map的value也经常使用struct类型,更适合管理复杂的数据(比前面value是一个map更好)比如value为Student 结构体