GO语言学习之数组与切片
1.为什么需要数组
一个养鸡场有六只鸡,他们的体重分别是3kg,5kg,1kg,3.4kg,2kg,50kg。请问这六只鸡的体重是多少?平均体重是多少?请你编一个程序
package main
import "fmt"
func main() {
ji1:=3.0
ji2:=4.0
ji3:=3.5
ji4:=7.8
ji5:=6.0
totalWeight:=ji1+ji2+ji3+ji4+ji5
avgWeight:=fmt.Sprintf("%.2f",totalWeight/5)
fmt.Printf("totalWeight%v ,totalWeight %v",totalWeight,avgWeight)
}
对上面代码的说明:
1》传统的方法不利于数据的管理和维护
2》传统的方法不够灵活,因此我们引出需要学习的新的数据类型==》数组
2.数组介绍
数组可以存放多个同一类型数据。数组也是一种数据类型,在Go中国,数组是值类型
3.数组的快速入门
我们使用数组的方法来解决养鸡场问题
package main
import "fmt"
func main() {
var hens [6]float64
hens[0]=6.9
hens[1]=6.8
hens[2]=6.6
hens[3]=6.7
hens[4]=6.5
hens[5]=6.9
totalWeight:=0.0
for 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 main
import "fmt"
func main() {
var intArr [3]int//int 占8个字节
fmt.Println(intArr)
intArr[0]=10
intArr[1]=11
intArr[2]=13
fmt.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 main
import "fmt"
func main() {
//从终端循环输入5个成绩,保存到float64数组,并输出
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\n",i,score[i])
}
}
四种初始化数组的方式:
package main
import "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 main
import "fmt"
func main() {
//演示for--range遍历数组
heores:=[...]string{"刘备","曹操","张飞"}
//使用forrange
for 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 main
import "fmt"
func main() {
var intArr [6]int=[...]int{1,6,3,4,5,9}
maxSum:=intArr[0]
maxSumIndex:=0
for 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 main
import "fmt"
func main() {
var intArr [6]int=[...]int{1,6,3,4,5,9}
sum:=0
for _,val :=range intArr{
//累计求和
sum+=val
}
//如何让平均值保留到小数
fmt.Printf("sum=%v 平均值=%v",sum,float64(sum)/float64(len(intArr)))
}
4》数组反转
package main
import (
"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:=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)
}
9.切片
1》为什么需要切片?
我们需要定义一个数组来存储班级同学成绩,但是学生个数不确定。
请问怎么办?
解决方案:使用切片
2》什么是切片
<切片的英文是slice
<切片是数组的一个引用,因此切片是引用类型。在进行传递时,遵守引用传递的机制
<切片的使用和数组类似,遍历切片,访问切片的元素和求切片长度len(slice)都一样
<切片的长度是可以变化的,因此切片是一个可以动态变化数组
<切片定义的基本语法
var 切片名 []类型
比如:var a []int
10.切片快速入门
演示一个切片的基本使用
package main
import "fmt"
func main() {
//
var intArr [5]int=[...]int{1,2,3,88,66}
//声明定义一个切片
slice :=intArr[1:4]//intArr中起始下标为1,结束下标为4
fmt.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 main
import "fmt"
func main() {
//
var intArr [5]int=[...]int{1,2,3,88,66}
//声明定义一个切片
slice :=intArr[1:4]//intArr中起始下标为1,结束下标为4
fmt.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 main
import "fmt"
func main() {
//使用make演示切片
var slice []float64 =make([]float64,5,10)
slice[1]=10
slice[2]=20
//对于切片,必须make用
fmt.Println(slice)
fmt.Println("slice的size=",len(slice))
fmt.Println("slice的cap=",cap(slice))
}
3.第三种方式>
定义一个切片,直接指定具体数组,使用原理类似make方式
package main
import "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 main
import (
"fmt"
)
func main() {
var intArr [5]int = [...]int{1,2,3,4,5}
slice:=intArr[1:4]
//for
for 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 main
import "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 main
import "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]=1000
fmt.Printf("slice4=%v\n",slice4)
fmt.Printf("slice5=%v\n",slice5)
fmt.Println(slice4[0])
8》copy(slice5,slice4) 左边长度可以小于右边
9》切片是引用类型,所以在传递时,遵守引用传递机制,看两段代码,并分析底层原理
package main
import "fmt"
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("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 main
import "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 main
import "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)
//[]rune
strArr2:=[]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 main
import "fmt"
func fbn(n int)([]uint64){
//声明一个切片,切片大小为n
fbnSlice:=make([]uint64,n)
//前两个的斐波那契为 1
fbnSlice[0]=1
fbnSlice[1]=1
for 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)
//[]rune
strArr2:=[]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 main
import "fmt"
func fbn(n int)([]uint64){
//声明一个切片,切片大小为n
fbnSlice:=make([]uint64,n)
//前两个的斐波那契为 1
fbnSlice[0]=1
fbnSlice[1]=1
for i:=2;i<n;i++{
fbnSlice[i]=fbnSlice[i-1]+fbnSlice[i-2]
}
return fbnSlice
}
func main() {
fbnSlice:=fbn(8)
fmt.Println(fbnSlice)
}