七、数组与切片 - Golang

Golang
356
0
0
2022-04-09
标签   Golang基础

Go中,数组是值类型

var hens [7]float64

hens[0] = 3.0
hens[1] = 3.0
hens[2] = 3.0
hens[3] = 3.0
hens[4] = 3.0

totalWeight := 0.0
for i := 0; i < len(hens); i++ {
   totalWeight += hens[i]

}

avgWeight := fmt.Sprintf("%.2f", totalWeight/float64(len(hens)))
fmt.Printf("total =%v,avg=%v", totalWeight, avgWeight)

数组定义和内存布局

数组的定义
var 数组名 [数组大小]数据类型
var class [50]int
var class [3]int = [...]int{1,2,3}
class[0]=1
class[1]=2
数组的内存布局

当我们定义完数组后,其实数组的各个元素有默认值

  1. 数组的地址可以通过数组名来获取 &intArr
  2. 数组的地址就是首位元素的地址
  3. 数组的各个元素的地址间隔是依据数组的类型决定 比如int64->8 int32->4
var intArr [3]int
fmt.Println(intArr)
//[0 0 0]
fmt.Println(&intArr[0])
//0xc000010360

var intArr [3]int
intArr[0] = 10
intArr[1] = 20
intArr[2] = 20
fmt.Printf("intArr的地址=%p intArr[0] 地址%p  intArr[1] 地址%p intArr[2] 地址%p", &intArr, &intArr[0], &intArr[1], &intArr[2])

//intArr的地址=0xc000010360 intArr[0] 地址0xc000010360  intArr[1] 地址0xc000010368 intArr[2] 地址0xc000010370

数组的使用

访问数组元素

数组名[下标]

var arr [5]float64

for i := 0; i < len(arr); i++ {
   fmt.Printf("请输入第%d个元素的值\n", i+1)
   fmt.Scanln(&arr[i])
}

for i := 0; i < len(arr); i++ {
   fmt.Printf("arr[%d]=%v", i, arr[i])
}
四种初始化数组的方法
var numarr01 [3]int = [3]int{1, 2, 3}
fmt.Println("numarr01=", numarr01)
var numarr02 = [3]int{1, 2, 3}
fmt.Println("numarr02=", numarr02)
var numarr03 = [...]int{8, 9, 10}
fmt.Println("numarr03=", numarr03)
var numarr04 = [...]int{1: 800, 2: 129, 3: 10}
fmt.Println("numarr03=", numarr04)
//类型推导
strArr := [...]string{1: "tom", 2: "jack", 3: "zjm"}
fmt.Println("strArr=", strArr)

//numarr01= [1 2 3] 
//numarr02= [1 2 3] 
//numarr03= [8 9 10] 
//numarr03= [0 800 129 10] 
//strArr= [ tom jack zjm]

数组的遍历

一般用for或者for–range

heros := [...]string{"zjm", "whq", "xgd"}
for i, v := range heros {
   fmt.Printf("i=%v v=%v\n", i, v)
   fmt.Printf("heros[%d]=%v\n", i, heros[i])
}

for _, v := range heros {
   fmt.Printf("元素的值%v\n", v)
}

使用注意-217

  1. 数组是多个相同类型数据的组合,一个数组一旦什么了,其长度是固定的,不能动态改变
  2. Var arr []int arr就是一个切片
  3. 数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用
  4. 数组创建后,没有赋值,有默认值 (string :‘’ bool:false 数值型:0)
  5. 数组的下标必须在指定范围内使用,否则报panic:数组越界
  6. Go的数组属值类型,在默认情况下是值传递,因此会进行值拷贝,数组间不会相互影响
  7. 如果下在其他函数中,去修改原来的数组,可以使用引用传递(指针方式)
  8. 长度是数组类型的一部分,在传递函数参数时,需要考虑数组的长度
var arr [3]int
arr[0] = 1
arr[1] = 2
arr[2] = 3.1
arr[3] = 4

var arr1 [3]float32
var arr2 [3]string
var arr3 [3]bool
fmt.Printf("arr1=%v arr2=%v arr3=%v", arr1, arr2, arr3)
//arr1=[0 0 0] arr2=[  ] arr3=[false false false]

//Go的数组属值类型,在默认情况下是值传递,因此会进行值拷贝,数组间不会相互影响

注意是值拷贝,所以呢,这个是在栈里新建一个数组

arr := [3]int{11, 22, 33}
test(arr)
fmt.Println(arr)

func test(arr [3]int) {
   arr[0] = 88
   fmt.Println("test中的arr:", arr)
}

test中的arr: [88 22 33] 
[11 22 33]

要改的话,需要用的是指针,改变地址引用

arr := [3]int{11, 22, 33}
test01(arr)
test02(&arr) //传地址
fmt.Println("arr:", arr)

func test01(arr [3]int) { //go中数组的长度是类型的一部分 [3]int
   arr[0] = 88
   fmt.Println("test01中的arr:", arr)
}

func test02(arr *[3]int) {(*arr)[0] = 88 //指针获取到地址
   fmt.Println("test02中的arr:", arr)
}

test01中的arr: [88 22 33] 
test02中的arr: &[88 22 33] 
arr: [88 22 33]

长度问题

var age [...]{1,2,3} //长度3
var age [] //长度0 函数参数传递的时候不能把3传0
var age [4]int  //长度3不能传4,要保持一致
var age [3]{3,4,5}

数组的应用案例

var chars [26]byte
for i := 0; i < 26; i++ {
   chars[i] = 'A' + byte(i)
}
for i := 0; i < 26; i++ {
   fmt.Printf("%c", chars[i])
}
//ABCDEFGHIJKLMNOPQRSTUVWXYZ
var intArr [6]int = [...]int{1, -1, 9, 99, 999, 9999} //声明
maxVal := intArr[0]
maxIndex := 0

for i := 0; i < len(intArr); i++ {for maxVal < intArr[i] {
      maxVal = intArr[i]
      maxIndex = i
   }
}
fmt.Printf("maxVal=%v , maxIndex=%v", maxVal, maxIndex)
//maxVal=9999 , maxIndex=5
var intArr [5]int = [...]int{1, 9, 3, 4, 5}
sum := 0
for _, v := range intArr {
   sum += v
}
fmt.Printf("sum=%v,avg=%v", sum, float64(sum)/float64(len(intArr))) 
//都是int
var intArr3 [5]int
len := len(intArr3)
rand.Seed(time.Now().UnixNano())
for i := 0; i < len; i++ {
   intArr3[i] = rand.Intn(100)
}

fmt.Println("交换前=", intArr3)

temp := 0
for i := 0; i < len/2; i++ {
   temp = intArr3[len-1-i]
   intArr3[len-1-i] = intArr3[i]
   intArr3[i] = temp
}
fmt.Println("交换后=", intArr3)

交换前= [24 63 71 32 66] 
交换后= [66 32 71 63 24]

切片

为什么要切片?在元素个数不确定的情况下,使用数组不知道开多大,大了浪费小了不够用
  1. 切片英文slice
  2. 切片是数组的一个引用,在进行传递时,遵守引用传递机制 【】
  3. 切片的使用和数组基本相似(遍历、访问、求长度等)
  4. 切片的长度是可以变化的,动态变化
  5. 定义: var 切片名 []类型 比如: var age []int

切片的使用

var intArr [5]int = [...]int{1, 22, 33, 66, 99}
//slice := intArr[1:4] //表示slice 应用到intArr这个数组
//intArr[1:3],起始下标包1不包3
slice := intArr[1:4]
fmt.Println("intArr=", intArr)
fmt.Println("slice元素是", slice)
fmt.Println("slice元素长度", len(slice))
fmt.Println("slice元素容量", cap(slice))

intArr= [1 22 33 66 99] 
slice元素是 [22 33 66] 
slice元素长度 3 
slice元素容量 4

fmt.Printf("intArr[1]的地址=%p\n", &intArr[1])
fmt.Printf("intArr[0]的地址=%p slice[0]==%v\n", &slice[0], slice[0])

intArr[1]的地址=0xc0000ca038 
intArr[0]的地址=0xc0000ca038 slice[0]==22

切片的内存形式

引用,包括地址、len长度、cap容量

切片的使用

引用数组

//定义切片,引用数组
var arr [5]int = [...]int{1, 2, 3, 4, 5}
var slice = arr[1:3]
fmt.Println("arr=", arr)
fmt.Println("slice=", slice)
fmt.Println("slice len=", len(slice))
fmt.Println("slice cap=", cap(slice))

arr= [1 2 3 4 5] 
slice= [2 3] 
slice len= 2 
slice cap= 4 

make创建

Var 切片名 []type = make([]type,len,[cap]) ;cap必须大于len
var slice []float64 = make([]float64, 5, 10)
slice[1] = 10
slice[3] = 20
fmt.Println("slice=", slice)
fmt.Println("slice len=", len(slice))
fmt.Println("slice cap=", cap(slice))

slice= [0 10 0 20 0] 
slice len= 5 
slice cap= 10

总结:

  1. make必须指定cap和len
  2. 没有给切片元素复制,默认(int,float => 0 string=> “” bool=>false)
  3. make方式创建的切片对应的数组由make底层维护不可见,只能通过slice去访问各个元素
  4. 方式1和方式2的区别:1的数组可见2的数组不可见

定义一个切片时候指定数组

var strSlice []string = []string{"tom", "jack", "rose"}
fmt.Println("strSlice=", strSlice)
fmt.Println("strSlice len=", len(strSlice))
fmt.Println("strSlice cap=", cap(strSlice))

strSlice= [tom jack rose] 
strSlice len= 3 
strSlice cap= 3

切片的遍历

For 和for–range
var strSlice []string = []string{"tom", "jack", "rose", "who"}
aaSlice := strSlice[0:4]
for i := 0; i < len(aaSlice); i++ {
   fmt.Printf("aaSlice[%v]=%v\n", i, aaSlice[i])
}

for i, v := range aaSlice {
   fmt.Printf("aaSlice[%v]=%v\n", i, v)
}

aaSlice[0]=tom 
aaSlice[1]=jack 
aaSlice[2]=rose 
aaSlice[3]=who

切片的使用注意

  1. 切片初始化 Var slice=[a:b]
  2. cap是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素
  3. 切片定义往后,还不能使用,因为切片本身是一个空的,需要让其应用到一个数组,或者make一个空间供切片来使用
  4. 切片可以继续切片
  5. appen动态追加,【slice3…是自己】
vare slice = arr[0:end]           var slice=arr[:end]
vare slice = arr[start:len(arr)]  var slice=arr[start:]
vare slice = arr[0len(arr)]      var slice=arr[:]

注意:

var strSlice []string = []string{"tom", "jack", "rose", "who"}
slice2 := strSlice[1:3]
slice3 := strSlice[1:2]
slice3[0] = "zhang"

fmt.Println("slice2=", slice2)
fmt.Println("slice3", slice3)
fmt.Println("strSlice=", strSlice)
---
slice2= [zhang rose] 
slice3 [zhang] 
strSlice= [tom zhang rose who]
//因为指向的是同一个空间,所以后面改这个值也会影响

var slice3 []int = []int{11, 22, 33}
slice3 = append(slice3, 400, 500, 600)
fmt.Println("slice3", slice3)

slice3 = append(slice3, slice3...)
fmt.Println("slice3", slice3)
---
slice3 [11 22 33 400 500 600] 
slice3 [11 22 33 400 500 600 11 22 33 400 500 600]

append原理:

  1. append其实就是扩容
  2. go底层会创建一个新的数组newArr(安装扩容后大小)
  3. 将slice原来包含的元素拷贝到新的数组newArr
  4. slice重新引用到newArr,底层不可见

再次强调切片是引用类型,传递时候遵守引用传递机制:【本质是操作一个空间】

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)
fmt.Println("slice", slice)
fmt.Println("arr", arr)
---
slice2 [10 2 3 4 5] 
slice [10 2 3 4 5] 
arr [10 2 3 4 5]

切片的拷贝操作

切片使用copy内置函数完成拷贝

  1. copy(A,B),把B的值赋值给A,都是切片
  2. A,B空间独立
  3. copy长度不够就不管呗
var slice4 []int = []int{1, 2, 3, 4, 5}
var slice5 = make([]int, 10)
copy(slice5, slice4)
fmt.Println("slice4", slice4)
fmt.Println("slice5", slice5)
---
slice4 [1 2 3 4 5] 
slice5 [1 2 3 4 5 0 0 0 0 0] 

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]

函数中改变会影响到函数外部的改变:实参

    var slice = []int{1, 2, 3, 4}
   fmt.Println("slice", slice)test(slice)
   fmt.Println("test(slice)", slice)
}
func test(slice []int) {
   slice[0] = 100
}
---
slice [1 2 3 4] 
test(slice) [100 2 3 4]

string与slice

  1. string底层是一个byte数组,因此string也可以进行切片处理
  2. String和切片在内存的形式,以 “abcd”画内存示意图
  3. string是不可变的,不能通过Str[0]=”Z”方式来修改字符串
  4. 如果要修改字符串,可以先将string转[]byte或者将 []rune-> 修改 ->重写转成string
str := "hello@atguige"
slice := str[6]
fmt.Println("slice", slice)
---
slice 97
---
str[0] = "z" //不可分配 string不可变

str := "hello@atguige" //atguige
slice := str[6:]
fmt.Println(slice) // atguige

str := "hello@atguige"
arr := []byte(str)
arr[0] = 'Z'
str = string(arr)
fmt.Println(str) //Zello@atguige

//英文和数字  []byte字节来处理,汉字是3个字节,因此会出现乱码
string转[]rune(切片,[]rune按字符处理)

str := "西京欢迎你"
arr := []rune(str)
arr[0] = '北'
str = string(arr)
fmt.Println(str) //北京欢迎你

练习

说明:编写一个函数 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,1,2,3,5,8

func fbn(n int)([]uint64){
   fbnSlice := make([]uint64,n)
   fbnSlice[0] = 1
   fbnSlice[1] = 1for i := 2; i < n; i++ {
      fbnSlice[i] = fbnSlice[i-1] + fbnSlice[i-2]}return fbnSlice
}