go中的類型與c的類似,經常使用類型有一個特例:byte類型,即字節類型,長度爲,默認值是0;git
1 bytes = [5]btye{'h', 'e', 'l', 'l', 'o'}
變量bytes的類型是[5]byte,一個由5個字節組成的數組。它的內存表示就是連起來的5個字節,就像C的數組。數組
字符串在Go語言內存模型中用一個2字長(64位,32位內存佈局方式下)的數據結構表示。它包含一個指向字符串數據存儲地方的指針,和一個字符串長度數據以下圖:安全
s是一個string類型的字符串,由於string類型不可變,對於多字符串共享同一個存儲數據是安全的。切分操做str[i:j]
會獲得一個新的2字長結構t,一個可能不一樣的但仍指向同一個字節序列(即上文說的存儲數據)的指針和長度數據。因此字符串切分不涉及內存分配或複製操做,其效率等同於傳遞下標。數據結構
數組類型定義了長度和元素類型。如, [4]int
類型表示一個四個整數的數組,其長度是固定的,長度是數組類型的一部分( [4]int
和 [5]int
是徹底不一樣的類型)。 數組能夠以常規的索引方式訪問,表達式 s[n]
訪問數組的第 n 個元素。數組不須要顯式的初始化;數組的零值是能夠直接使用的,數組元素會自動初始化爲其對應類型的零值。app
1 var a [4]int 2 a[0] = 1 3 i := a[0] 4 // i == 1 5 // a[2] == 0, int 類型的零值
Go的數組是值語義。一個數組變量表示整個數組,它不是指向第一個元素的指針(不像 C 語言的數組)。 當一個數組變量被賦值或者被傳遞的時候,實際上會複製整個數組。 (爲了不復制數組,你能夠傳遞一個指向數組的指針,可是數組指針並非數組。) 能夠將數組看做一個特殊的struct,結構的字段名對應數組的索引,同時成員的數目固定。函數
b := [2]string{"Penn", "Teller"} b := [...]string{"Penn", "Teller"}
這兩種寫法, b
都是對應 [2]string
類型。佈局
切片類型的寫法是[]T
,T
是切片元素的類型。和數組不一樣的是,切片類型並無給定固定的長度。切片的字面值和數組字面值很像,不過切片沒有指定元素個數:學習
1 letters := []string{"a", "b", "c", "d"}
2 s := letters [:] //a slice referencing the storage of x 3 func make([]T, len, cap) []T //使用內置函數 make 建立
一個slice是一個數組某個部分的引用。在內存中它是一個包含三個域的結構體:指向slice中第一個元素的指針ptr,slice的長度數據len,以及slice的容量cap。長度是下標操做的上界,如x[i]中i必須小於長度。容量是分割操做的上界,如x[i:j]中j不能大於容量。slice在Go的運行時庫中就是一個C語言動態數組的實現,在$GOROOT/src/pkg/runtime/runtime.h中定義:ui
struct Slice { // must not move anything byte* array; // actual data uintgo len; // number of elements uintgo cap; // allocated number of elements };
數組的slice會建立一份新的數據結構,包含一個指針,一個指針和一個容量數據。如同分割一個字符串,分割數組也不涉及複製操做,它只是新建了一個結構放置三個數據。以下圖:spa
示例中,對[]int{2,3,5,7,11}
求值操做會建立一個包含五個值的數組,並設置x的屬性來描述這個數組。分割表達式x[1:3]
不從新分配內存數據,只寫了一個新的slice結構屬性來引用相同的存儲數據。上例中,長度爲2--只有y[0]和y[1]是有效的索引,可是容量爲4--y[0:4]是一個有效的分割表達式。
由於slice分割操做不須要分配內存,也沒有一般被保存在堆中的slice頭部,這種表示方法使slice操做和在C中傳遞指針、長度對同樣廉價。
其實slice在Go的運行時庫中就是一個C語言動態數組的實現,要增長切片的容量必須建立一個新的、更大容量的切片,而後將原有切片的內容複製到新的切片。在對slice進行append等操做時,可能會形成slice的自動擴容。其擴容時的大小增加規則是:
下面的例子將切片 s
容量翻倍,先建立一個2倍 容量的新切片 t
,複製 s
的元素到 t
,而後將 t
賦值給 s
:
t := make([]byte, len(s), (cap(s)+1)*2) // +1 in case cap(s) == 0 for i := range s { t[i] = s[i] } s = t
循環中複製的操做能夠由 copy 內置函數替代,返回複製元素的數目。此外, copy
函數能夠正確處理源和目的切片有重疊的狀況。
一個常見的操做是將數據追加到切片的尾部。必要的話會增長切片的容量,最後返回更新的切片:
func AppendByte(slice []byte, data ...byte) []byte { m := len(slice) n := m + len(data) if n > cap(slice) { // if necessary, reallocate // allocate double what's needed, for future growth. newSlice := make([]byte, (n+1)*2) copy(newSlice, slice) slice = newSlice } slice = slice[0:n] copy(slice[m:n], data) return slice }
Go提供了一個內置函數 append,也實現了這樣的功能。
func append(s []T, x ...T) []T //append 函數將 x 追加到切片 s 的末尾,而且在必要的時候增長容量。 a := make([]int, 1) // a == []int{0} a = append(a, 1, 2, 3) // a == []int{0, 1, 2, 3}
若是是要將一個切片追加到另外一個切片尾部,須要使用 ...
語法將第2個參數展開爲參數列表。
a := []string{"John", "Paul"} b := []string{"George", "Ringo", "Pete"} a = append(a, b...) // equivalent to "append(a, b[0], b[1], b[2])" // a == []string{"John", "Paul", "George", "Ringo", "Pete"}
因爲切片的零值 nil
用起來就像一個長度爲零的切片,咱們能夠聲明一個切片變量而後在循環 中向它追加數據:
// Filter returns a new slice holding only // the elements of s that satisfy fn() func Filter(s []int, fn func(int) bool) []int { var p []int // == nil for _, v := range s { if fn(v) { p = append(p, v) } } return p }
切片操做並不會複製底層的數組。整個數組將被保存在內存中,直到它再也不被引用。 有時候可能會由於一個小的內存引用致使保存全部的數據。
以下, FindDigits
函數加載整個文件到內存,而後搜索第一個連續的數字,最後結果以切片方式返回。
var digitRegexp = regexp.MustCompile("[0-9]+") func FindDigits(filename string) []byte { b, _ := ioutil.ReadFile(filename) return digitRegexp.Find(b) }
這段代碼的行爲和描述相似,返回的 []byte
指向保存整個文件的數組。由於切片引用了原始的數組, 致使 GC 不能釋放數組的空間;只用到少數幾個字節卻致使整個文件的內容都一直保存在內存裏。要修復整個問題,能夠將須要的數據複製到一個新的切片中:
func CopyDigits(filename string) []byte { b, _ := ioutil.ReadFile(filename) b = digitRegexp.Find(b) c := make([]byte, len(b)) copy(c, b) return c }
使用 append
實現一個更簡潔的版本:
8 func CopyDigitRegexp(filename string) []byte { 7 b,_ := ioutil.ReadFile(filename) 6 b = digitRefexp.Find(b) 5 var c []intb 4 // for _,v := range b{ 3 c =append(c, b) 2 //} 1 return c 0 }
Go有兩個數據結構建立函數:make和new,也是兩種不一樣的內存分配機制。
make和new的基本的區別是new(T)
返回一個*T
,返回的是一個指針,指向分配的內存地址,該指針能夠被隱式地消除引用)。而make(T, args)
返回一個普通的T。一般狀況下,T內部有一些隱式的指針。因此new返回一個指向已清零內存的指針,而make返回一個T類型的結構。更詳細的區別在後面內存分配的學習裏研究。