七、陣列與切片

zhangsen發表於2021-04-20

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)
  1. 陣列定義和記憶體佈局

  2. 陣列的定義
var 陣列名 [陣列大小]資料型別
var class [50]int
var class [3]int = [...]int{1,2,3}
class[0]=1
class[1]=2
  1. 陣列的記憶體佈局

當我們定義完陣列後,其實陣列的各個元素有預設值

  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
  1. 陣列的使用

  2. 訪問陣列元素

陣列名[下標]

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])
}
  1. 四種初始化陣列的方法
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]
  1. 陣列的遍歷

一般用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)
}
  1. 使用注意-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}
  1. 陣列的應用案例

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

  6. 切片的使用

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
  1. 切片的記憶體形式

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

  1. 切片的使用

    1. 引用陣列

//定義切片,引用陣列
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 
  1. 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的陣列不可見
    1. 定義一個切片時候指定陣列

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
  1. 切片的遍歷

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. 切片的使用注意

  2. 切片初始化 Var slice=[a:b]

  3. cap是一個內建函式,用於統計切片的容量,即最大可以存放多少個元素

  4. 切片定義往後,還不能使用,因為切片本身是一個空的,需要讓其應用到一個陣列,或者make一個空間供切片來使用

  5. 切片可以繼續切片

  6. appen動態追加,【slice3…是自己】

vare slice = arr[0:end]           var slice=arr[:end]
vare slice = arr[start:len(arr)]  var slice=arr[start:]
vare slice = arr[0:len(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]
  1. string與slice

  2. string底層是一個byte陣列,因此string也可以進行切片處理

  3. String和切片在記憶體的形式,以 “abcd”畫記憶體示意圖

  4. string是不可變的,不能通過Str[0]=”Z”方式來修改字串

  5. 如果要修改字串,可以先將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) //北京歡迎你
  1. 練習

說明:編寫一個函式 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] = 1
   for i := 2; i < n; i++ {
      fbnSlice[i] = fbnSlice[i-1] + fbnSlice[i-2]
   }
   return fbnSlice
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章