4、指針&數組&切片

1、指針

引入:
指針對於性能的影響是不言而喻的,如果你想要做的是系統編程、操作系統或者網絡應用,指針更是不可或缺的一部分。Go 語言爲程序員提供了控制數據結構的指針的能力;但是,你不能進行指針運算。
程序在內存中存儲它的值,每個內存塊(或字)有一個地址,通常用十六進制數表示,如:0x6b0820,而一個指針變量指向了一個值的內存地址。
Go 語言的取地址符是 &;*號來獲取指針所指向的內容,這裏的*號是一個類型更改器。

//一個指針變量在32位機器上佔用4個字節,在64位機器上佔用8個字節,並且與它所指向的值的大小無關。當然,可以聲明指針指向任何類型的值來表明它的原始性或結構性;go指針類似於java中的引用。指針創建後,被指向的變量也保存在內存中,直到沒有任何指針指向它們,所以從它們被創建開始就具有相互獨立的生命週期。
var a int = 10   
fmt.Printf("變量的地址: %x\n", &a  )
//這個地址可以存儲在一個叫做指針的特殊數據類型中,聲明格式var var_name *var-type
var intP *int//引用上例
intP=&a//intP存儲了a的內存地址;它指向了a的位置,它引用了變量a。
b=*intP//符號 * 可以放在一個指針前,這被稱爲反引用(或者內容或者間接引用)操作符;另一種說法是指針轉移。
c=&10//不能得到一個文字或常量的地址
var p *int
*p=0//對一個空指針的反向引用是不合法的,並且會使程序崩潰

一個指針導致的間接引用(一個進程執行了另一個地址),指針的過度頻繁使用也會導致性能下降。

指針的應用:
☆定義一個指針數組來存儲地址
☆Go 支持指向指針的指針:
可以進行任意深度的嵌套,導致你可以有多級的間接引用,但在大多數情況這會使你的代碼結構不清晰。
☆通過引用或地址傳參,在函數調用時可以改變其值:
這樣不會傳遞變量的拷貝。指針傳遞是很廉價的,只佔用 4 個或 8 個字節。當程序在工作中需要佔用大量的內存,或很多變量,或者兩者都有,使用指針會減少內存佔用和提高效率。

2、數組與切片slice

數組:
數組是由一個固定長度的特定類型元素組成的序列,一個數組可以由零個或多個元素組成,數組元素可以重新賦值。數組的每個元素都可以通過下標來訪問。元素的數目,也稱爲長度或者數組大小必須是固定的並且在聲明該數組時就給出(編譯時需要知道數組長度以便分配內存);數組長度最大爲 2Gb。

聲明格式:
var name [size]type
name2 :=[…]int{1,2,3} // 數組長度根據初始化值的個數來計算(成爲切片?)
name3 :=[…]int{99:-1} // 定義一個含有100個元素的數組,最後一個元素被初始化爲-1,其他元素爲0
name4 :=[10]{1,2,3} // 從左邊起開始忽略,除了前三個元素外其他元素都爲0。
name5 :=new([5]int) // 數組是值類型,可以通過new函數創建,返回其指針

//數組長度也是數組類型的一個組成部分
a :=[3]int{1,2,3}//初始化數組中 {} 中的元素個數不能大於 [] 中的數字
a=[4]int{1,2,3,4}//編譯錯誤,類型不同
var arr=new([5]int)//類型:*[5]int
arr2:=*arr1
arr2[2]=100

數組的初始化與遍歷:

//使用for循環賦值與遍歷
func main(){
	var arr [5]int
	for i:=0;i<len(arr);i++{
	arr[i]=i*2
	}
	for i:=0;i<len(arr);i++{
	fmt.Printf("index is %d ,value is %d",i,arr[i])
	}
}
//for range方式
func main(){
	a := []{1,2,3}
	for i :=range a{
	fmt.Println("index: ",i,"value: ",a[i])
	}
}

多維數組:
聲明格式與一位數組相似:
var a [3][6]int

作爲函數參數:
因爲函數參數傳遞的機制導致傳遞大的數組類型將是低效的,並且對數組參數的任何的修改都是發生在複製的數組上,並不能直接修改調用時原始的數組變量。go語言可以通過指針來傳遞數組參數,而且也允許在函數內部修改數組的值,但是數組依然是僵化的類型,因爲數組的類型包含了僵化的長度信息。

切片slice:
切片是一個 長度可變的數組!
一個slice是一個輕量級的數據結構,提供了訪問數組子序列(或者全部)元素的功能,而且slice的底層確實引用一個數組對象。一個slice由三個部分構成:指針、長度和容量。長度不能超過容量,容量一般是從slice的開始位置到底層數據的結尾位置(容量即數組的長度)。內置的len和cap函數分別返回slice的長度和容量。多個slice之間可以共享底層的數據,並且引用的數組部分區間可能重疊。

優點:
因爲切片是引用,所以它們不需要使用額外的內存並且比使用數組更有效率,所以在 Go 代碼中 切片比數組更常
用。

聲明格式:
var r []type // 切片不需要說明長度。未初始化之前默認爲 nil,長度爲 0。
slice2 := make([]T, length, capacity)// 使用make()函數來創建切片,capacity是可選參數;make(T) 返回一個類型爲 T 的初始值,它只適用於3種內建的引用類型:切片、map 和 channel

s :=[] int {1,2,3 } //直接初始化切片,[]表示是切片類型,{1,2,3}初始化值依次是1,2,3.其cap=len=3
s := arr[:] //初始化切片s,是數組arr的引用
s := arr[startIndex:endIndex] //將arr中從下標startIndex到endIndex-1 下的元素創建爲一個新的切片
s := arr[startIndex:] //與string字符串截斷類似
s := arr[:endIndex]
s :=make([]int,len,cap) 

示意圖:
moth是一個數組;slice值包含指向第一個slice元素的指針。(複製一個slice只是對底層的數組創建了一個新的slice別名)(絕對不要用指針指向 slice。slice並不是一個純粹的引用類型)
在這裏插入圖片描述
切片的比較:
只能與nil相比較,原因:第一個原因,一個slice的元素是間接引用的,一個slice甚至可以包含自身。第二個原因,因爲slice的元素是間接引用的,一個固定值的slice在不同的時間可能包含不同的元素,因爲底層數組的元素可能會被修改。如果你需要測試一個slice是否是空的,使用len(s) == 0來判斷,而不應該用s == nil來判斷。除了文檔已經明確說明的地方,所有的Go語言函數應該以相同的方式對待nil值的slice和0長度的slice。
內置函數:

func main(){
	s := []int{1}
	s=append(s,2,3,4,5,6,)//append追加元素
	copy(s,s1)//copy複製,兩個slice可以共享同一個底層數組,甚至有重疊也沒有問題。
	fmt.Println(s1)
	sl = sl[0:len(sl)+1]//改變切片長度的過程稱之爲切片重組 reslicing
}

多維切片:
切片通常也是一維的,但是也可以由一維組合成高維。通過分片的分片(或者切片的數組),長度可以任意動態變化,所以 Go 語言的多維切片可以任意切分。而且,內層的切片必須單獨分配(通過 make 函數)。

典例bytes包:
讀寫長度未知的 bytes 最好使用bytes包中的buffer類型,這種實現方式比使用 += 要更節省內存和 CPU,尤其是要串聯的字符串數目特別多的時候。此類型有Read 和 Write 方法。

var buffer bytes.Buffer//聲明
var r *bytes.Buffer = new(bytes.Buffer)//獲取指針
var buffer bytes.Buffer
for {
	if s, ok := getNextString(); ok { //method getNextString() not shown here
	buffer.WriteString(s)
	} else {
	break
	}
}
fmt.Print(buffer.String(), "\n")

For——range結構:
這種結構常用於數組和切片
for index,value :=range slice{}//第一個返回值是數組或者切片的索引,第二個是在該索引位置的值;他們都是僅在 for 循環內部可見的局部變量。value 只是 slice 某個索引位置的值的一個拷貝,不能用來修改 slice 該索引位置的值。

應用:
☆從字符串生成字節切片:
假設 s 是一個字符串(本質上是一個字節數組),那麼就可以直接通過 c := []byte(s) 來獲取一個字節的切片 c。還可以通過 copy 函數來達到相同的目的: copy(dst []byte, src string) 。由於Unicode 字符會佔用 2 個字節,有些甚至需要 3 個或者 4 個字節來進行表示。如果發現錯誤的 UTF8 字符,可以使用 c := []int32(s) 語法,這樣切片中的每個 int 都會包含對應的 Unicode 代碼,因爲字符串中的每次字符都會對應一個整數。類似的,您也可以將字符串轉換爲元素類型爲 rune 的切片: r := []rune(s) 。可以通過代碼 len([]int32(s)) 來獲得字符串中字符的數量,但使用 utf8.RuneCountInString(s) 效率會更高一點。
☆字符串和切片的內存結構:
在內存中,一個字符串實際上是一個雙字結構,即一個指向實際數據的指針和記錄字符串長度的整數。因爲指針對用戶來說是完全不可見,因此我們可以依舊把字符串看做是一個值類型,也就是一個字符數組。
在這裏插入圖片描述
☆修改字符串中的某個字符:
Go 語言中的字符串是不可變的,必須先將字符串轉換成字節數組,然後再通過修改數組中的元素值來達到修改字符串的目的,最後將字節數組轉換回字符串格式。

s := "hello"
c := []byte(s)
c[0] = 'c'
s2 := string(c) // s2 == "cello"

☆搜索及排序切片和數組:
標準庫提供了 sort 包來實現常見的搜索和排序操作。您可以使用 sort 包中的函數 func Ints(a []int) 來實現對 int類型的切片排序,爲了檢查某個數組是否已經被排序,可以通過函數 IntsAreSorted(a []int) bool 來檢查,如果返回 true 則表示已經被排序。類似的,可以使用函數 func Float64s(a []float64) 來排序 float64 的元素,或使用函數 func Strings(a []string) 排序字符串元素。
想要在數組或切片中搜索一個元素,該數組或切片必須先被排序(因爲標準庫的搜索算法使用的是二分法)。然後可以使用函數 func SearchInts(a []int, n int) int 進行搜索,並返回對應結果的索引值。

☆切片和垃圾回收:
切片的底層指向一個數組,該數組的實際容量可能要大於切片所定義的容量。只有在沒有任何切片指向的時候,底層的數組內存纔會被釋放,這種特性有時會導致程序佔用多餘的內存。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章