1. 程式人生 > >JAVA轉go系列之 陣列切片

JAVA轉go系列之 陣列切片

 

陣列對於程式語言來說都是比較重要的資料結構之一,陣列是具有相同唯一型別的一組已編號且長度固定的資料項序列,這種型別可以是任意的原始型別例如整形、字串或者自定義型別。,java和go的陣列也是差不多

java 利用new關鍵字來例項陣列

dataType[] array= new dataType[arraySize]

上面的語句完成了兩件事情,使用new關鍵字建立了一個dataType型別長度為arraySize的陣列並且賦值給變數array

另外還可以使用如下方式來建立一個數組

dataType[] array = {dataTypeVal0,dataTypeVal1,dataTypeVal2,dataTypeVal3}

Go也大同小異,在Java中我們在編寫程式的時候很少會用到陣列,一般都是用集合來代替陣列,在go中也是如此,go維護了一個slice(切片)來使陣列使用的更加方便,

slice是對陣列的抽象,go中的陣列長度是固定的,在函式中傳遞的方式使用的是值的傳遞,用起來不是很方便,效能也會降低(值傳遞是copy一份來傳遞,copy會消耗掉效能)

定義切片

你可以使用make()函式來建立切片:

var slice1 []type = make([]type, len)
也可以簡寫為
slice1 := make([]type, len) //這裡的len是切片的初始長度
slice:=make([]int,5,10)

這樣建立的切片長度是5,容量是10,這個容量10是對應的切片底層陣列的

因為切片底層是陣列,所以建立切片時如果不指定字面值的話,預設就是陣列的元素的零值。這裡我們指定的容量是10,但是我們只能訪問5個元素,因為切片的長度是5,後面的5個元素,需要切片擴充後才能訪問

容量必須>=長度,我們是不能建立長度大於容量的切片的。

還有一種建立切面的方式,使用字面量,就是指定初始化的值

這樣的建立方式非常像陣列,只是【】裡面不需要宣告長度,這樣宣告的切片長度和容量是相等的,並且會根據我們指定的字面量的值推斷出來,當然我們也可以和陣列一樣,只初始化某個索引的值

這裡指定了陣列的長度是5個(從0開始)並且指定第5個元素的值是1其他元素的預設值都是0.順便對比一下切片和陣列的微小差別

輸出結果

切片還有nil切片和空切片,他們的長度和容量都是0,但是他們指向底層陣列的指標不一樣,nil意味著指向底層陣列的指標為nil,而空切片指標指向的是一個實際地址

//nil切片
var nilSlice []int
//空切片
slice:=[]int{}

nil切片表示一個不存在的切片,而空切片表示一個空的集合

切片的另外一個用處比較多的建立是基於現有的陣列或者切片建立

slice := []int{1, 2, 3, 4, 5}
slice1 := slice[:]
slice2 := slice[0:]
slice3 := slice[:5]

fmt.Println(slice1)
fmt.Println(slice2)
fmt.Println(slice3)

 

基於現有的切片或者陣列建立,使用【i:j】這樣的操作符即可,它表示i索引開始,到j索引結束,擷取原陣列或者切片,建立一個新的切片。新切片的值包含原來切片i索引,但是不包括j的索引,對比java類似於string的substring方法,

i如果省略,預設為0,j如果省略,預設為原陣列或切片的長度,示例中三個新切片的值都是一樣的,這裡的i和j都不能超過原切片的索引

這裡需要注意一點的是,利用這種方式創建出來的新的切片和原來的切片是共用一個底層陣列,也就是指正指向同一個地址,所以對新切片的值進行修改時原切片的值也會被修改。

使用切片

使用切片和陣列一樣,通過索引就可以獲取切片對應元素的值,同樣修改也對應

slice := []int{1, 2, 3, 4, 5}
fmt.Println(slice[2]) //獲取值
slice[2] = 10 //修改值
fmt.Println(slice[2]) //輸出10

切片實際上是一個動態的陣列,它可以按需增長,我們使用內建的函式apperd即可。append可以為一個切片增加追加一個元素,至於如何增加,返回的是原來的切片還是一個新的切片,長度,容量如何改變這些細節,append函式都會自動幫我們處理。

append函式會智慧的增長底層陣列的容量,目前的演算法是:容量小於1000個時,總是成倍的增長,一旦容量超過1000個,增長因子設為1.25,也就是說每次會增加百分之25的容量

內建的append是一個可變引數,所以我們可以同時追加好幾個值

newSlice=append(newSlice,10,20,30)

此外我們還可以通過...操作符,把一個切片追加到另外一個切片裡面

slice := []int{1, 2, 3, 4, 5}
newSlice := slice[1:2:3]

newSlice=append(newSlice,slice...)
fmt.Println(newSlice)
fmt.Println(slice)

迭代切片

切片是一個集合,我們可以使用for range迴圈來迭代它,

slice := []int{1, 2, 3, 4, 5}
	for i,v:=range slice{
		fmt.Printf("索引:%d,值:%d\n",i,v)
	}

 range返回的是切片元素的複製,而不是元素的引用。所以在for range裡面對元素值的修改是不會影響到原切片的

在函式間傳遞切片

我們知道切片是3個欄位構成的結構型別,所以函式之間以值的方式傳遞的時候,佔用的成本非常小,成本很低。在傳遞複製切片的時候,其底層的陣列不會被複制,也不會受影響,複製只是複製的切片本身,不涉及底層的陣列

func main() {
	slice := []int{1, 2, 3, 4, 5}
	fmt.Printf("%p\n", &slice)
	modify(slice)
	fmt.Println(slice)
}

func modify(slice []int) {
	fmt.Printf("%p\n", &slice)
	slice[1] = 10
}

列印的輸出如下:

0xc000046400
0xc000046440
[1 10 3 4 5]

仔細的看發現這兩個地址不一樣,所以確認切片在函式之間傳遞是複製的,而我們修改一個索引的值後,發現原切片的值也被修改了,說明他們共用一個底層陣列

在函式之間傳遞切片是非常高效的,而且不需要傳遞指標和處理複雜的語法,只需要複製切片,然後根據自己的業務修改。最後傳遞迴一個新的切片副本即可,這也是函式之間傳遞引數為甚麼用使用切片而不是使用陣列。