第八课 go语言基础-数组、切片和map

第八课 go语言基础-数组、切片和map

tags:

  • golang
  • 2019尚硅谷

categories:

  • golang
  • 数组
  • 切片
  • 二维数组
  • map

文章目录

  • 第八课 go语言基础-数组、切片和**map**
    • 第一节 go语言的数组
      • 1.1 数组定义
      • 1.2 数组使用
      • 1.3 数组使用的注意事项和细节
    • 第二节 go语言的切片
      • 2.1 切片的基本介绍
      • 2.2 切片的遍历
      • 2.3 切片的使用的注意事项和细节讨论
      • 2.4 string和slice
    • 第三节 go语言的二维数组
      • 3.1 二维数组的定义和使用
      • 3.2 二维数组的遍历
    • 第四节 map介绍和使用
      • 4.1 map的基本介绍
      • 4.2 map的声明
      • 4.3 map的使用
      • 4.4 map的增删改查和遍历
      • 4.5 map的切片
      • 4.6 map的排序
      • 4.7 map使用细节

第一节 go语言的数组

1.1 数组定义

  1. 数组可以存放多个同一类型数据。数组也是一种数据类型,在Go中,数组是值类型
  2. 数组的定义
    • var 数组名 [数组大小]数据类型比如:var a [5]int
    • 赋初值a[0]=1 a[1]=30 …
  3. 数组在内存布局(重要)
    • 数组的地址可以通过数组名来获取&intArr
    • 数组的第一个元素的地址,就是数组的首地址
    • 数组的各个元素的地址间隔是依据数组的类型决定,比如int64->8 int32->4…
      第八课 go语言基础-数组、切片和map_第1张图片
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 数组使用

  1. 访问数组元素 数组名[下标] 比如:你要使用a数组的第三个元素a[2]
  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)
  1. 数组的遍历
    • 常规遍历
    • 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 数组使用的注意事项和细节

  1. 数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的,不能动态变化
  2. var arr []int这时arr就是一个slice切片
  3. 数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用
  4. 数组创建后,如果没有赋值,有默认值(零值)
  5. 使用数组的步骤
    • 1.声明数组并开辟空间
    • 2给数组各个元素赋值(默认零值)
    • 3使用数组
  6. 数组的下标是从0开始的
  7. 数组下标必须在指定范围内使用,否则报panic:数组越界,比如var arr [5]int则有效下标为0-4
  8. Go的数组属值类型, 在默认情况下是值传递, 因此会进行值拷贝。数组间不会相互影响
  9. 如想在其它函数中,去修改原来的数组,可以使用引用传递(指针方式)
  10. 长度是数组类型的一部分,在传递函数参数时需要考虑数组的长度
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 切片的基本介绍

  1. 切片的英文是slice
  2. 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。
  3. 切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度len(slice)都一样。
  4. 切片的长度是可以变化的,因此切片是一个可以动态变化数组。
  5. 切片定义的基本语法:
    • var 切片名 []类型 比如: var a []int
  6. slice从底层来说,其实就是一个数据结构(struct结构体)
    第八课 go语言基础-数组、切片和map_第2张图片
type slice struct {
	ptr *[2]int
	len int 
	cap int
}
  1. 切片的具体使用的三种方式
    • 第一种方式: 定义一个切片,然后让切片去引用一个已经创建好的数组
    • 第二种方式: 通过make来创建切片
      • 基本语法: var 切片名 []type = make([]type, len, [cap])
      • 参数说明: type:就是数据类型len: 大小cap :指定切片容量,可选,如果 你分配了cap,则要求cap>=len
    • 第三种方式: 定义一个切片,直接就指定具体数组,使用原理类似make的方式
  2. 注意:
    1. 通过make方式创建切片可以指定切片的大小和容量
    2. 如果没有给切片的各个元素赋值,那么就会使用默认值[int, float=>0 string =>"" bool=>false]
    3. 通过make方式创建的切片对应的数组是由make底层维护,对外不可见,即只能通过slice去访问各个元素.
  3. 面试题:方式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 切片的遍历

  1. 切片的遍历和数组一样,也有两种方式
    • 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 切片的使用的注意事项和细节讨论

  1. 切片初始化时var slice = arr[startIndex : endIndex]
    • 说明:从arr数组下标为statIndex,取到下 标为endIndex的元素(不含arr[ endIndex])。
  2. 切片初始化时,仍然不能越界。范围在[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[:]
  3. cap 是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素。(会自动拓展)
  4. 切片定义完后,还不能使用,因为本身是一个空的,需要让其引用到一个数组,或者make 一个空间供切片来使用
  5. 切片可以继续切片
  6. append内置函数,可以对切片进行动态追加 对切片append操作的底层原理分析:
    • 追加切片时,记得加上**…**: slice3 = append(slice3, slice…)
    • 切片append操作的本质就是对数组扩容
    • go底层会创建一下新的数组newArr(安装扩容后大小)
    • 将slice原来包含的元素拷贝到新的数组newArr
    • slice重新引用到newArr
    • 注意newArr是在底层来维护的,程序员不可见
      第八课 go语言基础-数组、切片和map_第3张图片
  7. 切片的拷贝操作:切片使用copy内置函数完成拷贝
    • copy(para1, para2)参数的数据类型是切片
    • 按照上面的代码来看,slice4和slice5的数据空间是独立
    • 如果被复制的para2比para1长,那么就复制para1的长度的值就行,不会报错。看案例
  8. 切片是引用类型,所以在传递时,遵守引用传递机制
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

  1. string底层是一个byte数组,因此string也可以进行切片处理
  2. 但是string是不可变的,也就说不能通过str[0]=‘z’ 方式来修改字符串。(和python中一样)
  3. 如果需要修改字符串,可以先将string-> []byte或者[]rune-> 修改-> 重写转成string
    第八课 go语言基础-数组、切片和map_第4张图片
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. 使用方式如下。
// 使用方式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 */
}
  1. 维数组在内存的存在形式
    第八课 go语言基础-数组、切片和map_第5张图片

3.2 二维数组的遍历

  1. 双层for循环完成遍历
  2. 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的基本介绍

  1. map是key-value数据结构,又称为字段或者关联数组。类似其它编程语言的集合,在编程中是经常使用到

4.2 map的声明

  1. 基本语法: var map变量名 map[keytype]valuetype
  2. key可以是什么类型
    • golang中的map的key可以是很多种类型,比如bool, 数字,string, 指针, channel,还可以是只包含前面几个类型的接口,结构体,数组。通常key为int、string
    • 注意: slice,map 还有function 不可以,因为这几个没法用==来判断
  3. valuetype可以是什么类型
    • valuetype的类型和key基本一样。通常为:数字(整数浮点数),string**,map,struct**
  4. 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
}
  1. 注意:声明是不会分配内存的,初始化需要make,分配内存后才能赋值和使用。
    • map在使用前一定要make
    • map的key是不能重复,如果重复了,则以最后这个key-value为准
    • map的value是可以相同的.
    • map的key-value 是无序
    • make内置函数
      第八课 go语言基础-数组、切片和map_第6张图片
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的增删改查和遍历

  1. map增加和更新:map[ “key”] = value
    • 如果key还没有,就是增加,如果key存在就是修改。
  2. map 删除:
    • delete(map,“key”) ,delete 是-一个内置函数,如果key存在,就删除该key-value,如果key不存不操作,但是也不会报错
    • 如果我们要删除map的所有key,没有一个专门的方法一次删除。
      • 可以遍历一下key, 逐个删除
      • 或者map = make(…),make一个新的,让原来的成为垃圾,被gc回收
  3. map查找:
    • val, ok := cities[“no2”]
    • 说明:如果heroes这个map中存在"nol" ,那么findRes就会返回true,否则返回false
  4. map遍历
    • map的遍历使用for-range的结构遍历
  5. map的长度len()
    第八课 go语言基础-数组、切片和map_第7张图片
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的切片

  1. 切片的数据类型如果是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的排序

  1. golang中没有一个专门的方法针对 map的key进行排序
  2. golang中的map默认是无序的,注意也不是按照添加的顺序存放的,你每次遍历,得到的输出
    可能不一样
  3. 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使用细节

  1. map是引用类型,遵守引用类型传递的机制,在-一个函数接收map,修改后,会直接修改原来
    的map。
  2. map的容量达到后,再想map增加元素,会自动扩容,并不会发生panic,也就是说map能动
    态的增长键值对(key-value)
  3. map的value也经常使用struct类型,更适合管理复杂的数据(比前面value是一个map更好)比如value为Student 结构体

你可能感兴趣的:(go语言基础)