GO语言学习之数组与切片

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)

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值