GO语言学习之数组与切片
1.为什么需要数组
一个养鸡场有六只鸡,他们的体重分别是3kg,5kg,1kg,3.4kg,2kg,50kg。请问这六只鸡的体重是多少?平均体重是多少?请你编一个程序
package mainimport "fmt"func main() {ji1:=3.0ji2:=4.0ji3:=3.5ji4:=7.8ji5:=6.0totalWeight:=ji1+ji2+ji3+ji4+ji5avgWeight:=fmt.Sprintf("%.2f",totalWeight/5)fmt.Printf("totalWeight%v ,totalWeight %v",totalWeight,avgWeight)
}
对上面代码的说明:
1》传统的方法不利于数据的管理和维护
2》传统的方法不够灵活,因此我们引出需要学习的新的数据类型==》数组
2.数组介绍
数组可以存放多个同一类型数据。数组也是一种数据类型,在Go中国,数组是值类型
3.数组的快速入门
我们使用数组的方法来解决养鸡场问题
package mainimport "fmt"func main() {var hens [6]float64hens[0]=6.9hens[1]=6.8hens[2]=6.6hens[3]=6.7hens[4]=6.5hens[5]=6.9totalWeight:=0.0for i:=0;i<len(hens);i++{totalWeight+=hens[i]}avgWeight:=fmt.Sprintf("%.2f",totalWeight/5)fmt.Printf("totalWeight%v ,totalWeight %v",totalWeight,avgWeight)}
1》使用数组来解决问题,程序的可维护性增加
2》而且方法代码更加清晰,也容易扩展
4.数组定义和内存布局
定义:
var 数组名 [数组大小]数据类型
var a[5]int
赋初值 a[0]=1 a[1]=30
数组在内存的布局(重要)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fgEmkz4r-1575550043101)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1575459654229.png)]
1》数组名的地址可以通过数组名来获取 &intArr
2》数组的第一个元素的地址,就是数组的首地址
3》数组的各个元素的地址间隔是依据数组的类型决定,比如:int64–》8
int32—>4
$$
$$
package mainimport "fmt"func main() {var intArr [3]int//int 占8个字节fmt.Println(intArr)intArr[0]=10intArr[1]=11intArr[2]=13fmt.Println(intArr)fmt.Printf("intArr的地址=%p intArr[0]的地址为 %p intArr[1] 地址%p intArr[2] 地址为%p",&intArr,&intArr[0],&intArr[1],&intArr[2])
}
5.数组的使用
访问数组的元素:
数组名[下标] 比如:你要使用a数组的第三个元素 a[2]
快速入门案例:
从终端循环输入5个成绩,保存到float数组,并输出
package mainimport "fmt"func main() {//从终端循环输入5个成绩,保存到float64数组,并输出var score [5]float64for 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\n",i,score[i])}
}
四种初始化数组的方式:
package mainimport "fmt"func main() {var numARR01 [3]int=[3]int{1,2,3}fmt.Println("numArr01=",numARR01)var numArr2= [3]int{5,6,87}fmt.Println("numArr02=",numArr2)//规定写法var numArr3= [...]int{5,6,87}fmt.Println("numArr03=",numArr3)//var numArr4= [...]int{1:5,2:6,3:87}fmt.Println("numArr04=",numArr4)//类型推导strArr:=[...]string{1:"LIUYIFIE",2:"ZHAOLIYING",3:"MERRY"}fmt.Println("strArr:",strArr)
}
6.数组的遍历
1.常规遍历(上面例子)
2.for-range结构遍历
基本语法:
for index,value:=range array01{
}
1>第一个返回值index是数组的下标
2>第二个value是在该下标位置的值
3>他们都是仅在for循环内部可见的局部变量
4>遍历数组元素的时候,如果不想使用下标index,可以直接把下标index标为下划线
5>index和value 的名称是不固定的,即程序员可以自行指定,一般命名为index和value
package mainimport "fmt"func main() {//演示for--range遍历数组heores:=[...]string{"刘备","曹操","张飞"}//使用forrangefor i,v :=range heores{fmt.Printf("i=%v v=%v \n",i,v)fmt.Printf("heores[%d]=%v\n",i,heores[i])}for _,v :=range heores{fmt.Printf("元素的值:%v\n",v)}
}
7.数组的使用事项和细节
1》数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的,不能动态变化
var arr01 [3]int
arr01[0]=2.5//报错,数据类型
arr02[4]=30//数组越界
2》var arr[]int ,这时arr就是一个slice切片,切片后面会专门讲解
3》数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用
4》数组创建后,如果没有赋值,有默认值(零值)
数值类型数组:默认值为0
字符串数组:默认值““
bool 数组:默认值为false
var arr01 [3]float32
var arr02 [3]string
var arr03 [3]bool
fmt.Printf("arr01=%v arr02=%v arr03=%v \n",arr01,arr02,arr03)
5》使用数组的步骤
a.声明数组并开辟空间
b.给数组各个元素赋值(默认零值)
c.使用数组
6》数组的下标是从0开始的
var arr04 [3]string//0-2
var index int=3
arr04[index]="naicha"//数组下标越界
7》数组下标必须在指定范围内使用,否则报panic;数组越界
var arr [5]int ,则有效下标为0-4
8》GO的数组属于值类型,在默认情况下是值传递,因此会进行值拷贝,数组间不会相互影响
9》如果在其他函数中,去修改原来的数组,可以使用引用传递(指针方式)
func test(arr *[3]int){(*arr)[0]=88
}
10》长度是数组类型的一部分,在传递函数参数时,需要考虑数组的长度,看下面案例
func test(arr [3]int){arr[0]=100
}
func main(){var arr=[...]int{1,2,3,5}test(arr)//会报错,因为 test函数中的参数长度为3
}
8.数组的应用案例
1》创建一个byte类型的26个元素的数组,分别放置 ‘A’–‘Z’
使用for循环访问所有元素并打印出来,指示:字符数据运算
’A’+1–>‘B’
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])
}
2》请求出一个数组的最大值,并得到对应的下标
package mainimport "fmt"func main() {var intArr [6]int=[...]int{1,6,3,4,5,9}maxSum:=intArr[0]maxSumIndex:=0for i:=1;i<len(intArr) ; i++ {//从第二个元素开始循环比较,发现有更大的交换if maxSum<intArr[i]{maxSum=intArr[i]maxSumIndex=i}}fmt.Printf("maxSum=%v,maxSumIndex=%v",maxSum,maxSumIndex)}
3》请求出一个数组的和和平均值,for-range
package mainimport "fmt"func main() {var intArr [6]int=[...]int{1,6,3,4,5,9}sum:=0for _,val :=range intArr{//累计求和sum+=val}//如何让平均值保留到小数fmt.Printf("sum=%v 平均值=%v",sum,float64(sum)/float64(len(intArr)))
}
4》数组反转
package mainimport ("fmt""math/rand""time"
)func main() {//1.随机生成五个数,randIntn()_函数//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---100之间}fmt.Println("交换前=",intArr)//反转打印,交换的次数是 len/2//倒数第一个和第一个元素交换,temp:=0for i:=0;i<len/2;i++{temp=intArr[len-1-i]intArr[len-1-i]=intArr[i]intArr[i]=temp}fmt.Println("交换后:",intArr)
}
9.切片
1》为什么需要切片?
我们需要定义一个数组来存储班级同学成绩,但是学生个数不确定。
请问怎么办?
解决方案:使用切片
2》什么是切片
<切片的英文是slice
<切片是数组的一个引用,因此切片是引用类型。在进行传递时,遵守引用传递的机制
<切片的使用和数组类似,遍历切片,访问切片的元素和求切片长度len(slice)都一样
<切片的长度是可以变化的,因此切片是一个可以动态变化数组
<切片定义的基本语法
var 切片名 []类型
比如:var a []int
10.切片快速入门
演示一个切片的基本使用
package mainimport "fmt"func main() {//var intArr [5]int=[...]int{1,2,3,88,66}//声明定义一个切片slice :=intArr[1:4]//intArr中起始下标为1,结束下标为4fmt.Println("intArr=",intArr)fmt.Println("slice的元素是:",slice)fmt.Println("slice的元素个数:",len(slice))fmt.Println("slice的容量:",cap(slice))//切片的容量是可以动态变化的}
11.切片在内存中的形式
为了让大家更加深入理解切片,我们用图模拟10中代码
切片在内存中怎么布局的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-He5u5Tpb-1575550043109)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1575517777578.png)]
对上图分析总结:
1》slice是一个引用类型
2》slice从底层来说,其实就是一个数据结构(struct结构体)
type slice struct{
ptr *[2]int
len int
cap int
}
12.切片的使用
1>第一种方式:
定义一个切片,然后让切片去引用一个已经建好的数组,比如前面的案例就是这样的
package mainimport "fmt"func main() {//var intArr [5]int=[...]int{1,2,3,88,66}//声明定义一个切片slice :=intArr[1:4]//intArr中起始下标为1,结束下标为4fmt.Println("intArr=",intArr)fmt.Println("slice的元素是:",slice)fmt.Println("slice的元素个数:",len(slice))fmt.Println("slice的容量:",cap(slice))//切片的容量是可以动态变化的}
2>第二种方式:通过make来创建切片
基本语法:
var 切片名 []type=make([]type,len,[cap])
type:数据类型
len:大小
cap:指定切片容量,可选,如果分配了cap,必须 cap>=len
package mainimport "fmt"func main() {//使用make演示切片var slice []float64 =make([]float64,5,10)slice[1]=10slice[2]=20//对于切片,必须make用fmt.Println(slice)fmt.Println("slice的size=",len(slice))fmt.Println("slice的cap=",cap(slice))}
3.第三种方式>
定义一个切片,直接指定具体数组,使用原理类似make方式
package mainimport "fmt"func main() {var strSlice []string = []string{"liuyifei","zhangziyi","fanbingbing"}fmt.Println("strSlice=",strSlice)fmt.Println("len=",len(strSlice))fmt.Println("cap=",cap(strSlice))
}
13.切片的遍历
切片的遍历和数组一样,也有两种方式
package mainimport ("fmt"
)func main() {var intArr [5]int = [...]int{1,2,3,4,5}slice:=intArr[1:4]//forfor i:=0;i<len(slice);i++{fmt.Printf("slice[%v]=%v \n",i,slice[i])}//用for-range遍历for i,v :=range slice{fmt.Printf("i=%v v=%v \n",i,v)}
}
14.切片使用注意事项和细节讨论
1>切片初始化是,var slice=arr[startIndex:endIndex]
说明:从arr数组下标为startIndex,取到下标为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》切片可以继续切片
package mainimport "fmt"func main() {var arr [5]int=[...]int{100,200,250,260,280}slice:=arr[1:4]for i:=0;i<len(slice);i++{fmt.Printf("slice[%v]=%v\n",i,slice[i])}slice2:=slice[2:4]fmt.Println(slice2[0])fmt.Println(slice)fmt.Println(arr)
}
6》用append内置函数,可以对切片进行动态追加
package mainimport "fmt"func main() {var arr [5]int=[...]int{100,200,250,260,280}slice:=arr[1:4]for i:=0;i<len(slice);i++{fmt.Printf("slice[%v]=%v\n",i,slice[i])}slice2:=slice[2:4]fmt.Println(slice2[0])fmt.Println(slice)fmt.Println(arr)var slice3 []int=[]int{77,99,88}fmt.Println(slice3)//谁在前追加给谁slice3=append(slice3,slice2...)fmt.Println(slice3)
}
切片函数append操作的底层分析:
切片append操作的本质是对数组进行扩容,
go底层会创建一个新的数组,newArr(安装扩容后大小)
将slice原来包含的元素拷贝到新的数组newArr
slice重新引用到newArr
注意newArr是在底层维护的,我们看不见
7》切片的拷贝操作
切片使用copy内置函数完成拷贝
var slice4 []int=[]int{33,22,11,10,1}var slice5=make([]int,10)//把4 烤进 5中copy(slice5,slice4)fmt.Printf("slice4=%v\n",slice4)fmt.Printf("slice5=%v\n",slice5)
对上面代码说明:
(1)copy(para1,para2)参数的数据类型是切片
(2)按照上面代码来看,slice4和slice5 的数据空间是独立,相互不影响,也就是说slice4[0]=1000,slice5[0]=33
var slice4 []int=[]int{33,22,11,10,1}var slice5=make([]int,10)//把4 烤进 5中copy(slice5,slice4)slice4[0]=1000fmt.Printf("slice4=%v\n",slice4)fmt.Printf("slice5=%v\n",slice5)fmt.Println(slice4[0])
8》copy(slice5,slice4) 左边长度可以小于右边
9》切片是引用类型,所以在传递时,遵守引用传递机制,看两段代码,并分析底层原理
package mainimport "fmt"func main() {var slice []intvar arr [5]int=[...]int{1,2,3,4,5}slice=arr[:]var slice2=sliceslice2[0]=10fmt.Println("slice=",slice)fmt.Println("slice2=",slice2)fmt.Println("arr=",arr)
/*
结果:slice= [10 2 3 4 5]slice2= [10 2 3 4 5]arr= [10 2 3 4 5]*/
package mainimport "fmt"func test(slice[]int) {slice[0]=999//在这里修改,会影响实参
}
func main() {var slice=[]int{1,2,3,4}fmt.Println(slice)test(slice)fmt.Println(slice)/*结果为:[1 2 3 4][999 2 3 4]//*/
}
15.string 和slice
1》slice底层是一个byte数组,因此strig也可以进行切片处理
package mainimport "fmt"func main() {//string 底层是一个byte数组,因此string也可以进行切片处理str:="iloveliuyfiei"slice:=str[:6]fmt.Println("slice=",slice)}
2》string 是不可变的,也就说不能通过 str[i]=‘a’,方式来修改字符串
slice[0]='A'// cannot assign to slice[0]//编译不通过,原因string不可变
3》如果需要修改字符串,可以先将 string–>[]byte
或者[]rune–>修改–》重写转成string
//string 底层是一个byte数组,因此string也可以进行切片处理str:="iloveliuyfiei"strArr:=[]byte(str)strArr[0]='I'str=string(strArr)fmt.Println(str)
//[]runestrArr2:=[]rune(str)strArr2[0]='爱'str=string(strArr2)fmt.Println(str)
16.切片的课堂练习题:
说明:编写一个函数 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
package mainimport "fmt"func fbn(n int)([]uint64){//声明一个切片,切片大小为nfbnSlice:=make([]uint64,n)//前两个的斐波那契为 1fbnSlice[0]=1fbnSlice[1]=1for i:=2;i<n;i++{fbnSlice[i]=fbnSlice[i-1]+fbnSlice[i-2]}return fbnSlice
}
func main() {fbnSlice:=fbn(8)fmt.Println(fbnSlice)}
3》如果需要修改字符串,可以先将 string–>[]byte
或者[]rune–>修改–》重写转成string
//string 底层是一个byte数组,因此string也可以进行切片处理str:="iloveliuyfiei"strArr:=[]byte(str)strArr[0]='I'str=string(strArr)fmt.Println(str)
//[]runestrArr2:=[]rune(str)strArr2[0]='爱'str=string(strArr2)fmt.Println(str)
16.切片的课堂练习题:
说明:编写一个函数 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
package mainimport "fmt"func fbn(n int)([]uint64){//声明一个切片,切片大小为nfbnSlice:=make([]uint64,n)//前两个的斐波那契为 1fbnSlice[0]=1fbnSlice[1]=1for i:=2;i<n;i++{fbnSlice[i]=fbnSlice[i-1]+fbnSlice[i-2]}return fbnSlice
}
func main() {fbnSlice:=fbn(8)fmt.Println(fbnSlice)}