陣列和切片
【1】、陣列
1、什麼是陣列
一組數
- 陣列需要是相同型別的資料的集合
- 陣列是需要定義大小的
- 陣列一旦定義了大小是不可以改變的。
package main
import "fmt"
// 陣列
// 陣列和其他變數定義沒什麼區別,唯一的就是這個是一組數,需要給一個大小 [6]int [10]string
// 陣列是一個相同型別資料的==有序==集合,透過下標來取出對應的資料
// 陣列幾個特點:
// 1、長度必須是確定的,如果不確定,就不是陣列,大小不可以改變
// 2、元素必須是相,同型別不能多個型別混合, [any也是型別,可以存放任意型別的資料]
// 3、陣列的中的元素型別,可以是我們學的所有的型別,int、string、float、bool、array、slice、map
func main() {
// 派生資料型別
// 陣列的定義:【陣列大小size】變數的型別 我們定義了一組這個型別的數的集合,大小為size
// 預設值是00000
var arr1 [5]int
arr1[0] = 1
arr1[1] = 2
arr1[2] = 3
arr1[3] = 4
arr1[4] = 5
fmt.Println(arr1)
for i := 0; i < len(arr1); i++ {
fmt.Printf("%d\n", arr1[i])
}
// 陣列中的常用方法 len()獲取陣列的長度 cap() 獲取陣列的容量
fmt.Println("陣列的長度:", len(arr1)) // 陣列的長度: 5
fmt.Println("陣列的容量:", cap(arr1)) // 陣列的容量: 5
// 修改陣列的值
arr1[1] = 100
fmt.Println(arr1)
}
2、初始化陣列
package main
import "fmt"
func main() {
// 陣列的賦值初始化
var arr1 = [5]int{0, 1, 2, 3, 4}
fmt.Println(arr1)
// 快速賦值
arr2 := [5]int{0, 1, 2, 3, 4}
fmt.Println(arr2)
// 接受使用者輸入的資料,變為陣列
// ... 代表陣列長度
// Go的編譯器會自動根據陣列的長度來給 ... 賦值,自動推導長度
// 注意點:這裡的陣列不是無限長的,也是固定的大小,大小取決於陣列元素個數
arr3 := [...]int{0, 1, 2, 3, 4, 5, 5, 5, 6, 7, 8, 9}
fmt.Println(len(arr3), arr3)
// 陣列預設值,只給其中某幾個元素賦值
var arr4 [10]int
fmt.Println(arr4)
arr4[6] = 600
arr4[5] = 500
fmt.Println(arr4)
}
3、遍歷陣列元素
package main
import "fmt"
/*
1、直接透過下標獲取元素 arr[index]
2、 0-len i++ 可以使用for迴圈來結合陣列下標進行遍歷
3、for range:範圍 (new)
*/
func main() {
arr3 := [...]int{0, 1, 2, 3, 4, 5, 5}
for i := 0; i < len(arr3); i++ {
fmt.Println(arr3[i])
}
// goland 快捷方式 陣列.for,未來迴圈陣列、切片很多時候都使用for range
// for 下標,下標對應的值 range 目標陣列切片
// 就是將陣列進行自動迭代。返回兩個值 index、value
// 注意點,如果只接收一個值,這個時候返回的是陣列的下標
// 注意點,如果只接收兩個值,這個時候返回的是陣列的下標和下標對應的值
for _, value := range arr3 {
fmt.Println("value:", value)
}
}
4、陣列是值型別
package main
import "fmt"
// 陣列是值型別: 所有的賦值後的物件修改值後不影響原來的物件。
func main() {
arr3 := [...]int{0, 1, 2, 3, 4, 5, 5}
arr4 := [...]string{"111", "222"}
fmt.Printf("%T\n", arr3)
fmt.Printf("%T\n", arr4)
arr5 := arr3
arr3[6] = 7
fmt.Printf("%T\n", arr3)
fmt.Println(arr5) // 陣列是值傳遞,複製一個新的記憶體空間
}
5、陣列排序
arr := [6]int{1,2,3,4,5,0}
// 升序 ASC : 從小到大 0,1,2,3,4,5 A-Z 00:00-24:00
// 降序 DESC : 從大到小 5,4,3,2,1,0
// 氣泡排序
package main
import "fmt"
func main() {
arr1 := [...]int{1, 2, 3, 4, 5, 0, 77, 95, 11, 23, 54, 88, 33, 10, 23, 19}
//var temp int = 0
for i := 0; i < len(arr1); i++ {
for j := i + 1; j < len(arr1); j++ {
if arr1[i] > arr1[j] {
arr1[i], arr1[j] = arr1[j], arr1[i]
}
}
}
fmt.Println(arr1)
}
6、多維陣列
一維陣列: 線性的,一組數
二維陣列: 表格性的,陣列套陣列
三維陣列: 立體空間性的,陣列套陣列套陣列
xxxx維陣列:xxx,陣列套陣列套陣列.....
package main
import "fmt"
func main() {
// 定義一個多維陣列 二維
arr := [3][4]int{
{0, 1, 2, 3}, // arr[0] //陣列
{4, 5, 6, 7}, // arr[1]
{8, 9, 10, 11}, // arr[2]
}
// 二維陣列,一維陣列存放的是一個陣列
fmt.Println(arr[0])
// 要獲取這個二維陣列中的某個值,找到對應一維陣列的座標,arr[0] 當做一個整體
fmt.Println(arr[0][1])
fmt.Println("------------------")
// 如何遍歷二維陣列
for i := 0; i < len(arr); i++ {
for j := 0; j < len(arr[i]); j++ {
fmt.Println(arr[i][j])
}
}
// for range
for i, v := range arr {
fmt.Println(i, v)
}
}
【2】、切片
Go 語言切片是對陣列的抽象。
Go 陣列的長度不可改變,在特定場景中這樣的集合就不太適用,Go 中提供了一種靈活,功能強悍的內建型別 切片("動態陣列"),與陣列相比切片的長度是不固定的,可以追加元素,在追加時可能使切片的容量增大。
切片是一種方便、靈活且強大的包裝器,切片本身沒有任何資料,他們只是對現有陣列的引用。
切片與陣列相比,不需要設定長度,在[]中不用設定值,相對來說比較自由
從概念上面來說 slice 像一個結構體,這個結構體包含了三個元素:
- 指標:指向陣列中 slice 指定的開始位置
- 長度:即slice的長度
- 最大長度:也就是 slice 開始位置到陣列的最後位置的長度
1、切片的定義
package main
import "fmt"
func main() {
var s1 []int // 變長,長度是可變的
fmt.Println(s1)
// 切片空的判斷,初始化切片中,預設是nil
if s1 == nil {
fmt.Println("s1是空的")
}
fmt.Printf("%T\n", s1)
s2 := []int{1, 2, 3, 4}
fmt.Println(s2[2])
}
2、make來建立切片
package main
import "fmt"
func main() {
// make()
// make([]Type,length,capacity) // 建立一個切片,長度,容量
s1 := make([]int, 5, 10)
fmt.Println(s1)
fmt.Println(len(s1), cap(s1))
// 思考:容量為10,長度為5,我能存放6個資料嗎?
s1[0] = 10
s1[7] = 200 // index out of range [7] with length 5
// 切片的底層還是陣列 [0 0 0 0 0] [2000]
// 直接去賦值是不行的,不用用慣性思維思考
fmt.Println(s1)
// 切片擴容
}
3、切片擴容
package main
import "fmt"
func main() {
s1 := make([]int, 0, 5)
// 切片擴容,append()
s1 = append(s1, 1, 2)
fmt.Println(s1)
s2 := []int{100, 200, 300, 400}
// 切片擴容之引入另一個切片。
// slice = append(slice, anotherSlice...)
// ... 可變引數 ...xxx
// [...] 根據長度變化陣列的大小定義
// anotherSlice... , slice...解構,可以直接獲取到slice中的所有元素
s1 = append(s1, s2...)
fmt.Println(s1)
}
4、遍歷切片
package main
import "fmt"
func main() {
s1 := make([]int, 0, 5)
fmt.Println(s1)
// 切片擴容,append()
s1 = append(s1, 1, 2)
fmt.Println(s1)
// 問題:容量只有5個,那能放超過5個的嗎? 可以,切片是會自動擴容的。
s1 = append(s1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 7)
fmt.Println(s1)
// 切片擴容之引入另一個切片。
// new : 解構 slice.. ,解出這個切片中的所有元素。
s2 := []int{100, 200, 300, 400}
// slice = append(slice, anotherSlice...)
// ... 可變引數 ...xxx
// [...] 根據長度變化陣列的大小定義
// anotherSlice... , slice...解構,可以直接獲取到slice中的所有元素
// s2... = {100,200,300,400}
s1 = append(s1, s2...)
// 遍歷切片
for i := 0; i < len(s1); i++ {
fmt.Println(s1[i])
}
for i := range s1 {
fmt.Println(s1[i])
}
}
5、擴容的記憶體分析
// 1、每個切片引用了一個底層的陣列
// 2、切片本身不儲存任何資料,都是底層的陣列來儲存的,所以修改了切片也就是修改了這個陣列中的資料
// 3、向切片中新增資料的時候,如果沒有超過容量,直接新增,如果超過了這個容量,就會自動擴容,成倍的增加, copy
// - 分析程式的原理
// - 看原始碼
//
// 4、切片一旦擴容,就是重新指向一個新的底層陣列。
package main
import "fmt"
func main() {
s2 := []int{1, 2, 3}
fmt.Println(len(s2), cap(s2))
fmt.Printf("%p\n", s2)
s2 = append(s2, 4, 5)
fmt.Println(len(s2), cap(s2))
fmt.Printf("%p\n", s2)
s2 = append(s2, 4, 5)
fmt.Println(len(s2), cap(s2))
fmt.Printf("%p\n", s2)
s2 = append(s2, 4, 5)
fmt.Println(len(s2), cap(s2))
fmt.Printf("%p\n", s2)
s2 = append(s2, 4, 5)
fmt.Println(len(s2), cap(s2))
fmt.Printf("%p\n", s2)
s2 = append(s2, 4, 5)
fmt.Println(len(s2), cap(s2))
fmt.Printf("%p\n", s2)
s2 = append(s2, 4, 5)
fmt.Println(len(s2), cap(s2))
fmt.Printf("%p\n", s2)
/* 3 3
0xc000126078
5 6
0xc000144030
7 12
0xc000102060
9 12
0xc000102060
11 12
0xc000102060
13 24
0xc000152000
15 24
0xc000152000
*/
}
slice擴容的具體實現
主要使用到了make方法和copy方法
package main
import "fmt"
func main() {
s2 := []int{1, 2, 3}
fmt.Printf("len:%d,cap:%d,%v\n", len(s2), cap(s2), s2)
s3 := make([]int, len(s2), cap(s2)*2)
copy(s3, s2)
fmt.Printf("len:%d,cap:%d,%v\n", len(s3), cap(s3), s3)
}
6、使用陣列建立切片
package main
import "fmt"
func main() {
arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 陣列的擷取
// 透過陣列建立切片
s1 := arr[:5] // 1-5
s2 := arr[3:8] // 4-8
s3 := arr[5:] // 6-10
s4 := arr[:] // 0-10
fmt.Println(s1)
fmt.Println(s2)
fmt.Println(s3)
fmt.Println(s4)
// 檢視容量和長度
// 擷取後切片的長度就是擷取陣列的元素個數,切片的容量就是從擷取陣列的開始位置到陣列的最後位置
fmt.Printf("s1 len:%d, cap:%d\n", len(s1), cap(s1)) // s1 len:5, cap:10
fmt.Printf("s2 len:%d, cap:%d\n", len(s2), cap(s2)) // s2 len:5, cap:7
fmt.Printf("s3 len:%d, cap:%d\n", len(s3), cap(s3)) // s3 len:5, cap:5
fmt.Printf("s4 len:%d, cap:%d\n", len(s4), cap(s4)) // s4 len:10, cap:10
// 切片的記憶體地址,就是擷取陣列開始的地址 cap(切片)=len(陣列) 他們的記憶體地址就相同
fmt.Printf("%p,%p\n", s1, &arr) // 0xc000016230,0xc000016230
fmt.Printf("%p,%p\n", s2, &arr[3]) // 0xc000016248,0xc000016248
fmt.Printf("%p,%p\n", s2, &arr[3]) // 0xc000016248,0xc000016248
fmt.Printf("%p,%p\n", s3, &arr[5]) // 0xc000016258,0xc000016258
fmt.Printf("%p,%p\n", s4, &arr[0]) // 0xc000016230,0xc000016230
// 修改陣列的內容, 切片也隨之發生了變化 (切:切片不儲存資料-->底層的陣列 )
arr[2] = 100
fmt.Println(arr) // [1 2 100 4 5 6 7 8 9 10]
fmt.Println(s1) // [1 2 100 4 5]
fmt.Println(s2) // [4 5 6 7 8]
fmt.Println(s3) // [6 7 8 9 10]
fmt.Println(s4) // [1 2 100 4 5 6 7 8 9 10]
fmt.Println("--------------------------------------------")
// 修改切片的內容,發現陣列也隨之發生了變化。(本質:修改的都是底層的陣列)
s2[2] = 80
fmt.Println(arr) // [1 2 100 4 5 80 7 8 9 10]
fmt.Println(s1) // [1 2 100 4 5]
fmt.Println(s2) // [4 5 80 7 8]
fmt.Println(s3) // [80 7 8 9 10]
fmt.Println(s4) // [1 2 100 4 5 80 7 8 9 10]
fmt.Println("--------------------------------------------")
// 切片擴容,如果容量超過了cap,那麼不會影響原陣列,因此此時s1指向的是一個新的陣列
s1 = append(s1, 11, 12, 13, 14, 15, 16)
fmt.Println(arr) // [1 2 100 4 5 80 7 8 9 10]
fmt.Printf("s1:%p,arr:%p\n", s1, &arr) // s1:0xc0000d4000,arr:0xc0000ac0f0
}
7、切片:引用型別
package main
import "fmt"
func main() {
arr1 := [3]int{1, 2, 3}
arr2 := arr1
fmt.Println("arr1:", arr1, "arr2:", arr2)
fmt.Printf("arr1:%p,arr2:%p\n", &arr1, &arr2) // arr1:0xc0000aa078,arr2:0xc0000aa090
arr1[1] = 10
fmt.Println("arr1:", arr1, "arr2:", arr2) // [3 4 5] [3 4 5]
fmt.Println("---------------------------------")
s1 := make([]int, 0, 5)
s1 = append(s1, 3, 4, 5)
s2 := s1
fmt.Println(s1, s2)
fmt.Printf("s1:%p,s2:%p\n", s1, s2) //s1:0xc0000c8030,s2:0xc0000c8030
s1[1] = 30
fmt.Println(s1, s2) // [3 30 5] [3 30 5]
}
8、深複製、淺複製
深複製:複製是資料的本身
- 值型別的資料,預設都是深複製,array、int、float、string、bool、struct....
淺複製:複製是資料的地址,會導致多個變數指向同一塊記憶體。
- 引用型別的資料: slice、map
- 因為切片是引用類的資料,直接複製的是這個地址
切片怎麼實現深複製 copy
package main
import "fmt"
func main() {
// 將原來切片中的資料複製到新切片中
s1 := []int{1, 2, 3, 4}
s2 := make([]int, 0) // len:0 cap:0
for i := 0; i < len(s1); i++ {
s2 = append(s2, s1[i])
}
fmt.Println(s1, s2)
fmt.Printf("%p,%p\n", s1, s2)
s1[2] = 200
fmt.Println(s1, s2)
}
9、函式中引數傳遞問題
按照資料的儲存特點來分:
- 值型別的資料:操作的是資料本身、int 、string、bool、float64、array...
- 引用型別的資料:操作的是資料的地址 slice、map、chan....
值傳遞
引用傳遞
package main
import "fmt"
func main() {
// 值傳遞
arr := [3]int{1, 2, 3}
fmt.Println(arr)
update(arr)
fmt.Println(arr)
// 引用傳遞
s1 := []int{4, 5, 6}
fmt.Println(s1)
update_s(s1)
fmt.Println(s1)
}
func update(arr [3]int) {
fmt.Println(arr)
arr[1] = 100
fmt.Println(arr)
}
func update_s(s1 []int) {
fmt.Println(s1)
s1[1] = 20
fmt.Println(s1)
}