數(shù)組
Go語言中的數(shù)組是定長的同一類型數(shù)據(jù)的集合,數(shù)組索引是從0開始的筋现。
數(shù)組有以下幾種創(chuàng)建方式
// 聲明一個叫 balance的 10個元素的float32 數(shù)組
var balance [10] float32
//數(shù)組初始化
var balance = [5]float32{1000.0, 2000.0, 3000.4, 7000.0, 5000.0}
// 忽略 [] 中的數(shù)字不設(shè)置數(shù)組大小唐础,Go 語言會根據(jù)元素的個數(shù)自動設(shè)置數(shù)組的大小
var balance = [...]float32{1000.0, 2000.0, 3000.4, 7000.0, 5000.0}
以下是一些特殊數(shù)組
[2*N] struct {x,y int32} //復雜類型數(shù)組
[1000]* float64 //指針數(shù)組
[3][5]int //二維數(shù)組
[2][3][5]float64 // 等同于 [2]([3][5]float64)
當創(chuàng)建數(shù)組時,若沒有被顯示的初始化或者只是部分初始化矾飞,那么Go語言會自動的把數(shù)組其他的項都初始化為0(元素類型默認值)
獲取數(shù)組長度彻犁,使用len函數(shù);獲取數(shù)組容量大小凰慈,使用cap函數(shù)汞幢,由于數(shù)組長度不可變,因此數(shù)組的容量等于長度微谓。
len(arr) == cap(arr)
訪問數(shù)組
使用 len 遍歷
for i := 0; i < len(arr); i++ {
fmt.Printf("%c", arr[i])
}
使用 range 遍歷森篷,有兩個返回值输钩,第一個是 元素的數(shù)組下標,第二個是元素的值
for _, v := range arr{
fmt.Printf("%c", v)
}
數(shù)組是值傳遞仲智,因此在函數(shù)內(nèi)操作數(shù)組只是數(shù)組的一個副本买乃,不會影響數(shù)組本身,但可以通過傳遞指針來避免值傳遞钓辆。
func main() {
array := [5]int{1,2,3,4,5}
modify(array)
fmt.Println("In main, array values:",array)
}
func modify(array [5]int) {
array[0] = 10
fmt.Println("In modify, array values:",array)
}
------------output-----------
In modify, array values: [10 2 3 4 5]
In main, array values: [1 2 3 4 5]
數(shù)組切片
是引用類型剪验,可以自動擴容但容量固定,彌補數(shù)組的長度在定義后無法再次修改前联,在函數(shù)體內(nèi)無法對外部的數(shù)組內(nèi)部結(jié)構(gòu)進行修改的缺點功戚。
數(shù)組切片的數(shù)據(jù)結(jié)構(gòu)可以抽象為以下3個變量:
①一個指向原生數(shù)組的指針
②數(shù)組切片中的元素個數(shù)
③數(shù)組切片已分配的存儲空間
數(shù)組與切片都可以使用下面所給出的語法進行切片
s[n] //切片s中索引為n的項
s[n:m] //從切片s的索引位置 n 到 m-1 處所獲得的切片
s[n:] //從切片s的索引位置 n 到len(s)-1處所獲得的切片
s[:m] //從切片s的索引位置 0 到 m-1 處所獲得的切片
s[:] //切片s的索引位置0到len(s)-1處所獲得的切片
cap(s) //獲得切片的容量:總是>= len(s)
len(s) //獲得切片包含元素的個數(shù):總是<= cap(s)
s[:cap(s)] //增加切片s的長度到其容量,如果長度小于等于容量的話
注:s == s[:n]+s[n:] //s是一個字符串似嗤,n為整型啸臀,0<=n<=len(s)
創(chuàng)建數(shù)組切片有以下幾種方式
①基于數(shù)組創(chuàng)建數(shù)組切片
var myArray [10]int = [10]int{1,2,3,4,5}
var mySlice []int = myArray[:5] //前五個元素創(chuàng)建數(shù)組切片
var mySlice []int = myArray[:] // 所有元素創(chuàng)建數(shù)組切片
②直接創(chuàng)建數(shù)組切片
//創(chuàng)建元素初始值為0,比如下面初始元素個數(shù)即長度為5(必須設(shè)定)
//預留10個元素的存儲容量(可以不設(shè)定烁落,默認跟初始元素相等)乘粒,空間容量大于等于初始元素個數(shù)
mySlice :=make([]int,5,10)
mySlice := []int{1,2,3,4,5}
③基于數(shù)組切片創(chuàng)建數(shù)組切片(指向同一個隱藏數(shù)組)
func main() {
mySlice1 := []int{1,2,3,4,5} //容量與長度相同
mySlice2 := mySlice1[:3]
fmt.Println(mySlice2)
}
只要mySlice2選擇的范圍 mySlice1[:n] 這個n不超過 cap(mySlice1) 的值即可,自動補充0
當創(chuàng)建一個切片時伤塌,它會創(chuàng)建一個隱藏的初始化為零值的數(shù)組灯萍,然后返回引用該隱藏數(shù)組的切片。該隱藏數(shù)組也是固定長度的每聪,該長度始終等于切片的容量旦棉。比如下圖所示的切片x,基于切片x創(chuàng)建的切片y熊痴,隱藏數(shù)組替裆。
由于數(shù)組切片是一個引用類型甫匹,因此若有多個指向同一個隱藏數(shù)組的切片中的某一個進行修改操作币砂,那么其他切片都會受影響
func main() {
var mySlice1 = []int{1,2,3,4,5,6,7}
mySlice2 := mySlice1[:5]
mySlice3 := mySlice1[3:]
fmt.Println(mySlice1,mySlice2,mySlice3)
mySlice3[0] = 100
fmt.Println(mySlice1,mySlice2,mySlice3)
}
-----output-----
[1 2 3 4 5 6 7] [1 2 3 4 5] [4 5 6 7]
[1 2 3 100 5 6 7] [1 2 3 100 5] [100 5 6 7]
空數(shù)組切片
一個數(shù)組切片未被初始化時砾嫉,默認為nil怕午,存儲空間長度為0度宦,元素個數(shù)為0
len(): 返回當前切片存儲的元素個數(shù) cap(): 返回數(shù)組切片分配的存儲空間
var numbers []int // len=0 cap=0 slice=[]
動態(tài)增減元素琳钉,合理的設(shè)置數(shù)組切片的存儲空間菠剩,將會減少切片內(nèi)部重新分配內(nèi)存和搬送內(nèi)存塊的頻率纪他,提高性能鄙煤。
接下來我們看一個創(chuàng)建切片綜合運用實例
func main() {
/* 創(chuàng)建切片 */
numbers := []int{0,1,2,3,4,5,6,7,8}
printSlice(numbers) // len=9 cap=9 slice=[0 1 2 3 4 5 6 7 8]
/* 打印原始切片 */
fmt.Println("numbers ==", numbers) //numbers == [0 1 2 3 4 5 6 7 8]
/* 打印子切片從索引1(包含) 到索引4(不包含)*/
fmt.Println("numbers[1:4] ==", numbers[1:4]) //numbers[1:4] == [1 2 3]
/* 默認下限為 0*/
fmt.Println("numbers[:3] ==", numbers[:3]) //numbers[:3] == [0 1 2]
/* 默認上限為 len(s)*/
fmt.Println("numbers[4:] ==", numbers[4:]) //numbers[4:] == [4 5 6 7 8]
numbers1 := make([]int,0,5)
printSlice(numbers1) //len=0 cap=5 slice=[]
/* 打印子切片從索引 0(包含) 到索引 2(不包含) */
number2 := numbers[:2]
printSlice(number2) // len=2 cap=9 slice=[0 1]
/* 打印子切片從索引 2(包含) 到索引 5(不包含) */
number3 := numbers[2:5]
printSlice(number3) //len=3 cap=7 slice=[2 3 4]
}
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
總結(jié):基于一個數(shù)組切片a通過s[n:m]方式創(chuàng)建一個新的子切片b,那么子切片b的容量等于a切片容量減去n
向數(shù)組切片添加元素
切片不支持+=操作茶袒,所以要繼續(xù)增加存儲的元素梯刚,可以使用append 函數(shù)。append 函數(shù)可以直接將一個數(shù)組切片加到當前數(shù)組切片的后面薪寓,記住添加的數(shù)組切片后邊必須加三個點亡资,相當于把mySlice1的所有元素打散后傳入mySlice澜共,但是傳入的mySlice1所有元素順序不變,使用append函數(shù)的兩個數(shù)組切片的元素類型必須是相同的锥腻。若原始切片的容量不足夠容納原始元素和新添加進來的元素嗦董,那么append 函數(shù)將會隱式的創(chuàng)建一個新的切片,并將原始元素與新元素都添加進來瘦黑。
//numbers指切片京革,n指元素,這個n可以為空幸斥,那么依舊為原數(shù)組切片
numbers = append(numbers, n)
//示例一
func main() {
var mySlice1 = make([]int,5,10)
fmt.Printf("mySlice1:%v, len(mySlice1):%v, cap(mySlice1):%v\n",mySlice1,len(mySlice1),cap(mySlice1))
mySlice1 = append(mySlice1,1,2,3)
fmt.Printf("mySlice1:%v, len(mySlice1):%v, cap(mySlice1):%v\n",mySlice1,len(mySlice1),cap(mySlice1))
mySlice2 := []int{4,5,6}
mySlice1 = append(mySlice1,mySlice2...)
fmt.Printf("mySlice1:%v, len(mySlice1):%v, cap(mySlice1):%v\n",mySlice1,len(mySlice1),cap(mySlice1))
}
------output------
mySlice1:[0 0 0 0 0], len(mySlice1):5, cap(mySlice1):10
mySlice1:[0 0 0 0 0 1 2 3], len(mySlice1):8, cap(mySlice1):10
mySlice1:[0 0 0 0 0 1 2 3 4 5 6], len(mySlice1):11, cap(mySlice1):20
之前說切片的底層實現(xiàn)是通過共享數(shù)組的方式實現(xiàn)的匹摇,append在進行添加元素時,會首先檢查原切片的可用容量睡毒,也就是底層共享數(shù)組的長度是否滿足来惧,如果底層數(shù)組長度不夠,那么就會分配一個新的數(shù)組演顾,將被引用的所有的值復制到新數(shù)組當中供搀,再繼續(xù)添加新元素。
如果是新切片進行append添加新元素钠至,那么原切片的容量與長度都不會改變葛虐,哪怕新切片擴容超過原切片的容量
//示例二
func main() {
a := []int{1, 2, 3, 4, 5}
b := a[2:3]
fmt.Println("a: ", a, " len: ", len(a), " cap: ", cap(a))
fmt.Println("b: ", b, " len: ", len(b), " cap: ", cap(b))
b = append(b, 6)
fmt.Println("a: ", a, " len: ", len(a), " cap: ", cap(a))
fmt.Println("b: ", b, " len: ", len(b), " cap: ", cap(b))
}
---output---
a: [1 2 3 4 5] len: 5 cap: 5
b: [3] len: 1 cap: 3
a: [1 2 3 6 5] len: 5 cap: 5
b: [3 6] len: 2 cap: 3
在上面的示例中使用newSlice = Slice[n:m],新切片的容量會隨著舊切片走棉钧。如果使用索引參數(shù)就可以來指定新切片的容量
//示例三
a := []int{1, 2, 3, 4, 5}
c := a[2:3:4] //注:a[i:j:k]:容量cap = k-i屿脐,長度len = j-i,因為a的容量為5宪卿,因此這里k的值最大不能大于5的诵,k< cap(a)
fmt.Println("c: ", c, " len: ", len(c), " cap: ", cap(c))
----output----
c: [3] len: 1 cap: 2
若將k的值大于cap(a),也就是設(shè)定新切片容量大于舊切片容量時佑钾,那么就會報運行時錯誤西疤,這個很難找到錯誤原因。
c := a[2:3:6]
---------------------------------------------------------
panic: runtime error: slice bounds out of range
goroutine 1 [running]:
main.main()
D:/GoDemo/src/MyGo/Demo_05.go:17 +0x74a
解決方法:如果在創(chuàng)建新切片時休溶,設(shè)定長度與容量一致代赁,新切片進行append操作時,會強制在底層立即創(chuàng)建新的數(shù)組兽掰,這就跟原切片在底層上不是共享同一數(shù)組芭碍,這樣就可以安全的操作新切片,將上面的示例二中 b := a[2:3] 改為 b := a[2:3:3] 進行結(jié)果對比
//設(shè)置新切片長度與容量都相等的情況
a := []int{1, 2, 3, 4, 5}
b := a[2:3:3] //只取出原切片索引為2的值孽尽,并設(shè)定新切片容量與長度都為1
fmt.Println("a: ", a, " len: ", len(a), " cap: ", cap(a))
fmt.Println("b: ", b, " len: ", len(b), " cap: ", cap(b))
//新切片添加新元素操作
b = append(b,6 )
fmt.Println("a: ", a, " len: ", len(a), " cap: ", cap(a))
fmt.Println("b: ", b, " len: ", len(b), " cap: ", cap(b))
---output---
a: [1 2 3 4 5] len: 5 cap: 5
b: [3] len: 1 cap: 1
a: [1 2 3 4 5] len: 5 cap: 5
b: [3 6] len: 2 cap: 2
內(nèi)容復制
使用Go語言另一個內(nèi)置函數(shù)copy函數(shù)窖壕,它接受兩個包含相同類型元素的切片,并將源切片的元素復制到目標切片,同時返回所復制元素的數(shù)量瞻讽。若這兩個切片不是一樣大狐蜕,那么自動按照較小的數(shù)組切片的元素個數(shù)進行復制。這個復制只是復制數(shù)值卸夕,原切片索引值改變层释,不會影響到新的切片
func main() {
mySlice1 := []int{1,2,3,4,5}
mySlice2 := []int{6,7,8}
copy(mySlice1,mySlice2) //將mySlice2 復制到 mySlice1 的前3個位置
fmt.Println(mySlice1) // [6 7 8 4 5]
copy(mySlice2,mySlice1) //因為mySlice2的長度比mySlice1小,反過來就是將 mySlice1 的前3個元素復制到mySlice2
fmt.Println(mySlice2) // [1 2 3]
mySlice1[0] = 100
fmt.Println(mySlice1) //[100 2 3 4 5]
fmt.Println(mySlice2) //[1 2 3]
}
切片迭代
同樣可以使用range配合for循環(huán)迭代輸出切片中的元素快集,但是要注意這里的range迭代輸出的兩個值:第一個值是當前迭代的索引位置贡羔,第二個值是該位置對應元素值的副本。
func main() {
slice := []int{10, 20, 30, 40}
// 迭代每一個元素个初,并顯示其值
for index, value := range slice {
fmt.Printf("Index: %d Value: %d\n", index, value)
}
}
---output---
Index: 0 Value: 10
Index: 1 Value: 20
Index: 2 Value: 30
Index: 3 Value: 40
range迭代切片時乖寒,會返回當前迭代的索引位置與該位置對應元素值的副本,range為每個元素都創(chuàng)建了副本院溺,而不是直接返回對該元素的引用楣嘁。
如果使用返回value值的地址作為指向每個元素的指針,就會造成錯誤珍逸,因為每個value值的地址都是相同的逐虚,無法區(qū)分.
func main() {
slice := []int{10, 20, 30, 40}
// 迭代每一個元素,并顯示其值
for index, value := range slice {
fmt.Printf("Value: %d Value-Addr: %X ElemAddr: %X\n",
value, &value, &slice[index])
}
}
----output----
Value: 10 Value-Addr: C042060080 ElemAddr: C04205E0C0
Value: 20 Value-Addr: C042060080 ElemAddr: C04205E0C8
Value: 30 Value-Addr: C042060080 ElemAddr: C04205E0D0
Value: 40 Value-Addr: C042060080 ElemAddr: C04205E0D8
結(jié)論:每次迭代返回的變量value值其實是在迭代過程中據(jù)切片依次賦值的新變量谆膳,不是切片中原來的值了叭爱,因此如果需要求得每個元素的地址,還是使用 &slice[index] 的方式
切片操作實戰(zhàn)
①插入元素漱病。先保存后面的元素买雾,再取前面的元素添加元素,再組合起來
第一種:利用append函數(shù)
func main() {
var name = []int{1,2,3,4,5,6,7,8,9,10}
index := 5
insertSlice := []int{1000}
name1 := append([]int{},name[index:]...) //name1: [6 7 8 9 10]
name2 := append(name[:index],insertSlice...) //name2: [1 2 3 4 5 1000]
name2 = append(name2,name1...)
fmt.Println(name2) //name2: [1 2 3 4 5 1000 6 7 8 9 10]
}
上面的方法可以再簡化如下
func main() {
var name = []int{1,2,3,4,5,6,7,8,9,10}
index := 5
insertSlice := []int{1000}
name = append(name[:index],append(insertSlice, name[index:]...)...)
fmt.Println(name) //name2: [1 2 3 4 5 1000 6 7 8 9 10]
}
第二種:利用copy函數(shù)
func main() {
var slice = []int{1,2,3,4,5,6,7,8,9,10}
insertSlice := []int{1000}
index := 5 //插入的切片索引
//根據(jù)原始切片與新切片創(chuàng)建新的切片
result := make([]int,len(slice)+len(insertSlice))
at := copy(result,slice[:index])
at += copy(result[at:],insertSlice)
copy(result[at:],slice[index:])
fmt.Println(result) // result:[1 2 3 4 5 1000 6 7 8 9 10]
}
②刪除元素杨帽。
從開頭刪除某個索引處的元素
func main() {
var name = []int{1,2,3,4,5,6,7,8,9,10}
name = name[1:]
}
從結(jié)尾刪除某個索引處的元素
func main() {
var name = []int{1,2,3,4,5,6,7,8,9,10}
name = name[:9]
}
從中間刪除某個索引處的元素
func main() {
var name = []int{1,2,3,4,5,6,7,8,9,10}
a := len(name)/2
name = append(name[:a],name[a+1:]...) //name: [1 2 3 4 5 7 8 9 10]
}
刪除某一個索引區(qū)間的全部元素
func main() {
var name = []int{1,2,3,4,5,6,7,8,9,10}//name: [1 2 3 4 5 6 7 8 9 10]
start := 1
end := 5
name = append(name[:start],name[end:]...) //name: [1 6 7 8 9 10]
}
使用copy同樣能達到目的
③切片尾部追加元素
func main() {
var name = []int{1,2,3,4,5,6,7,8,9,10}
fmt.Println("name: ",name)
//尾部追加元素
for i:=11;i<=15 ;i++ {
name = append(name,i)
}
fmt.Println("name: ",name)
}
append函數(shù)修改切片會改變原始切片漓穿,而copy函數(shù)修改不會。
關(guān)于切片的指針
①當我們用append追加元素到切片時注盈,如果容量不夠晃危,go就會創(chuàng)建一個新的切片變量,看下面程序的執(zhí)行結(jié)果:
func main() {
var sa []int
fmt.Printf("addr:%p \tlen:%v \tcontent:%v\n",sa,len(sa),sa);
for i:=0;i<10;i++{
sa=append(sa,i)
fmt.Printf("addr:%p \tlen:%v \t content:%v\n",sa,len(sa),sa);
}
fmt.Printf("addr:%p \tlen:%v \t content:%v\n",sa,len(sa),sa);
}
------output-------
addr:0x0 len:0 content:[]
addr:0xc042060088 len:1 content:[0]
addr:0xc0420600c0 len:2 content:[0 1]
addr:0xc04205e0e0 len:3 content:[0 1 2]
addr:0xc04205e0e0 len:4 content:[0 1 2 3]
addr:0xc042084100 len:5 content:[0 1 2 3 4]
addr:0xc042084100 len:6 content:[0 1 2 3 4 5]
addr:0xc042084100 len:7 content:[0 1 2 3 4 5 6]
addr:0xc042084100 len:8 content:[0 1 2 3 4 5 6 7]
addr:0xc04208e000 len:9 content:[0 1 2 3 4 5 6 7 8]
addr:0xc04208e000 len:10 content:[0 1 2 3 4 5 6 7 8 9]
addr:0xc04208e000 len:10 content:[0 1 2 3 4 5 6 7 8 9]
因為初始時指定的切片容量不足当凡,因此切片在進行append操作時山害,會自動擴容產(chǎn)生新的切片變量纠俭,因此切片變量地址會頻繁變動
因此在不能預估切片的容量情況下沿量,又要防止切片變量地址頻繁變動,我們就需要使用指針來操作切片變量冤荆,其本質(zhì)上是:append操作亦然會在需要的時候構(gòu)造新的切片朴则,不過是將地址都保存到了sa中,因此我們通過該指針始終可以訪問到真正的數(shù)據(jù)。
func main() {
var osa = make ([]int,0);
sa:=&osa;
for i:=0;i<10;i++{
*sa=append(*sa,i)
fmt.Printf("addr of osa:%p,\taddr:%p \t content:%v\n",osa,sa,sa);
}
fmt.Printf("addr of osa:%p,\taddr:%p \t content:%v\n",osa,sa,sa);
}
-------output--------
addr of osa:0xc042060080, addr:0xc04205a3e0 content:&[0]
addr of osa:0xc0420600b0, addr:0xc04205a3e0 content:&[0 1]
addr of osa:0xc04205e0e0, addr:0xc04205a3e0 content:&[0 1 2]
addr of osa:0xc04205e0e0, addr:0xc04205a3e0 content:&[0 1 2 3]
addr of osa:0xc042084100, addr:0xc04205a3e0 content:&[0 1 2 3 4]
addr of osa:0xc042084100, addr:0xc04205a3e0 content:&[0 1 2 3 4 5]
addr of osa:0xc042084100, addr:0xc04205a3e0 content:&[0 1 2 3 4 5 6]
addr of osa:0xc042084100, addr:0xc04205a3e0 content:&[0 1 2 3 4 5 6 7]
addr of osa:0xc04208e080, addr:0xc04205a3e0 content:&[0 1 2 3 4 5 6 7 8]
addr of osa:0xc04208e080, addr:0xc04205a3e0 content:&[0 1 2 3 4 5 6 7 8 9]
addr of osa:0xc04208e080, addr:0xc04205a3e0 content:&[0 1 2 3 4 5 6 7 8 9]