go语言系列-从数组到map
数组
数组可以存放多个同一类型数据。数组也是一种数据类型,在go中,数组是值类型
一个养鸡场有6只鸡,它们的体重分别是3kg,5kg,1kg,3.4kg,2kg,50kg。请问这六只鸡的总体重是多少?平均体重是多少?
使用传统的方法不利于数据的管理和维护 传统的方法不够灵活,因此我们引出需要学习的新的数据类型 ==》数组 //使用数组的方式来解决问题 var 数组名 [数组大小]数据类型 var a [5]int 赋初值 a[0] = 1 a[1] = 30 ... func main() { //1.定义一个数组 var hens [7]float64 //2. 给数组的每个元素赋值,元素的下标是从0开始的 0 - 6 hens[0] = 3.0 //hens数组的第1个元素 hens[0] hens[1] = 5.0 //hens数组的第2个元素 hens[1] hens[2] = 1.0 hens[3] = 3.4 hens[4] = 2.0 hens[5] = 50.0 hens[6] = 150.0 //增加一只鸡 //3. 遍历数组求出总体重 totalweight := 0.0 for i := 0; i < len(hens); i++ { totalweight += hens[i] } //4. 求出平均体重 avgweight := fmt.sprintf("%.2f",totalweight / float64(len(hens))) fmt.printf("totalweight = %v avgweight = %v",totalweight,avgweight) } //输出:totalweight = 214.4 avgweight = 30.63 使用数组来解决问题,增加程序的可维护性 而且方法代码更加清晰,也容易扩展
数组在内存布局
数组的地址可以通过数组名来获取 &intarr 数组的第一个元素的地址,就是数组的首地址 数组的各个元素的地址间隔是依据数组的类型决定,比如 int64 -> 8 int32 -> 4 ... 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]) } //输出:[0 0 0] //[10 20 30] //intarr的地址 = 0xc000010380 intarr[0] 地址 = 0xc000010380 intarr[1] 地址 = 0xc000010388 intarr[2] 地址 = 0xc000010390
数组的使用
func main() { var score [5]float64 for i := 0; i < len(score); i++ { fmt.printf("请输入第%d个元素的值\n",i+1) fmt.scanln(&score[i]) } //变量数组打印 for i := 0; i < len(score); i++ { fmt.printf("score[%d] = %v\t",i,score[i]) //访问数组元素:数组名[下标]比如:要使用a数组的第三个元素 a[2] } }
初始化数组的方式
func main() { //四种初始化数组的方式 var numarr01 [3]int = [3]int{1,2,3} fmt.println("numarr01 = ",numarr01) var numarr02 = [3]int{4,5,6} fmt.println("numarr02 = ",numarr02) //这里的 [...]是规定的写法 var numarr03 = [...]int{7,8,9} fmt.println("numarr03 = ",numarr03) var numarr04 = [...]int{1: 800, 0: 900, 2: 999} fmt.println("numarr04 = ",numarr04) //类型推导 strarr05 := [...]string{1: "zisefeizhu", 0: "jack", 2: "mary"} fmt.println("strarr05 = ",strarr05) } //输出:numarr01 = [1 2 3] //numarr02 = [4 5 6] //numarr03 = [7 8 9] //numarr04 = [900 800 999] //strarr05 = [jack zisefeizhu mary]
数组的遍历
方法1: 常规遍历 遍历数组求出总体重 totalweight := 0.0 for i := 0; i < len(hens); i++ { totalweight += hens[i] } 方法2:for - range 结构遍历 第一个返回值index是数组的下标 第二个value是在该下标位置的值 它们都是仅在for循环内部可见的局部变量 遍历数组元素的时候,如果不想使用下标index,可以直接把下边index标为下划线_ index和value的名称不是固定的,即程序员可以自行指定,一般命名为index和value //演示for-range遍历数组 heroes := [...]string{"宋江","吴用","林冲"} for i, v := range heroes { fmt.printf("i = %v v = %v\n",i, v) fmt.printf("heroes[%d] = %v \n",i,heroes[i]) } for _,v := range heroes { fmt.printf("元素的值 = %v\n",v) } } //输出:i = 0 v = 宋江 //heroes[0] = 宋江 //i = 1 v = 吴用 //heroes[1] = 吴用 //i = 2 v = 林冲 //heroes[2] = 林冲 //元素的值 = 宋江 //元素的值 = 吴用 //元素的值 = 林冲
数组使用的注意事项和细节
数组是多个相同类型数组的组合,一个数组一旦声明/定义了,其长度是固定的,不能动态变化 var arr []int 这时 arr就是一个slice切片,切片后面专门讲解 数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混合 数组创建后,如果没有赋值,有默认值(零值) 数值类型数组:默认值为0 字符串数组: 默认值为" " bool数组: 默认值为false 使用数组的步骤 1.声明数组并开辟空间 2.给数组各个元素赋值(默认零值) 3.使用数组 数组的下标是从0开始的 数组下标必须在指定范围内使用,否则报panic:数组越界,比如 var arr[5]int 则有效下标为 0 - 4 go的数组属于值类型,在默认情况下是值类型,因此会进行值拷贝。数组间不会相互影响
如果想在其它函数中,去修改原来的数组,可以使用引用传递(指针方式)
长度是数组类型的一部分,在传递函数参数时,需要考虑数组的长度
数组的应用案例
创建一个byte类型的26个元素的数组,分别放置’a’ - ’z’。使用for循环访问所有元素并打印出来。提示:字符数据运算’a’+1 ->’b’
func main(){ //1)创建一个byte类型的26个元素的数组,分别放置’a’ - ’z’。 // 使用for循环访问所有元素并打印出来。 // 提示:字符数据运算’a’+1 ->’b’ //思路 //1. 声明一个数组 var mychars [26]byte //2. 使用for循环,利用 字符可以进行运算的特点来辅助 'a' + 1 = 'b' //3. 使用for 循环打印 var mychars [26]byte for i := 0; i < 26; i++ { mychars[i] = 'a' + byte(i) //注意需要将i =》 byte } for i := 0; i < 26; i++ { fmt.printf("%c",mychars[i]) } //输出:abcdefghijklmnopqrstuvwxyz }
请求出一个数组的最大值,并得到对应的下标
func main() { //思路 //1. 声明一个数组 var intarr[5] = [...]int{1,-1,9,90,11} //2. 假定第一个元素就是最大值,下标就是0 //3. 然后从第二个元素开始循环比较,如果发现有更大值,则交换 var intarr [6]int = [...]int {1,-1,9,90,11,9000} maxval := intarr[0] maxvalindex := 0 for i := 1; i < len(intarr); i++ { //从第二个元素开始循环比较,如果发现有更大,则交换 if maxval < intarr[i] { maxval = intarr[i] maxvalindex = i } } fmt.printf("maxval = %v maxvalindex = %v", maxval, maxvalindex) } //输出:maxval = 9000 maxvalindex = 5
请求出一个数组的和和平均值。for-range
func main() { //思路 //1. 声明一个数组 var intarr[5] = [...]int{1,-1,9,90,11} //2. 求出和sum //3. 求出平均值 var intarr [5]int = [...]int{1, -1, 9, 90, 12} sum := 0 for _,val := range intarr { //累计求和 sum += val } //如何让平均值保留到小数 fmt.printf("sum = %v 平均值 = %v", sum, float64(sum) / float64(len(intarr)) ) } //输出:sum = 111 平均值 = 22.2
要求:随机生成五个数,并将其反转打印,复杂应用
import ( "fmt" "math/rand" "time" ) func main() { //思路 //1. 随机生成5个数,rand.intn()函数 //2. 当我们得到随机数后,就放到一个数组int数组 //3. 反转打印,交换的次数是len/2,倒数第一个和第一个元素交换,倒数第二个和第二个元素交换 var intarr [5]int //为了每次生成的随机数都不一样,我们需要给一个seed值 len := len(intarr) rand.seed(time.now().unixnano()) for i := 0; i < len; i++ { intarr[i] = rand.intn(100) //0 <= n < 100 } fmt.println("交换前",intarr) //反转打印,交换的次数是 len / 2 //倒数第一个和第一个元素交换,倒数第二个和第二个元素交换 temp := 0 //做一个临时变量 for i := 0; i < len / 2; i++ { temp = intarr[len - 1 - i] intarr[len - 1 -i] = intarr[i] intarr[i] = temp } fmt.println("交换后",intarr) } //输出:交换前 [24 15 90 17 6] //交换后 [6 17 90 15 24]
数组练习题
题目要求:
跳水比赛 8个评委打分,运动员的成绩去掉一个最高分,去掉一个最低分,剩下的6个分数的平均分就是最后得分,使现
(1)请把最高分,最低分的评委找出
(2)找出最佳评委和最差评委。最佳评委是最后得分差距最小,最差评委最后得分差距最大
分析:
设计一个函数求最高分 最低分 平均分 需要考虑存在多个最低分和最高分的情况
找最有裁判和最差裁判使用abs() 以及切片完成 将绝对值传入到切片中再遍历
package main import ( "fmt" "math") func max(array *[8]float64) (mx float64,mi float64,avg float64){ max := 0.0 for i := 0; i < len(array); i++ { if (*array)[i] > max { max = (*array)[i] } } min := max for i := 0; i < len(array); i++ { if (*array)[i] < min { min = (*array)[i] } } mx = max mi = min sum := 0.0 maxcount,mincount :=0.0,0.0 for i := 0; i < len(array); i++ { //判断最大值和最小值出现的次数 1次时直接去掉 多次是需要加上去 if (*array)[i] == max { maxcount +=1 } if (*array)[i] == min{ mincount +=1 } //算出不包含任意一次不包含 最大值、最小值的和 if (*array)[i] != max && (*array)[i] != min{ sum += (*array)[i] } } //fmt.println(maxcount,mincount) //处理出现多次最大值或者最小值 if mincount > 1.0 && maxcount > 1.0{ sum += (max*(maxcount-1)+min*(mincount-1)) }else if mincount > 1.0 && maxcount == 1.0{ sum += (min*(mincount-1)) }else if mincount ==1.0 && maxcount > 1.0{ sum += (max*(maxcount-1)) }else { sum += 0 } avg = sum/6.0 return mx,min,avg} func best(array1 *[8]float64, arry2 []float64, avg float64) { for i := 0; i < len(array1); i++ { arry2 = append(arry2, math.abs((*array1)[i]-avg)) } max := 0.0 for j :=0;j < len(arry2);j++{ if arry2[j] > max{ max = arry2[j] } } min := max for i := 0; i < len(arry2); i++ { if arry2[i] < min { min = arry2[i] } } for i := 0; i < len(arry2); i++ { if arry2[i] == min { fmt.printf("最优秀评分者为第%v位裁判,评分:%v 和平均分相差%v\n",i+1,(*array1)[i],min) } } for i := 0; i < len(arry2); i++ { if arry2[i] == max { fmt.printf("评分差距最大者为第%v位裁判,评分:%v和平均分相差%v\n",i+1,(*array1)[i],max) } }} var socre [8]float64 var socreabs = make([]float64,0,0) func main() { //输入8个裁判的分数 for i := 0;i<len(socre);i++{ fmt.printf("请输入第%v位裁判的的评分:\n",i+1) fmt.scanln(&socre[i]) } max,min,avg :=max(&socre) fmt.println(socre) fmt.printf("最高分为:%v,最低分为:%v 平均分为:%v\n",max,min,avg) //知道最大分 最小分 找最大分、最小分的裁判 for k :=0;k<len(socre);k++{ if socre[k] == max{ fmt.printf("最高分为:%v,第%v位裁判\n",max,k+1) }else if socre[k] == min { fmt.printf("最低分:%v,第%v位裁判\n",min,k+1) } } best(&socre,socreabs,avg) }
切片
切片的英文是slice
切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制
切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度len(slice)都一样
切片的长度是可以变化的,因此切片是一个可以动态变化的数组
var 切片名 []类型 比如: var a []int func main() { //演示切片的基本使用 var intarr [5]int = [...]int {1,22,33,66,99} //声明/定义一个切片 //1. slice := intarr[1:3] //2. intarr[1:3]表示slice引用到intarr这个数组 //3. 引用intarr数组的起始下标为1,最后的下标为3(但是不包含3) slice := intarr[1:3] fmt.println("intarr = ",intarr) fmt.println("slice 的元素是 = ",slice) // 22,33 fmt.println("slice 的元素个数 = ",len(slice)) // 2 fmt.println("slice 的容量 = ", cap(slice)) //切片的容量是可以动态变化 } //输出:intarr = [1 22 33 66 99] //slice 的元素是 = [22 33] //slice 的元素个数 = 2 //slice 的容量 = 4
切片在内存中形式【重要】
对上面的分析图总结
slice的确是一个引用类型 slice从底层来说,其实就是一个数据结构(struct结构体) type slice struct { ptr *[2]int len int cap int }
切片的使用
方式1:定义一个数组,然后让切片去引用一个已经创建好的数组 func main() { //演示切片的基本使用 var intarr [5]int = [...]int {1,22,33,66,99} //声明/定义一个切片 //1. slice := intarr[1:3] //2. intarr[1:3]表示slice引用到intarr这个数组 //3. 引用intarr数组的起始下标为1,最后的下标为3(但是不包含3) slice := intarr[1:3] fmt.println("intarr = ",intarr) fmt.println("slice 的元素是 = ",slice) // 22,33 fmt.println("slice 的元素个数 = ",len(slice)) // 2 fmt.println("slice 的容量 = ", cap(slice)) //切片的容量是可以动态变化 } //输出:intarr = [1 22 33 66 99] //slice 的元素是 = [22 33] //slice 的元素个数 = 2 //slice 的容量 = 4 方式2:通过make来创建切片 var 切片名 []type = make([]type,len,[cap]) 参数说明: type:就是数据类型 len :大小 cap :指定切片容量,可选,如果分配了cap,则要求cap >= len func main() { //演示切片的使用 make var slice []float64 = make([]float64, 5, 10) slice[1] = 10 slice[3] = 20 //对于切片,必须make使用 fmt.println(slice) fmt.println("slice的size = ",len(slice)) fmt.println("slice的cap = ",cap(slice)) } //输出:[0 10 0 20 0] //slice的size = 5 //slice的cap = 10
对上面代码的小结: 1)通过make方式创建切片可以指定切片的大小和容量 2)如果没有给切片的各个元素赋值,那么就会使用默认值[int, float => 0 string => “ ” bool => false] 3)通过make方式创建的切片对应的数组是由make底层维护,对外不可见,即只能通过slice去访问各个元素 方式3:定义一个切片,直接就指定具体数组,使用原理类似make的方式 func main() { var strslice []string = []string {"zisefeizhu","zhujingxing","mayike"} fmt.println("strslice = ", strslice) fmt.println("strslice size = ",len(strslice)) fmt.println("strslice cap = ",cap(strslice)) } //输出:strslice = [zisefeizhu jingxing mayike] //strslice size = 3 //strslice cap = 3 方式1和方式2的区别(面试) 方式1是直接引用数组,这个数组是事先存在的,程序员是可见的 方式2是通过make来创建切片,make也会创建一个数组,是由切片在底层进行维护,程序员是看不见的,make创建切片的示意图
切片的遍历
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 \t",i , slice[i]) } fmt.println() //使用for - range 方式遍历切片 for i, v := range slice { fmt.printf("i = %v v = %v \n",i ,v) } }
切片使用的注意事项和细节
slice扩充缩容会有内存申请释放也是开销,且扩容好像是1.25倍 切片初始化时 var slice = arr[startindex:endindex] 说明:从arr数组下标为startindex,取到下标为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一个空间供切片来使用 切片可以继续切片 func main() { var arr [5]int = [...]int {10, 20, 30, 40, 50} slice := arr[1:4] // 20, 30, 40 slice2 := slice[1:2] // slice [20,30,40] [30] slice2[0] = 100 //因为arr slice 和slice2 指向的数据空间是一个,因此slice2[0] =100 fmt.println("slice2 = ",slice2) fmt.println("slice = ",slice) fmt.println("arr = ",arr) } append内置函数,可以对切片进行动态追加 func main() { //用append内置函数,可以对切片进行动态追加 var slice []int = []int {100,200,300} //通过append直接给slice追加具体的元素 slice = append(slice,400,500,600) fmt.println("slice",slice) //100, 200, 300, 400, 500, 600 //通过append将切片slice 追加给slice slice = append(slice,slice...) fmt.println("slice",slice) ////slice [100 200 300 400 500 600 100 200 300 400 500 600] } 切片append操作的底层原理分析 切片append操作的本质就是对数组扩容 go底层会创建一下新的数组newarr(安装扩容后大小) 将slice原来包含的元素拷贝到新的数组newarr slice 重新引用到newarr 注意newarr是在底层来维护的,程序员不可见
切片的拷贝操作 切片使用copy内置函数完成拷贝,举例说明 func main() { //切片的拷贝操作 //切片使用copy内置函数完成拷贝,举例说明 var slice []int = []int {1,2,3,4,5} var slice2 = make([]int,10) copy(slice2,slice) fmt.println("slice = ",slice) //slice = [1 2 3 4 5] fmt.println("slice2 = ",slice2) //slice2 = [1 2 3 4 5 0 0 0 0 0] } copy(para1,para2)参数的数据类型是切片 按照上面的代码来看,slice和slice2的数据空间是独立,相互不影响,也就是说slice[0] = 999,slice5[0] 仍然是1 关于拷贝的注意事项 func main() { var a []int = []int {1,2,3,4,5} var slice = make([]int,1) fmt.println(slice) copy(slice,a) fmt.println(slice) } //输出:[0] //[1] 切片是引用类型,所以在传递时,遵守引用传递机制。看两段代码,并分析底层原理
string和slice
string底层是一个byte数组,因此string也可以进行切片处理
func main(){ str := "hello@zisefeizhu" //使用切片获取到zisefeizhu slice := str[6:] fmt.println("slice = ",slice) } //输出:slice = zisefeizhu
string和切片在内存的形式,以”abcd”画出内存示意图
string是不可变的,也就是说不能通过str[0] = ‘z’ 方式来修改字符串
如果需要修改字符串,可以先将string -> []byte /或者 []rune -> 修改 -> 重写转成string
如果需要修改字符串,可以先将string -> []byte /或者 []rune -> 修改 -> 重写转成string //"hello@zisefeizhu" => 改成 "zello@zisefeizhu" 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(arr1) fmt.println("str = ", str) } //输出:slice = zisefeizhu //str = zello@zisefeizhu //str = zello@zisefeizhu
切片练习题
说明:编写一个函数 fbn(n int),要求完成
-
可以接收一个n int
-
能够将斐波那契的数列放到切片中
-
提示,斐波那契的数列形式:
arr[0] = 1; arr[1] = 1; arr[2] = 2; arr[3] = 3; arr[4] = 5; arr[5] = 8
package main import "fmt" func fbn(n int) ([]uint64) { //声明一个切片,切片大小n fbnslice := make([]uint64, n) //第一个数和第二个数的斐波那契为 1 fbnslice[0] = 1 fbnslice[1] = 1 //进行for循环来存放斐波那契的数列 for i := 2; i < n; i++ { fbnslice[i] = fbnslice[i - 1] + fbnslice[i - 2] } return fbnslice } func main() { /* 1)可以接收一个n int 2)能够将斐波那契的数列放到切片中 3)提示,斐波那契的数列形式: arr[0] = 1; arr[1] = 1; arr[2] = 2; arr[3] = 3; arr[4] = 5; arr[5] = 8 思路 1. 声明一个函数fbn(n int) ([]uint64) 2. 编程fbn(n int) 进行for循环来存放斐波那契的数列 0 =》1 1 =》 1 */ fnbslice := fbn(20) fmt.println("fnbslice = ",fnbslice) } //输出:fnbslice = [1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765]
排序
排序
排序是将一组数据,依指定的顺序进行排序的过程
排序的分类 1)内部排序: 指将需要处理的所有数据都加载到内部存储器中进行排序 包括(交换式排序法、选择式排序法和插入式排序法); 2)外部排序法 数据量过大,无法全部加载到内存中,需要借助外部存储进行排序 包括(合并排序法和直接合并排序法)
冒泡排序的思路分析
冒泡排序实现
//冒泡排序 func bubblesort(arr *[5]int) { fmt.println("排序前arr = ",(*arr)) temp := 0 //临时变量(用于做交换) //冒泡排序:一步一步推导出来的 for i := 0; i < len(*arr) - 1; i++ { for j := 0; j < len(*arr) -1 - i; j++ { if (*arr)[j] < (*arr)[j + 1] { //交换 temp = (*arr)[j] (*arr)[j] = (*arr)[j + 1] (*arr)[j + 1] = temp } } } fmt.println("排序后arr = ",(*arr)) } func main() { //定义数组 arr := [...]int {24,69,80,57,13} //将数组传递给一个函数,完成排序 bubblesort(&arr) fmt.println("main arr = ",arr) //有序? 是有序的 } //输出:排序前arr = [24 69 80 57 13] //排序后arr = [80 69 57 24 13] //main arr = [80 69 57 24 13] 优化 //冒泡排序 func bubblesort(arr []int) []int { fmt.println("排序前arr = ", arr) flag := true //冒泡排序:一步一步推导出来的 for i := 0; i < len(arr) - 1; i++ { for j := 0; j < len(arr) -1 - i; j++ { if arr[j] < arr[j + 1] { //交换 swap(arr, j , j+1) flag = false } } //优化不必要的交换 if flag { break } } return arr } func swap(arr []int, i int, j int ) { temp := arr[i] arr[i] = arr[j] arr[j] = temp } func main() { //定义数组 arr := []int {24,69,80,57,13} //将数组传递给一个函数,完成排序 num := bubblesort(arr) fmt.println("main num = ",num) } //排序前arr = [24 69 80 57 13] //main num = [80 69 57 24 13]
冒泡排序练习题
package main //要求:随机生成5个元素的数组,并使用冒泡排序对其排序 从小到大 //思路分析: //随机数用math/rand生成为了更好的保证其不会重复 使用 rand.new(rand.newsource(time.now().unixnano()))并定义一个随机生成函数 //用冒泡排序对其排序 import ( "fmt" "math/rand" "time" ) var arrnum [5]int = [5]int{109,137,49,190,87} //定义冒泡函数 //重复地走访过要排序的元素列,依次比较两个相邻的元素,如果他们的顺序(如从大到小、首字母从a到z) // 错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素已经排序完成 func bubblesort( arrary *[5]int){ //第一次比较 fmt.println("排序前arr=",(*arrary)) tmp :=0 for j := 0 ; j <len(arrary)-1 ;j++{ for i :=0;i <len(arrary)-1-j ;i++ { if arrary[i] > arrary[i+1]{ tmp = arrary[i] arrary[i] = arrary[i+1] arrary[i+1] = tmp } } } fmt.println("排序后arr=",(*arrary)) } var arrname [5]int func main() { r := rand.new(rand.newsource(time.now().unixnano()))//生成随机数字 for i := 0; i < len(arrname) ; i++ { arrname[i] = r.intn(20000) //指定生成随机数的范围 } bubblesort(&arrname) }
查找
在go中,常用的查找有两种:
-
顺序查找
-
二分查找(该数组是有序)
案例演示
1)有一个数列:白眉鹰王、金毛狮王、紫衫龙王、青翼蝠王
猜数游戏:从键盘中任意输入一个名称,判断数列中是否包含此名称【顺序查找】
func main() { names := [4]string{"白眉鹰王","金毛狮王","紫衫龙王","青翼蝠王"} var heroname = "" fmt.println("请输入要查找的人名...") fmt.scanln(&heroname) //顺序查找:第一种方式 for i := 0; i < len(names); i++ { if heroname == names[i] { fmt.printf("找到%v 下标%v \n",heroname,i) break } else if i == (len(names) -1 ) { fmt.printf("没有找到%v \n",heroname) } } //顺序查找:第二种方式【推荐...】 index := -1 for i := 0; i < len(names); i++ { if heroname == names[i] { index = i //将找到的值对应的下标赋给index break } } if index != -1 { fmt.printf("找到%v,下标%v \n",heroname,index) } else { fmt.println("没有找到",heroname) } } //输出:请输入要查找的人名... 白眉鹰王 找到白眉鹰王 下标0 找到白眉鹰王,下标0
- 请对一个有序数组进行二分查找 {1,8,10,89,1000,1234},输入一个数看看该数组是否存在此数,并且求出下标,如果没有就提示”没有这个数”【会使用到递归】
二分查找的思路分析
//二分法查找 /* 二分查找思路:比如要查找的数是findval 1. arr是一个有序数组,并且是从小到大排序 2. 先找到中间的下标middle = (leftindex + rightindex)/2,然后让中间下标的值和findval进行比较 2.1 如果arr[middle] > findval 就应该向 leftindex --- (middle - 1) 2.2 如果arr[middle] < findval 就应该向 (middle + 1) --- rightindex 2.3 如果arr[middle] == findval 就找到 2.4 上面的2.1 2.2 2.3 的逻辑会递归执行 3. 想一下,怎么样的情况下,就说明找不到【分析出退出递归的条件!!】 if leftindex > rightindex { //找不到... return ... } */ func binaryfind(arr *[6]int, leftindex int, rightindex int, findval int) { //判断leftindex是否大于rightindex if leftindex > rightindex { fmt.println("找不到") return } //先找到 中间的下标 middle := (leftindex + rightindex) / 2 if (*arr)[middle] > findval { //说明要查找的数,应该在leftindex -- (middle - 1) binaryfind(arr, leftindex, middle - 1, findval) } else if (*arr)[middle] < findval { //说明要查找的数,应该在middle + 1 -- rightindex binaryfind(arr, middle + 1, rightindex, findval) } else { //找到了 fmt.printf("找到了,下标为%v \n",middle) } } func main() { arr := [6]int {1,8,89,1000,1234} //测试 binaryfind(&arr,0,len(arr) - 1, 1234) } //输出:找到了,下标为4
二维数组
请用二维数组输出如下图形
func main() { //定义/声明二维数组 var arr [4][6]int //赋初值 arr[1][2] = 1 arr[2][1] = 2 arr[2][3] = 3 //遍历二维数组,按照要求输出图形 for i := 0; i < 4; i++ { for j := 0; j < 6; j++ { fmt.print(arr[i][j]," ") } fmt.println() } } //0 0 0 0 0 0 //0 0 1 0 0 0 //0 2 0 3 0 0 //0 0 0 0 0 0
二维数组在内存的存在形式
二维数组在声明/定义时的写法
1)var 数组名 [大小][大小]类型 = [大小][大小]类型{{初值...},{初值...}} 2)var 数组名 [大小][大小]类型 = [...][大小]类型{{初值...},{初值...}} 3)var 数组名 = [大小][大小]类型{{初值...},{初值...}} 4)var 数组名 = [...][大小]类型{{初值...},{初值...}}
二维数组的遍历
func main() { //演示二维数组的遍历 var arr = [2][3]int{{1,2,3},{4,5,6}} //for循环遍历 for i := 0; i < len(arr); i++ { for j := 0; j < len(arr[i]); j++ { fmt.printf("%v\t",arr[i][j]) } fmt.println() } //for-range遍历二维数组 for i, v := range arr { for j, v2 := range v { fmt.printf("arr[%v][%v]=%v\t",i,j,v2) } fmt.println() } }
二维数组的应用案例
定义二维数组,用于保存三个班,每个班五名同学成绩,
并求出每个班级平均分、以及所有班级平均分
func main() { //定义二维数组,用于保存三个班,每个班五名同学成绩, //并求出每个班级平均分、以及所有班级平均分 //1.定义二维数组 var scores [3][5]float64 //2.循环的输入成绩 for i := 0; i < len(scores); i++ { for j := 0; j < len(scores[i]); j++ { fmt.printf("请输入第%d班的第%d个学生的成绩\n",i+1, j+1) fmt.scanln(&scores[i][j]) } } //fmt.println(scores) //3.遍历输出成绩后的二维数组,统计平局分 totalsum := 0.0 //定义一个变量,用于累计所有班级的总分 for i := 0; i < len(scores); i++ { sum := 0.0 //定义一个变量,用于累计各个班级的总分 for j := 0; j < len(scores[i]); j++ { sum += scores[i][j] } totalsum += sum fmt.printf("第%d班级的总分为%v,平均分%v\n", i + 1, sum, sum / float64(len(scores[i]))) } fmt.printf("所有班级的总分为%v,所有班级平均分%v\n", totalsum, totalsum / 15 ) }
二维数组练习题
转置概念:矩阵的行列互换得到的新矩阵称为转置矩阵,而二维数组就是我们通常说的矩阵。
需求:使用go语言方法实现二维数组(3*3)的矩阵的转置
转置前:
[ 0, 1, 2]
[ 4, 5, 6]
[ 8, 9, 10]
转置后
[ 0, 4, 8]
[ 1, 5, 9]
[ 2, 6, 10]
type num struct { } func (array num ) upserver(aaaay3 [3][3]int) { for i :=0; i<len(aaaay3);i++{ for j:=0;j<i;j++{ aaaay3[i][j],aaaay3[j][i] = aaaay3[j][i],aaaay3[i][j] } } fmt.println(aaaay3) } func (array num ) upserver2(aaaay3 [3][3]int) { temparry :=[3][3]int{} for i :=0; i<len(aaaay3);i++{ for j:=0;j<i;j++{ temparry[i][j]=aaaay3[i][j] aaaay3[i][j] =aaaay3[j][i] aaaay3[j][i]=temparry[i][j] } } fmt.println(aaaay3) } func main() { arrinfo :=num{ } aeey :=[3][3]int{ {0, 1, 2} , /* 第一行索引为 0 */ {4, 5, 6} , /* 第二行索引为 1 */ {8, 9, 10}} fmt.println(aeey) fmt.println("****") arrinfo.upserver(aeey) arrinfo.upserver2(aeey) }
map
map是key-value数据结构,又称为字段或者关联数组。类似其它编程语言的集合,在编程中是经常使用到的
v 内部实现
map是给予散列表来实现,就是我们常说的hash表,所以我们每次迭代map的时候,打印的key和value是无序的,每次迭代的都不一样,即使我们按照一定的顺序存在也不行。
map的散列表包含一组桶,每次存储和查找键值对的时候,都要先选择一个桶。如何选择桶呢?就是把指定的键传给散列函数,就可以索引到相应的桶了,进而找到对应的键值。
这种方式的好处在于,存储的数据越多,索引分布越均匀,所以我们访问键值对的速度也就越快,当然存储的细节还有很多,大家可以参考hash相关的知识,这里我们记住map存储的是无序的键值对集合
map的声明
var 变量名 map[keytype]valuetype key可以是什么类型 go中的map的key可以是很多种类型,比如bool、数字、string、指针、channel,还可以是只包含前面几个类型的 接口、结构体、数组 通常key为int、string 注意:slice、map还有function不可以,因为这几个没法用 == 来判断 valuetype可以是什么类型 valuetype的类型和key基本一样 通常为:数字(整数,浮点数)、string、map、struct map声明的举例: var a map[string]string var a map[string]int var a map[int]string var a map[string]map[string]string 注意:声明是不会分配内存的,初始化需要make,分配内存后才能赋值和使用 func main() { //map的声明和注意事项 var a map[string]string //在使用map前,需要先make,make的作用就是给map分配数据空间 a = make(map[string]string,10) a["no1"] = "松江" a["no2"] = "无用" a["no1"] = "武松" a["no3"] = "无用" fmt.println(a) } //输出:map[no1:武松 no2:无用 no3:无用] 对上面代码的说明 1)map在使用前一定要make 2)map的key是不能重复的,如果重复了,则以最后这个key-value为准 3)map的value是可以相同的 4)map的key-value是无序的 5)make内置函数数目
map的使用
方式1: func main() { //第一种使用方式 var a map[string]string //在使用map前,需要先make,make的作用就是给map分配数据空间 a = make(map[string]string,10) a["no1"] = "松江" a["no2"] = "无用" a["no1"] = "武松" a["no3"] = "无用" fmt.println(a) } 方式2: func main() { //第二种方式 cities := make(map[string]string) cities["no1"] = "北京" cities["no2"] = "天津" cities["no3"] = "上海" fmt.println(cities) //输出:map[no1:北京 no2:天津 no3:上海] 方式3: func main() { //第三种方式 heroes := map[string]string { "her01" : "宋江", "her02" : "吴用", "her03" : "林冲", } heroes["her04"] = "武松" fmt.println("heroes = ",heroes) } //heroes = map[her01:宋江 her02:吴用 her03:林冲 her04:武松]
map的增删改查操作
map增加和更新
map["key"] = value //如果key还没有,就是增加,如果key存在就是修改
func main(){ cities := make(map[string]string) cities["no1"] = "北京" cities["no2"] = "天津" cities["no3"] = "上海" fmt.println(cities) //因此no3 这个key已经存在,因此下面的这句就是修改 cities["no3"] = "上海~" fmt.println(cities) } //输出:map[no1:北京 no2:天津 no3:上海] //map[no1:北京 no2:天津 no3:上海~]
map删除
delete(map,"key"),delete是一个内置函数,如果key存在,就删除该key-value,如果key不存在,不操作,但是也不会报错
func main(){ cities := make(map[string]string) cities["no1"] = "北京" cities["no2"] = "天津" cities["no3"] = "上海" fmt.println(cities) //map[no1:北京 no2:天津 no3:上海] //因此no3 这个key已经存在,因此下面的这句就是修改 cities["no3"] = "上海~" fmt.println(cities) //map[no1:北京 no2:天津 no3:上海~] //演示删除 delete(cities,"no1") fmt.println(cities) //map[no2:天津 no3:上海~] //当delete指定的key不存在时,删除不会操作,也不会报错 delete(cities,"no4") fmt.println(cities) //map[no2:天津 no3:上海~] 如果要删除map的所有key,没有一个专门的方法一次删除,可以遍历一下key,逐个删除或者map = make(...),make一个新的,让原来的成为垃圾,被gc回收 func main(){ cities := make(map[string]string) cities["no1"] = "北京" cities["no2"] = "天津" cities["no3"] = "上海" fmt.println(cities) //map[no1:北京 no2:天津 no3:上海] //因此no3 这个key已经存在,因此下面的这句就是修改 cities["no3"] = "上海~" fmt.println(cities) //map[no1:北京 no2:天津 no3:上海~] //演示删除 delete(cities,"no1") fmt.println(cities) //map[no2:天津 no3:上海~] //当delete指定的key不存在时,删除不会操作,也不会报错 delete(cities,"no4") fmt.println(cities) //map[no2:天津 no3:上海~] //如果希望一次性删除所有的key //1. 遍历所有的key,遍历逐一删除 //2. 直接make一个新的空间 cities = make(map[string]string) fmt.println(cities) //map[] }
map查找
func main(){ cities := make(map[string]string) cities["no1"] = "北京" cities["no2"] = "天津" cities["no3"] = "上海" fmt.println(cities) //map[no1:北京 no2:天津 no3:上海] //因此no3 这个key已经存在,因此下面的这句就是修改 cities["no3"] = "上海~" fmt.println(cities) //map[no1:北京 no2:天津 no3:上海~] //演示map 的查找 va1, ok := cities["no2"] if ok { fmt.printf("有no2 key值为%v\n",va1) //有no2 key值为天津 } else { fmt.printf("没有no2 key\n") } } 对上面代码的说明: 说明:如果cities这个map中存在”no2”,那么ok就会返回true,否则返回false
map遍历
案例演示相对复杂的map遍历:该map的value又是一个map
说明:map的遍历使用for-range的结构遍历
func main() { //使用for-range遍历map cities := make(map[string]string) cities["no1"] = "北京" cities["no2"] = "天津" cities["no3"] = "上海" for k,v := range cities { fmt.printf("k = %v v = %v\n",k,v) } //使用for-range遍历一个结构比较复杂的map 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"] = "上海黄埔江" 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() } } //输出:k = no1 v = 北京 //k = no2 v = 天津 //k = no3 v = 上海 //k1 = stu01 // k2 = name v2 = tom // k2 = sex v2 = 男 // k2 = address v2 = 北京长安街 // //k1 = stu02 // k2 = name v2 = mary // k2 = sex v2 = 女 // k2 = address v2 = 上海黄埔江
map的长度
func main() { //使用for-range遍历map cities := make(map[string]string) cities["no1"] = "北京" cities["no2"] = "天津" cities["no3"] = "上海" for k,v := range cities { fmt.printf("k = %v v = %v\n",k,v) } fmt.println(len(cities)) //3 }
map切片
切片的数据类型如果是map,则我们称为slice of map,map切片,这样使用则map个数就可以动态变化了
案例演示
要求:使用一个map来记录monster的信息name和age,也就是说一个monster对应一个map,并且妖怪的个数可以动态的增加=》map切片
package main import ( "fmt" _ "unicode" ) func main() { 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" } //下面这个写法越界 //if monsters[2] == nil { // monsters[2] = make(map[string]string, 2) // monsters[2]["name"] = "狐狸精" // monsters[2]["age"] = "300" // } //这里需要使用到切片的append函数,可以动态的增加monster //1. 先定义monster信息 newmonster := map[string]string { "name" : "新的妖怪-孙悟空", "age" : "1500", } monsters = append(monsters,newmonster) fmt.println(monsters) } //输出:[map[age:500 name:牛魔王] map[age:400 name:玉兔精] map[age:1500 name:新的妖怪-孙悟空]]
map排序
go中没有一个专门的方法针对map的key进行排序
go中的map默认是无序的,注意也不是按照添加的顺序存放的,每次遍历,得到的输出可能不一样
go中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]) } } //输出:map[1:13 4:56 8:90 10:100] //[1 4 8 10] //map1[1] = 13 //map1[4] = 56 //map1[8] = 90 //map1[10] = 100
map使用细节
map是引用类型,遵守引用类型传递的机制,在一个函数接收map,修改后,会直接修改原来的map
func modify(map1 map[int]int) { map1[10] = 900 } func main() { map1 := make(map[int]int) map1[1] = 90 map1[2] = 88 map1[10] = 1 map1[20] = 2 modify(map1) fmt.println(map1) } //输出:map[1:90 2:88 10:900 20:2]
map的容量达到后,再想map增加元素,会自动扩容,并不会发生panic,也就是说map能动态的增长 键值对(key-value)
map的value也经常使用struct类,更适合管理复杂的数据(比前面value是一个map更好),比如value为student结构体
type stu struct { name string age int address string } func main() { //3)map的value也经常使用struct类型, // 更适合管理复杂的数据(比前面value是一个map更好), // 比如value为student结构体 //1. map 的 key 为学生的学号,是唯一的 //2. map的value 为结构体,包含学生的名字,年龄,地址 students := make(map[string]stu, 10) //创建2个学生 stu1 := stu{"tom", 18, "北京"} stu2 := stu{"mary", 28, "上海"} students["no1"] = stu1 students["no2"] = stu2 fmt.println(students) //遍历各个学生信息 for k, v := range students { fmt.printf("学生的编号是%v \n",k) fmt.printf("学生的名字是%v \n",v.name) fmt.printf("学生的年龄是%v \n",v.age) fmt.printf("学生的地址是%v \n",v.address) } } //map[no1:{tom 18 北京} no2:{mary 28 上海}] //学生的编号是no1 //学生的名字是tom //学生的年龄是18 //学生的地址是北京 //学生的编号是no2 //学生的名字是mary //学生的年龄是28 //学生的地址是上海
map练习题
演示一个key-value的value是map的案例
比如:要存放3个学生信息,每个学生有name和sex信息
思路: map[string]map[string]string
func main() { 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) //map[stu01:map[address:北京长安街 name:tom sex:男] stu02:map[address:上海黄埔江 name:mary sex:女]] fmt.println(studentmap["stu02"]) //map[address:上海黄埔江 name:mary sex:女] fmt.println(studentmap["stu02"]["address"]) //上海黄埔江 }
编写一个函数modifyuser(users map[string]map[string]string,name string)完成上下述功能
-
使用map[string]map[string]string的map类型
-
key:表示用户名,是唯一的,不可以重复
-
如果某个用户存在,就将其密码修改"888888",如果不存在就增加这个用户信息(包括昵称nickname和密码pwd)。
func modifyuser(users map[string]map[string]string,name string) { //判断users中是否有name //v,ok := users[name] if users[name] != nil { //有这个用户 users[name]["pwd"] = "888888" } else { //没有这个用户 users[name] = make(map[string]string,2) users[name]["pwd"] = "888888" users[name]["nickname"] = "昵称~" + name //示意 } } func main() { users := make(map[string]map[string]string,10) users["smith"] = make(map[string]string,2) users["smith"]["pwd"] = "999999" users["smith"]["nickname"] = "小花猫" modifyuser(users,"tom") modifyuser(users,"mary") modifyuser(users,"smith") fmt.println(users) } //输出:map[mary:map[nickname:昵称~mary pwd:888888] smith:map[nickname:小花猫 pwd:888888] tom:map[nickname:昵称~tom pwd:888888]]
上一篇: 秦朝和汉朝之间真的有一个朝代 这个政权能不能成为王朝
下一篇: 用python代替人脑运算24点游戏
推荐阅读