文章目录
一、数组
1.为什么需要数组
问题:
一个养鸡场有6只鸡,他们的体重分别是3kg .5kg 1kg 3.4kg 2kg 50kg请问这六只鸡的总体重是多少?平均体重是多少?请你编写一个程序
传统方法:定义六个变量进行求值即可
问题:不利于数据的管理和维护,不够灵活,我们需要使用新的数据类型数组
数组介绍
数组可以存放多个同一类型的数据。数组也是一种数据类型,在Go中数组就是值类型
2.数组快速入门
/*
一个养鸡场有6只鸡,他们的体重分别是3kg .5kg 1kg 3.4kg
2kg 50kg请问这六只鸡的总体重是多少?平均体重是多少?
请你编写一个程序
*/
func main(){
//使用数组来解决问题
//1.定义一个数组
var hens [6]float64
//2.给数组的每个元素赋值操作,元素下标从0开始
hens[0] =3.0 //hens数组的第一个元素赋值
hens[1] =5.0
hens[2] =1.0
hens[3] =3.4
hens[4] =2.0
hens[5] =50.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("鸡的总体重是:%v,平均体重是%v",totalweight,avgweight)
}
对上面代码总结
1)使用数组解决问题,程序的可维护性增加
2)而且的方法代码更加清晰,也容易扩展
3、数组的定义和内存布局
数组的定义
var 数组名[数组大小] 数据类型
var a[5]int
赋初值 a[0]=1 a[1]=30 ...
数组内存(重要)
func main(){
var intArr [3]int
//当我们定义完数组后,数组的各个元素有默认值0
fmt.Println(intArr)//[0 0 0]
fmt.Printf("数组的地址是:%p",&intArr)//数组的地址是:0xc0420082c0
fmt.Printf("数组首地址是:%p",&intArr[0])
数组的首地址是:0xc0420082c0
}
- 数组的地址可以通过数组名来获取:&intArr
- 数组的第一个元素的地址就是数组的首地址
func main(){
var intArr [3]int//int占8个字节 如果是int32就是4个字节
//当我们定义完数组后,数组的各个元素有默认值0
fmt.Println(intArr)//[0 0 0]
fmt.Printf("数组的地址是:%p\n",&intArr)
//数组的地址是:0xc0420082c0
fmt.Printf("数组首地址是:%p,地址intArr[1]的地址是%p",&intArr[0],&intArr[1])
//数组首地址是:0xc04205c0a0,地址intArr[1]的地址是0xc04205c0a8
}
数组的使用
访问数组元素
数组名[下标]比如:你要使用a数组的第三个元素 a[2]
案例:
循环输入5个成绩,保存到float64数组,并输出
func arry(){
//从终端输入5个成绩,保存到float64数组,并输出
var score[5]float64
for i:=0;i<5;i++{
fmt.Printf("请输入第%v个元素的值:",i)
fmt.Scanln(&score[i])
}
// fmt.Println("score的值是",score)
//遍历数组打印
for i :=0;i< len(score);i++{
fmt.Printf("score[%v]=%v\n",i,score[i])
}
}
四种初始化数组的方式:
func main(){
// 四种初始化数组的方式
//way1
var numArr01 [3]int=[3]int{1,2,3}
fmt.Println("numArr01=",numArr01)
//输出结果为:numArr= [1 2 3]
//way2
var numArr02 =[3]int{5,6,7}
fmt.Println("numArr02=",numArr02)
//输出结果为:numArr02= [5 6 7]
//way3
var numArr03 =[...]int{8,9,10}
//这里的[...]是规定的写法
fmt.Println("numArr03=",numArr03)
//numArr03= [8 9 10]
// way4
var numArr04 =[...]int{1:800,0:900,2:999}
fmt.Println("numArr04=",numArr04)
//numArr04= [900 800 999]
//类型推导
numArr05 :=[...]string{1:"tom",0:"jfon",2:"feilipu"}
fmt.Println("numArr05=",numArr05)
//numArr05= [jfon tom feilipu]
}
数组的遍历
for -range结构遍历
这是Go语言一种独有的遍历,可以用来遍历访问数组元素
基本语法
for index,value := range array01{
...
}
说明
- 第一个返回值index是数组的下标
- 第二个value是在该下标位置的值
- 他们都是仅在for循环内部可见的局部变量
- 遍历数组元素的时候,如果不想使用下标index,可以直接把下标index标为下划线
- index和value的名称不是固定的,即程序员可以自行指定,一般命名为index和value
案例演示:
func main(){
//演示for -range遍历数组
heroes :=[...]string{"刘备","张飞","关羽"}
fmt.Println(heroes)
//for -range遍历
for i,v :=range heroes{
fmt.Printf("i=%v,v=%v",i,v)//i=0,v=刘备i=1,v=张飞i=2,v=关羽s
//除此之外这样遍历也可以
fmt.Printf("heroes[%d]=%v\n",i,heroes[i])
}
//不要元素的下标只要元素的值可以这样写:
for _,v :=range heroes{
fmt.Printf("元素的值=%v\n",v)
}
}
}
数组的注意事项和细节
1)数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的,不能动态变化
//1)数组是多个相同类型数据的组合,一个数组一旦
// 声明/定义了,其长度是固定的,不能动态变化
var arr01 [3]int
arr01[0] =1
arr01[1] =30
//arr01[2] =1.1//这里会报错类型不一致
arr01[2] =90
// arr01[3] =890//数组会发生越界,超出指定范围长度
fmt.Println(arr01)
2)var arr []int这时arr是一个slice切片
数组需要写大小 var arr[3]int这样的写法才是数组
3)数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用
4)数据创建后,如果没有赋值,有默认值
数组类型数组 默认值为0
字符串数组,默认值""
bool数组,默认值为false
//数组创建后,如果没有赋值,有默认值(0值)
//1.数值(整数系列,浮点数系列)=》0
//2.字符串==》""
//3.bool类型 ==》flase
var arr01 [3]float32
var arr02 [3]string
var arr03 [3]bool
fmt.Printf("arr01=%v arr02=%v arr03=%v",arr01,arr02,arr03)
//输出结果 arr01=[0 0 0] arr02=[ ] arr03=[false false false]
5)使用数组的步骤:1.声明数组并开辟空间 2给数组各个元素赋值 3使用数组
6)数组的下标是从0开始的
//数组的下标是从0开始
var arr04 [3]string //0-2
fmt.Println(arr04[3])// 报错,原因是数组越界
7)数组下标必须在指定范围内使用,否则报panic,数组越界比如:var arr[5]int 则下标为0~4
8)Go的数组属于值类型,在默认情况下不是值传递,因此会进行值拷贝。数组间不会相互影响
//函数
func test01(arr [3]int){
arr[0] = 88
}
main中进行调用
arr := [3]int{11,22,33}
test01(arr)
fmt.Println(arr)//输出结果仍然是:[11 22 33] 无影响
9)如想在其它函数中,去修改原来的数组,可以使用引用传递【指针方式】
//函数
func test02(arr *[3]int){
(*arr)[0] = 88
}
main中进行调用
arr := [3]int{11,22,33}
test02(&arr)
fmt.Println("main里面的arr的值是",arr)
//输出的内容是
// main里面的arr的值是 [88 22 33]
10)长度是数组类型的一部分,在传递函数参数时,需要考虑数组的长度看案例
题1 默认值拷贝
func modify(arr []int){ //编译就直接报错因为没有指定长度
arr[0] = 100
fmt.Println("modify的值arr",arr)
}
func main(){
var arr = [...]int{1,2,3}
modify(arr)
}
题2 默认值拷贝
func modify(arr [4]int){
arr[0] = 100
fmt.Println("modify的值arr",arr)
}
func main(){
var arr = [...]int{1,2,3}
modify(arr)
}
题2 默认值拷贝
func modify(arr [4]int){
arr[0] = 100
fmt.Println("modify的值arr",arr)
}
func main(){
var arr = [...]int{1,2,3}
modify(arr)
}
//编译错误,长度是数据类型的一部分
题2 默认值拷贝
func modify(arr [3]int){
arr[0] = 100
fmt.Println("modify的值arr",arr) 100 2 3
}
func main(){
var arr = [...]int{1,2,3}
modify(arr) // 1 2 3
}
//这个正确,但是不能修改成功
数组的应用案例
1)创建一个byte类型的26个元素的数组,分别放置’A’-‘Z’,使用for循环访问所有元素并打印出来,提示:字符数据运算’A’+1->‘B’
func th1(){//自己写的
//声明一个数组
var arr [26]byte
arr[0]='A'
for i :=1;i<26;i++{
arr[i]=arr[i-1]+1
}
for a,v :=range arr{
fmt.Printf("arr[%d]=%c ",a,v)
}
}
func the1(){//老师写的
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])
}
}
func main(){
//th1()
the1()
}
2)请求出一个数组的最大值,并得到对应的下标
//请求出一个数组的最大值,并得到对应的下标
/*
1.声明一个数组[6]int{12,56,7,9,23,1}
2.假定第一个数为最大值,下标就为0
3,然后从第二个元素开始循环比较,如果发现有更大则交换
*/
func the2(){
arr :=[6]int{12,56,7,90,23,1}
var max int =arr[0]
maxValIndex :=0
for i :=1;i<len(arr);i++{
if arr[i]>max{
max=arr[i]
maxValIndex=i
}
}
fmt.Printf("max=%v,index=%v",max,maxValIndex)
}
3)请求出一个数组的和以及他的平均值 。for-range
//请求出一个数组的和以及平均值。for-range
func suma(){
//1.声明一个数组arr :=[6]int{12,56,7,90,23,1}
//2.求出sum
//3.求出平均值
arr :=[6]int{12,57,7,90,23,1}
sum :=0
for _,v :=range arr{
//累积求和
sum += v
}
fmt.Printf("数组的和是%v,数组的平均值是%.2f",sum,float64(sum)/float64(len(arr)))
}
func main(){
suma()
}
数组的复杂使用—数组反转
要求:随机生成5个数,并将其反转打印
/*
要求:随机生成5个数,并将其反转打印
思路
1.随机生成5个数,rand.Intn()函数
2,当我们得到随机数后就放到一个数组中 int数组
3.反转打印
*/
func fanzhuan(){
var intArr3 [5]int
len :=len(intArr3) //先算出数组的长度,避免反复调用
//为了每次生成的随机数都不一样,我们需要给一个seed值
rand.Seed(time.Now().UnixNano())
for i := 0; i < len);i++{
intArr3[i] =rand.Intn(100) //0<=n<=100
}
fmt.Println("交换前:",intArr3)
//3.反转打印,交换的次数是len/2 2.倒数第一个和第一个交换倒数第二个与第二个进行交换
temp :=0 //作为一个临时变量用于交换操作
for i := 0; i < len/2;i++{
temp =intArr3[len-1 -i] //倒数第n个和第n个元素进行交换
intArr3[len-1 -i]=intArr3[i]
intArr3[i] = temp
}
fmt.Println(intArr3)
fmt.Println("交换后:",intArr3)
最后main中调用即可
4.slice切片
为什么需要切片?
先看一个需求:我们需要一个数组用于保存学生的成绩,但是学生的个数是不确定的,请问怎么办,解决方案:使用切片
1.基本介绍
1)切片的英文slice
2)切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制
3)切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度len(slice)都一样
4)切片的长度是可以变化的,因此切片是一个可以动态变化数组
5)切片定义的基本语法
var 变量名 []类型
比如: var a []int
入门案例
func main(){
//演示切片的基本使用
var intArr [5]int = [...]int{1,22,33,66,99}
//声明定义一个切片
/*
1.slice 就是切片的名称
2.intArr[1:3] 表示slice 引用到intArr这个数组
3.应用inArr数组的起始下标为1终止下标为3不包含3
*/
slice := intArr[1:3]
fmt.Println("intArr=",intArr) // intArr= [1 22 33 66 99]
fmt.Println("slice 的元素是=",slice)//slice 的元素是= [22 33]
fmt.Println("slice 的元素的个数是=",len(slice))//slice 的元素的个数是= 2
fmt.Println("slice 的容量是=",cap(slice))//slice 的容量是= 4
//切片的容量是可以动态变化的 cability
fmt.Printf("intArr[1]的地址=%p\n",&intArr[1])
fmt.Printf("slice[0]的地址=%p slice[0]=%v\n",&slice[0],slice[0])
slice[0]=34 //相当于*intArr[1]=34
fmt.Println("intArr=",intArr)//intArr= [1 34 33 66 99]
}
2.切片在内存中的形式
为了让大家更加深入的理解切片,我们画图分析一下切片在内存中是如何布局的
这是一个非常重要的知识点
1)以前面的案例来分析切片在内存中的布局
2)切片底层的数据结构可以理解成一个结构体struct
3)输出切片和切片的引用地址
2.切片使用的三种方式
way1
第一种方式:定义一个切片,然后让切片去引用一个已经创建好的数组像前面的案例
func main(){
//演示切片的基本使用
var intArr [5]int = [...]int{1,22,33,66,99}
//声明定义一个切片
/*
1.slice 就是切片的名称
2.intArr[1:3] 表示slice 引用到intArr这个数组
3.应用inArr数组的起始下标为1终止下标为3不包含3
*/
slice := intArr[1:3]
fmt.Println("intArr=",intArr) // intArr= [1 22 33 66 99]
fmt.Println("slice 的元素是=",slice)//slice 的元素是= [22 33]
fmt.Println("slice 的元素的个数是=",len(slice))//slice 的元素的个数是= 2
fmt.Println("slice 的容量是=",cap(slice))//slice 的容量是= 4
way2
第二种方式:通过make来创建切片
基本语法:
var 切片名 []type = make([].len,[cap])
参数说明:type就是数据类型 len:大小 cap指定切片的容量可选
案例演示
func main(){
var slice []int =make([]int,4,10)
fmt.Println(slice)//默认值为0
fmt.Println("slice len=",len(slice),"slice cap=",cap(slice))
slice[0]=100
slice[2]=100
fmt.Println(slice)
}
//演示切片的使用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))
内部有个数组是不可见的
对上面代码的小结:(面试重点)
1)通过make方式创建切片可以指定切片的大小和容量
2)如果没有给切片的各个元素赋值,那么就会使用默认值[int,float=>0 string=>" " bool=>false]
3)通过make方式创建的切片对应的数组是由make底层维护,对外不可见,即只能通过slice方式去访问
way3
第3中方式:定义一个切片,直接就指定具体数组,使用原理类似make方式
//第3中方式:定义一个切片,直接就指定具体数组,使用原理类似make方式
var strSlice []string = []string {"tom","jack","mary"}
fmt.Println(strSlice)
fmt.Println("strSlice的size=",len(strSlice))//3
fmt.Println("strSlice的cap=",cap(strSlice))//3
3.切片的遍历
切片的遍历和数组一样,也有两种方式‘
1)for循环常规遍历方式
案例演示
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])//slice[0]=20 slice[1]=30 slice[2]=40
}
2)for-range结构遍历切片
案例演示:
//使用for -range 方式遍历切片
for i,v :=range slice{
fmt.Printf("slice[%v]=%v ",i,v)slice[0]=20 slice[1]=30 slice[2]=40
}
3.切片的注意事项
1)切片初始化时 var slice = arr[start index:endindex]
说明:从arr数组下标为startindex,取到下标为endindex的元素(不含 arr[endindex])
2)切片初始化时,仍然不能越界。范围在[0-len(arr)]之间,但是可以动态增长
1)var slice = arr[0:end]可以简写 var slice = arr[:end]
2)var slice = arr[start:len(arr)]可以简写:var slice = arr[start:]
3)var slice = arr[0:len(arr)]可以简写:var slice = arr[:]
-
cap是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素
-
切片定义完后,还不能使用,因为本身是一个空的,需要让其引用到一个数组或者make一个空间供切片来使用
-
切片可以继续切片
slice2 := slice[1:2] //20 fmt.Println("slice1=",slice2) //[20]
当一个切片元素发生变化,其关联的数组和其他切片也会变化。因为切片是引用数据类型
3)用append内置函数,可以对切片进行动态追加
//用append内置函数,可以对切片进行动态追加 var slice3 []int = []int{100,200,300} //通过append直接对slice3追加具体的元素 slice3 = append(slice3,400,500,600) fmt.Println("slice3=",slice3) //通过append将切片slice3追加到slice3 slice3 = append(slice3,slice3...) fmt.Println("slice3=",slice3) }
4)切片append操作的底层原理分析
- 切片append操作的本质就是对数组的扩容
- go底层会创建一个新的数组newArr(安装扩容后的大小)
- 将slice原来包含的元素拷贝到新的数组newArr
- slice重新引用到newArr
- 注意newArr是在底层来维护的,程序员是不可见的
- 案例演示
5)切片的拷贝操作
切片使用copy内置函数完成拷贝
//切片的拷贝操作
//切片的copy内置函数完成拷贝,举例说明
var slice4 []int =[]int{1,2,3,4,5}
var slice5 = make([]int,10)
copy(slice5,slice4)//将slice4拷贝给slice5
fmt.Println("slice4=",slice4)//[1 2 3 4 5]
fmt.Println("slice5=",slice5)//[1 2 3 4 5 0 0 0 0 0]
(1)说明:copy(para1,para2):para1和para2都是切片类型
(2)按照上面的代码来看,slice4和slice5的数据空间是独立的,相互不影响,也就是说slice[0]=9999,slice5[0]仍然是1不会受到影响
思考题
var a []int =[]int{1,2,3,4,5}
var slice5 = make([]int,1)
copy(slice5,a)//ok只拷贝一个元素
fmt.Println(slice5) //[1]
上面的代码没有问题
切片式引用类型,所以在传递时,遵守引用传递机制
func main(){
var slice[]int
var arr[5]int = [...]int{1,2,3,4,5}
slice = arr[:]
var slice2 = slice
slice2[0] =10
fmt.Println("slice2",slice2) //[10,2,3,4,5]
fmt.Println("slice",slice)//[10,2,3,4,5]
fmt.Println("arr",arr) //[10,2,3,4,5]
}
func test(slice []int){
slice[0]=100
}
func main(){
var slice = []int{1,2,3,4}
fmt.Println("slice=",slice)//[1,2,3,4]
test(slice)
fmt.Println("slice=",slice)[100,2,3,4]
}
4.string和slice
1)string底层是一个byte数组,因此string也可以进行切片处理
案例演示:
func main(){
//string底层是一个byte数组,因此string也可以进行切片处理
str:= "hello mrliu"
//使用切片获取mrliu
slice :=str[6:]
fmt.Println("slice",slice) //slice=mrliu
}
2)string和切片在内存种的形式,”abcd“画出内存示意图
3)string是不可改变的,也就是说不能通过str[0]='z’方式来修改字符串
//string底层是一个byte数组,因此string也可以进行切片处理
str:= "hello mrliu"
//使用切片获取mrliu
slice :=str[6:]
fmt.Println("slice",slice) //slice=mrliu
//string是不可改变的,也就是说不能通过str[0]='z'方式来修改字符串
// str[0] = 'z' //错误,编译不会通过string是不可变的
4)如果需要修改字符串,可以先将string->[]byte / 或者 []rune ->修改->重写转成string
//如果需要修改字符串,可以先将string->[]byte / 或者 []rune ->修改->重写转成string
"hello mrliu"=》改成"zello mrsliu"
arr1 := []byte(str)
arr1[0]='z'
str = string(arr1)
fmt.Println(str)//zello mrliu
//细节:我们转成[]byte后,可以处理英文和数字,但是没办法处理中文
//原因是 []byte字节来处理,而一个汉字是3个字节。因此就会出现erro
//解决办法是将string转成[]rune即可,因为[]rune是按照字符处理兼容汉字
arr1 := []rune(str)
arr1[0]='北' //utf-8 21271
str = string(arr1)
fmt.Println(str)//北ello mrliu
4.切片的课堂练习
说明:编写一个函数fbn(n int),要求完成
1)可以接收一个n int
2)能够将斐波那契的数列放到切片中
3)提示,斐波那契的数列形式
arr[0] =1;arr[1]=1;arr[2]=2;arr[3]=3;arr[4]=5;arr[5]=8
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(){
/*
说明:编写一个函数fbn(n int),要求完成
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循环来存放斐波那契数列
*/
fnbSlice :=fbn(10)
fmt.Println(fnbSlice) //[1 1 2 3 5 8 13 21 34 55]
}
5.二维数组
(2)快速入门案例
请使用二维数组输出如下图形
000000
001000
020300
000000
使用方式:先声明/定义再赋值
实现:
1)语法:
var 数组名[大小][大小]类型
var arr [2][3]int,再赋值
2)代码演示
/*
000000
001000
020300
000000
定义声明一个二维数组
*/
func demo1(){
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()
}
}
func main(){
demo1()
}
4)二维数组在内存中的存在形式(重点)
func arrmemory(){
var arr2 [2][3]int
arr2 [1][1]=10
fmt.Println(arr2)
fmt.Printf("arr2[0]的地址是%p\n",&arr2[0])
//arr2[0]的地址是0xc04207a030 与arr2[1]相差24个字节
fmt.Printf("arr2[1]的地址是%p\n",&arr2[1])
//arr2[1]的地址是0xc04207a048
fmt.Printf("arr2[0][0]的地址是%p\n",&arr2[0][0])
//arr2[0][0]的地址是0xc04207a030
fmt.Printf("arr2[1][0]的地址是%p\n",&arr2[1][0])
// arr2[1][0]的地址是0xc04207a048
}
(2)使用方式2:直接初始化
1)声明 :
var 数组名 [大小][大小]类型 =[大小][大小]类型{{初值..},{初值...}}
2)赋值(有默认值,比如 int 类型就是0)
3)使用演示
func demo3 (){
var arr3 [2][3]int = [2][3]int{{1,2,3},{4,5,6}}
fmt.Println("arr3=",arr3)//arr3= [[1 2 3] [4 5 6]]
}
4)说明:二维数组在声明/定义时也应有四种写法【和一维数组类似】
var 数组名 [大小] [大小]类型 = [大小][大小]类型{{初值...},{初值...}}
var 数组名 [大小] [大小]类型 = [...][大小]类型{{初值...},{初值...}}
var 数组名 =[大小][大小]类型{{初值...},{初值...}}
var 数组名 =[...][大小]类型{{初值...},{初值...}}
(3)二维数组的遍历
1)双层for循环完成遍历
案例:
func bainli(){
//演示二维数组的遍历
var arr3 = [2][3]int{{1,2,3},{4,5,6}}
//for循环来遍历
for i :=0;i<len(arr3);i++{
for j :=0; j < len(arr3[i]); j++{
fmt.Printf("%v\t",arr3[i][j])
}
fmt.Println()
}
}
2)for-range方式完成遍历
案例演示:
// for -range遍历
for i,v := range arr3{
for j , v2 := range v{
fmt.Printf("arr3[%v][%v]=%v\t",i,j,v2)
}
fmt.Println()
}
(3)二维数组的应用案例
定义一个二维数组,用于保存三个班,每个班五名同学成绩,并求出每个班级的平均分、以及所有班级的平均分
package main
import (
"fmt"
)
/*
定义一个二维数组,用于保存三个班,每个班五名同学成绩,
并求出每个班级的平均分、以及所有班级的平均分
*/
func classf(){
//定义一个二维数组
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)
//遍历输出成绩后的二维数组,统计平均分
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",
totalSum,totalSum/15)
}
func main(){
classf()
}
1.排序
1)排序的基本介绍
排序就是将一组数据,依指定的顺序进行排列的过程
2)排序的分类:
(1)内部排序:
指将需要处理的所有数据都加载到内部存储器中进行排序。
包括(交换式排序法、选择式排序法和****插入排序法)
(2)外部排序法
数据量过大,无法全部加载到内存中,需要借助外部存储进行排序。包括(合并排序法和直接合并排序法)
3)交换式排序
交换式排序属于内部排序法,是运用数据值比较后,依判断规则对数据位置进行交换,以达到排序的目的
交换式排序又分为两种
1)冒泡排序法(Bubble sort)
2)快速排序法(Quick sort)
4)交换式排序法–冒泡排序
(1)基本思想
冒泡排序(Bubble sort0)的基本思想是:通过对待排序序列从后向前(从下标较大的元素开始),依次比较相邻元素的排序码,若发现逆序交换,使排序码较小的元素逐渐从后部移向前部(从下标较大的单元移向下标较小的单元),就像水底下的气泡一样逐渐向上冒。
因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换,从而减少不必要的比较
(2)案例
我们将5个数字:24,69,80,57,13使用冒泡排序将其排成一个从小到大的有序数列
package main
import (
"fmt"
)
//冒泡排序
func BubbleSort(arr *[5]int){
fmt.Println("排序前的ar=",(*arr))
temp :=0//临时变量用来做交换的
//完成第一轮排序(外层排序)
for j :=0;j < 4;j++{
if (*arr)[j] > (*arr)[j+1]{
//交换
temp = (*arr)[j]
(*arr)[j]=(*arr)[j+1]
(*arr)[j+1] = temp
}
}
fmt.Println("第一次排序过后arr=",(*arr))//[24 69 57 13 80]
//完成第二轮排序(外层排序)
for j :=0;j < 3;j++{
if (*arr)[j] > (*arr)[j+1]{
//交换
temp = (*arr)[j]
(*arr)[j]=(*arr)[j+1]
(*arr)[j+1] = temp
}
}
fmt.Println("第三次排序过后arr=",(*arr))//[24 57 13 69 80]
//完成第二轮排序(外层排序)
for j :=0;j < 2;j++{
if (*arr)[j] > (*arr)[j+1]{
//交换
temp = (*arr)[j]
(*arr)[j]=(*arr)[j+1]
(*arr)[j+1] = temp
}
}
fmt.Println("第三次排序过后arr=",(*arr))//arr= [24 13 57 69 80]
//完成第四轮排序(外层排序)
for j :=0;j < 1;j++{
if (*arr)[j] > (*arr)[j+1]{
//交换
temp = (*arr)[j]
(*arr)[j]=(*arr)[j+1]
(*arr)[j+1] = temp
}
}
fmt.Println("第四次排序过后arr=",(*arr))完成第二轮排序(外层排序)
for j :=0;j < 2;j++{
if (*arr)[j] > (*arr)[j+1]{
//交换
temp = (*arr)[j]
(*arr)[j]=(*arr)[j+1]
(*arr)[j+1] = temp
}
}
fmt.Println("第四次排序过后arr=",(*arr))//[13 24 57 69 80]
}
func BubbleSort2(arr *[5]int){
fmt.Println("排序前的ar=",(*arr))
temp :=0//临时变量用来做交换的
len :=len((*arr))
//完成第一轮排序(外层排序)
for i :=0;i<len-1;i++{
for j :=0;j < len-i-1;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 := [5]int{24,69,80,57,13}
//将数组传递给一个函数,完成排序
//BubbleSort(&arr)
BubbleSort2(&arr) //调用改写之后的冒泡排序
}
2.查找
1)介绍
在Golang中,我们常用的查找有两种
(2) 顺序查找
(2)二分查找
2)案例演示
(1)有一个数列:白眉鹰王、金毛狮王、紫衫龙王、青翼斧王
猜数游戏:从键盘任意输入一个名称,判断数列中是否包含此名称
package main
import (
"fmt"
)
/*
(1)有一个数列:白眉鹰王、
金毛狮王、紫衫龙王、青翼斧王
猜数游戏:从键盘任意输入一个名称,
判断数列中是否包含此名称
思路:
1.定义一个数组:白眉鹰王、金毛狮王、紫衫龙王、青翼斧王
2.从控制台接收一个名词,依次比较如果发现有就提示
*/
func find1(){
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",heroName,i)
break
} else if i ==(len(names)-1){//判断当i为最后一个下标
fmt.Printf("没有找到%v",heroName)
}
}
}
//顺序查找第二种方式(推荐)
func find2(){
names := [4]string{"白眉鹰王","金毛狮王","紫衫龙王","青翼斧王"}
var heroName = " "
fmt.Println("请输入要查找的人名...")
fmt.Scanln(&heroName)
index := -1
for i :=0; i < len(names); i++{
if heroName == names[i]{
index =i
break
}
}
if index != -1{
fmt.Printf("找到了%v,下标%v",heroName,index)
}else{
fmt.Printf("没有找到%v",heroName)
}
}
func main(){
// find1()
find2()
}
(2)请对一个有序数列进行二分查找{1,8,10,89,1000,1234}.输入一个数看看该数组是否存在此数,并且求出下标,如果没有就提示“没有这个数”会使用到递归
二分法查找的思路分析
arr = [1,8,10,89,1000,1234] 8
二分法查找的思路:比如我们要查找的数是findVal
1.arr是有一个有序数组,并且是从小到大排序
2.先找到中间的下标middle =(leftindex + rightindex)/2然后让中间的值和findval进行比较
逻辑:
2.1如果arr[middle]>findval,就应该问 leftindex----(middle -1)
2.1如果arr[middle]<findval,就应该问 middel+1----right
2.3如果Arr[middle]==findVal就找到
对上面的逻辑进行递归执行
递归退出条件
if lefetindex > rightindex
//找不到
return ..
思路---代码
func BinaryFind(arr *[6]int,leftindex,rightindex,findVal int){
//判断leftIndex是否大于rightindex
if leftindex >rightindex{
fmt.Println("没有找到")
return
}
//先找到中间的下标
middle :=(leftindex + rightindex) /2
if (*arr)[middle] > findVal{
//说明我们要查找的数,应该在 leftIndex ---middel-1之间
BinaryFind(arr,leftindex,middle -1,findVal)
}else if (*arr)[middle] < findVal{
//说明我们要查找得数在middel + -----rightindex
BinaryFind(arr,middle +1,rightindex,findVal)
}else{ //就是当Arr[middle]==findVal的时候
//找到了
fmt.Printf("找到了下标为%v\n",middle)
}
}
func main(){
arr := [6]int{1,8,10,89,1000,1234}
BinaryFind(&arr,0,len(arr)-1,1000)//找到了下标为4
}
二、map
1.map的基本介绍
map是key -value数据结构,又称为字段或者关联数组。类似其他编程语言的集合,在编程中经常使用到
2.map的声明
基本语法
var map 变量名 map[keptype]valuetype
key可以是什么类型
golang中的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的声明和注意事项,map是无序的数据结构
var a map[string]string
//使用、map前,需要先make,make的作用就是给map分配数据空间
a = make(map[string]string,10) //最大可以放10对
a["ao1"]="宋江"
a["ao2"]="吴用"
a["ao3"]="李逵"
a["ao4"]="林冲"
a["ao5"]="吴用"
a["ao5"]="无名" //会覆盖同key的值
fmt.Println(a)
}
对上面代码的说明:
1)map在使用前一定要make
2)map的key是不能重复,如果重复了,则以最后这个key-value为准
3)map的value是可以相同的
4)map的key-value是无序的
3.map的使用方式
1)方式1
func main(){
//map的声明和注意事项,map是无序的数据结构
var a map[string]string
//使用、map前,需要先make,make的作用就是给map分配数据空间
a = make(map[string]string,10) //最大可以放10对
a["ao1"]="宋江"
a["ao2"]="吴用"
a["ao3"]="李逵"
a["ao4"]="林冲"
a["ao5"]="吴用"
a["ao5"]="无名" //会覆盖同key的值
fmt.Println(a)
}
2)方式2
//第二种方式
cities := make(map[string]string)
cities["no1"] = "北京"
cities["no2"] = "天津"
cities["no3"] = "上海"
fmt.Println(cities)//map[no1:北京 no2:天津 no3:上海]
3)方式3
//第三种方式
heroes := map[string]string{
"heroe1" : "宋江",
"heroe2" : "林冲",
}
herroes["hertoe3"] = "张顺"
fmt.Println(heroes)//map[heroe2:林冲 heroe1:宋江 heroes:张顺]
练习:演示一个key-value的value是map的案例
比如:我们要存放3个学生的信息,每个学生有name,sex,adress信息
思路:map[string]map[string]string
代码:
package main
import(
"fmt"
)
func main(){
studentsMap := make(map[string]map[string]string)
studentsMap["no1"] = make(map[string]string,3)
studentsMap["no1"]["name"] = "tom"
studentsMap["no1"]["sex"] = "男"
studentsMap["no1"]["adrress"] = "北京"
//第二个学生
studentsMap["no2"] = make(map[string]string,3)
studentsMap["no2"]["name"] = "jhon"
studentsMap["no2"]["sex"] = "男"
studentsMap["no2"]["adrress"] = "上海"
fmt.Println(studentsMap["no1"])
fmt.Println(studentsMap["no2"])
//打印结果如下
/*
map[sex:男 adrress:北京 name:tom]
map[name:jhon sex:男 adrress:上海]
*/
}
4.map的增删改查操作
1)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)
}
2)map删除
说明:
delete(map,“key”),delete是一个内置函数,如果key存在,就删除该key-value.如果key不存在,不操作,但是也不会报错
func delete
func delete(m map[Type]Type1,key Type)
内建函数delete按照指定的键将元素从个映射中删除,若m为nil或无此元素,delete将不进行操作
案例演示:
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)//map[no2:天津 no3:深圳]没有no1
//当delete指定的key不存在时,删除不会操作,也不会报错
delete(cities,"no5")
fmt.Println(cities)map[no2:天津 no3:深圳]
//如果希望一次性删除所有的key
//1.遍历啊所有的key,如何逐一删除[遍历]
//2.直接make一个新空间
cities = make (map[string]string)//效率较高
fmt.Println(cities)
}
3)map的查找
案例演示
cities := make(map[string]string)
cities["no1"] = "北京"
cities["no2"] = "天津"
cities["no3"] = "上海"
//演示map的查找
val, ok := cities["no1"]
if ok{
fmt.Printf("有no1的key,值为:%v\n",val)
}else{
fmt.Println("没有no1的key,不存在这个值")
}
注意:如果cities这个map中存在“no1",那么findRes就会返回true,否则返回false
4)map的遍历:
案例演示 相对复杂的map遍历,该map的value又是一个map
说明:map的遍历使用for-range的结构遍历
//使用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)
}
//输出结果如下
//k=no1 v=北京k=no2 v=天津k=no3 v=上海
//使用for-range遍历比较复杂的map
studentsMap := make(map[string]map[string]string)
studentsMap["no1"] = make(map[string]string,3)
studentsMap["no1"]["name"] = "tom"
studentsMap["no1"]["sex"] = "男"
studentsMap["no1"]["adrress"] = "北京"
//第二个学生
studentsMap["no2"] = make(map[string]string,3)
studentsMap["no2"]["name"] = "jhon"
studentsMap["no2"]["sex"] = "男"
studentsMap["no2"]["adrress"] = "上海"
for k1,v1 :=range studentsMap{
fmt.Println("k1=",k1)
for k2,v2 := range v1 {
fmt.Printf("\t k2=%v v2 = %v\n",k2,v2)
}
}
/*
输出结果如下
k1= no1
k2=name v2 = tom
k2=sex v2 = 男
k2=adrress v2 = 北京
k1= no2
k2=sex v2 = 男
k2=adrress v2 = 上海
k2=name v2 = jhon
map的长度
func len
func len(v Type)int
内置函数的len返回v的长度,这取决于具体类型:
数组:v中元素的数量
数组指针:*v中元素的数量(v为nil时panic)
切片\映射:v中元素的数量:若v为nil,len(v)为0
字符串:v中字节的数量
通道:通道缓存中队列(未读取)元素的数量nil,len(v)即为0
案例演示
fmt.Printf("cities有%v 对key-value \n",len(cities)) //3对
5.map切片
基本介绍
切片的数据类型如果是map,则我们称为silice of map ,ma切片,这样使用规则map个数就可以动态变化了
案例演示
要求:使用一个map来记录monster的信息name和age,也就是说一个monster对应一个map,并且妖怪的个数可以动态的增加=>map切片
func main(){
//演示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"
}
//下面这个写法越界.
// 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" : "200",
}
monsters = append(monsters,newMonster)
fmt.Println(monsters)
}
6.map排序
基本介绍
1)golang中没有一个专门的方法针对map的key进行排序
2)golang中的map默认是无序的,注意也不是按照添加的顺序存放的,你每次遍历,得到的输出可能也不一样
3)golang中map的排序,是先将key进行排序,然后根据key值遍历输出即可
案例演示
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[10:100 1:13 4:56 8:90]
//如何按照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)
//遍历切片,然后按照key来输出map的值
for _, k := range keys{
fmt.Printf("map[%v]=%v\n",k,map1[k])
}
}
7.map的使用细节
1)map是引用类型,遵守引用类型传递的机制,在一个函数接收map,修改后,会直接修改原来的map[案例演示]
func main() {
//map是引用类型,
// 遵守引用类型传递的机制,在一个函数接收map,修改后,会直接修改原来的map
map1 := make(map[int]int)
map1[1] = 90
map1[2] = 88
map1[10] = 1
map1[20] = 2
modify(map1)
//观察结果如果map1[10]= 900说明map是引用类型
fmt.Println(map1) //map[10:900 20:2 1:90 2:88]
}
2)map的容量达到后,再想map增加元素,会自动扩容,并不会发生panic,也就是说map能动态的增长键值对(key -value)
3)map的value也经常使用struct类型,更适合管理复杂的数据(比前面value是一个map更好),比如value为student结构体
//定义一个学生结构体
type Stu struct {
Name string
Age int
Address string
}
func main() {
//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{"jhon",19,"上海"}
students["no1"] = stu1
students["no2"] = stu2
fmt.Println(students) //map[no1:{tom 18 北京} no2:{jhon 19 上海}]
//遍历各个学生的信息
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)
fmt.Println(" ")
}
}
//输出结果如下
map[no1:{tom 18 北京} no2:{jhon 19 上海}]
学生的编号是no2
学生的名字是jhon
学生的年龄是19
学生的住址是上海
学生的编号是no1
学生的名字是tom
学生的年龄是18
学生的住址是北京
D:\myfile\GO\project\src\go_code\map\mapdetails>
8.综合练习题:
1)使用map[string]map[string]string的map类型
2)key:表示用户名,是唯一的,不可以重复
3)如果某个用户名存在,就将其密码修改"8888",如果不存在就增加这个用户信息,(包括nickname和密码pwd)
4)编写一个函数modifyUser(users map[string]map[string] string,name string)完成上述功能
package main
import(
"fmt"
)
func modifyUsers(users map[string]map[string]string,name string){
//判断users中是否有name
// v , ok := users[name]
if users[name] != nil {
//有这个用户
users[name]["pws"] = "8888"
}else {
//没有这个用户
users[name] = make(map[string]string,2)
users[name]["pws"] = "8888"
users[name]["nicname"] = "昵称" + name //示意
}
}
func main(){
users :=make(map[string]map[string]string)
users["smith"] =make(map[string]string,2)
users["smith"]["pwd"] = "999999"
users["smith"]["nickname"] = "小花猫"
modifyUsers(users,"tom")
modifyUsers(users,"mary")
modifyUsers(users,"smith")
fmt.Println(users)
//输出结果为:map[mary:map[pws:8888 nicname:
//昵称mary] smith:map[nickname:小花猫 pws:8888 pwd:999999] tom:map[pws:8888 nicname:昵称tom]]
}
ess string
}
func main() {
//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{“jhon”,19,“上海”}
students[“no1”] = stu1
students[“no2”] = stu2
fmt.Println(students) //map[no1:{tom 18 北京} no2:{jhon 19 上海}]
//遍历各个学生的信息
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)
fmt.Println(" ")
}
}
//输出结果如下
map[no1:{tom 18 北京} no2:{jhon 19 上海}]
学生的编号是no2
学生的名字是jhon
学生的年龄是19
学生的住址是上海
学生的编号是no1
学生的名字是tom
学生的年龄是18
学生的住址是北京
D:\myfile\GO\project\src\go_code\map\mapdetails>
8.综合练习题:
1)使用map[string]map[string]string的map类型
2)key:表示用户名,是唯一的,不可以重复
3)如果某个用户名存在,就将其密码修改"8888",如果不存在就增加这个用户信息,(包括nickname和密码pwd)
4)编写一个函数modifyUser(users map[string]map[string] string,name string)完成上述功能
```go
package main
import(
"fmt"
)
func modifyUsers(users map[string]map[string]string,name string){
//判断users中是否有name
// v , ok := users[name]
if users[name] != nil {
//有这个用户
users[name]["pws"] = "8888"
}else {
//没有这个用户
users[name] = make(map[string]string,2)
users[name]["pws"] = "8888"
users[name]["nicname"] = "昵称" + name //示意
}
}
func main(){
users :=make(map[string]map[string]string)
users["smith"] =make(map[string]string,2)
users["smith"]["pwd"] = "999999"
users["smith"]["nickname"] = "小花猫"
modifyUsers(users,"tom")
modifyUsers(users,"mary")
modifyUsers(users,"smith")
fmt.Println(users)
//输出结果为:map[mary:map[pws:8888 nicname:
//昵称mary] smith:map[nickname:小花猫 pws:8888 pwd:999999] tom:map[pws:8888 nicname:昵称tom]]
}