雖然golang是用C實現的,而且被稱爲下一代的C語言,可是golang跟C的差異仍是很大的。它定義了一套很豐富的數據類型及數據結構,這些類型和結構或者是直接映射爲C的數據類型,或者是用C struct來實現。瞭解golang的數據類型和數據結構的底層實現,將有助於咱們更好的理解golang並寫出質量更好的代碼。html
基礎類型golang
源碼在:$GOROOT/src/pkg/runtime/runtime.h 。咱們先來看下基礎類型:算法
?數組
1數據結構 2app 3函數 4測試 5ui 6spa 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/* * basic types */ typedef signed char int8; typedef unsigned char uint8; typedef signed short int16; typedef unsigned short uint16; typedef signed int int32; typedef unsigned int uint32; typedef signed long long int int64; typedef unsigned long long int uint64; typedef float float32; typedef double float64;
#ifdef _64BIT typedef uint64 uintptr; typedef int64 intptr; typedef int64 intgo; // Go's int typedef uint64 uintgo; // Go's uint #else typedef uint32 uintptr; typedef int32 intptr; typedef int32 intgo; // Go's int typedef uint32 uintgo; // Go's uint #endif
/* * defined types */ typedef uint8 bool; typedef uint8 byte; |
int八、uint八、int1六、uint1六、int3二、uint3二、int6四、uint6四、float3二、float64分別對應於C的類型,這個只要有C基礎就很容易看得出來。uintptr和intptr是無符號和有符號的指針類型,而且確保在64位平臺上是8個字節,在32位平臺上是4個字節,uintptr主要用於golang中的指針運算。而intgo和uintgo之因此不命名爲int和uint,是由於int在C中是類型名,想必uintgo是爲了跟intgo的命名對應吧。intgo和uintgo對應golang中的int和uint。從定義能夠看出int和uint是可變大小類型的,在64位平臺上佔8個字節,在32位平臺上佔4個字節。因此若是有明確的要求,應該選擇int3二、int64或uint3二、uint64。byte類型的底層類型是uint8。能夠看下測試:
1 2 3 4 5 6 7 8 9 10 11 |
package main
import ( "fmt" "reflect" )
func main() { var b byte = 'D' fmt.Printf("output: %v\n", reflect.TypeOf(b).Kind()) } |
1 2 3 4 |
$ cd $GOPATH/src/basictype_test $ go build $ ./basictype_test output: uint8 |
數據類型分爲靜態類型和底層類型,相對於以上代碼中的變量b來講,byte是它的靜態類型,uint8是它的底層類型。這點很重要,之後常常會用到這個概念。
rune類型
rune是int32的別名,用於表示unicode字符。一般在處理中文的時候須要用到它,固然也能夠用range關鍵字。
string類型
string類型的底層是一個C struct。
1 2 3 4 5 |
struct String { byte* str; intgo len; }; |
成員str爲字符數組,len爲字符數組長度。golang的字符串是不可變類型,對string類型的變量初始化意味着會對底層結構的初始化。至於爲何str用byte類型而不用rune類型,這是由於golang的for循環對字符串的遍歷是基於字節的,若是有必要,能夠轉成rune切片或使用range來迭代。咱們來看個例子:
$GOPATH/src
----basictype_test
--------main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package main
import ( "fmt" "unsafe" )
func main() { var str string = "hi, 陳一回~" p := (*struct { str uintptr len int })(unsafe.Pointer(&str))
fmt.Printf("%+v\n", p) } |
1 2 3 4 |
$ cd $GOPATH/src/basictype_test $ go build $ ./basictype_test output: &{str:135100456 len:14} |
內建函數len對string類型的操做是直接從底層結構中取出len值,而不須要額外的操做,固然在初始化時必需同時初始化len的值。
slice類型
slice類型的底層一樣是一個C struct。
1 2 3 4 5 6 |
struct Slice { // must not move anything byte* array; // actual data uintgo len; // number of elements uintgo cap; // allocated number of elements }; |
包括三個成員。array爲底層數組,len爲實際存放的個數,cap爲總容量。使用內建函數make對slice進行初始化,也能夠相似於數組的方式進行初始化。當使用make函數來對slice進行初始化時,第一個參數爲切片類型,第二個參數爲len,第三個參數可選,若是不傳入,則cap等於len。一般傳入cap參數來預先分配大小的slice,避免頻繁從新分配內存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package main
import ( "fmt" "unsafe" )
func main() { var slice []int32 = make([]int32, 5, 10) p := (*struct { array uintptr len int cap int })(unsafe.Pointer(&slice))
fmt.Printf("output: %+v\n", p) } |
1 2 3 4 |
$ cd $GOPATH/src/basictype_test $ go build $ ./basictype_test output: &{array:406958176 len:5 cap:10} |
因爲切片指向一個底層數組,而且能夠經過切片語法直接從數組生成切片,因此須要瞭解切片和數組的關係,不然可能就會不知不覺的寫出有bug的代碼。好比有以下代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package main
import ( "fmt" )
func main() { var array = [...]int32{1, 2, 3, 4, 5} var slice = array[2:4] fmt.Printf("改變slice以前: array=%+v, slice=%+v\n", array, slice) slice[0] = 234 fmt.Printf("改變slice以後: array=%+v, slice=%+v\n", array, slice) } |
1 2 3 4 5 |
$ cd $GOPATH/src/basictype_test $ go build $ ./basictype_test 改變slice以前: array=[1 2 3 4 5], slice=[3 4] 改變slice以後: array=[1 2 234 4 5], slice=[234 4] |
您能夠清楚的看到,在改變slice後,array也被改變了。這是由於slice經過數組建立的切片指向這個數組,也就是說這個slice的底層數組就是這個array。所以很顯然,slice的改變其實就是改變它的底層數組。固然若是刪除或添加元素,那麼len也會變化,cap可能會變化。
那這個slice是如何指向array呢?slice的底層數組指針指向array中索引爲2的元素(由於切片是經過array[2:4]來生成的),len記錄元素個數,而cap則等於len。
之因此說cap可能會變,是由於cap表示總容量,添加或刪除操做不必定會使總容量發生變化。咱們接着再來看另外一個例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package main
import ( "fmt" )
func main() { var array = [...]int32{1, 2, 3, 4, 5} var slice = array[2:4] slice = append(slice, 6, 7, 8) fmt.Printf("改變slice以前: array=%+v, slice=%+v\n", array, slice) slice[0] = 234 fmt.Printf("改變slice以後: array=%+v, slice=%+v\n", array, slice) } |
1 2 3 4 5 |
$ cd $GOPATH/src/basictype_test $ go build $ ./basictype_test 改變slice以前: array=[1 2 3 4 5], slice=[3 4 6 7 8] 改變slice以後: array=[1 2 3 4 5], slice=[234 4 6 7 8] |
通過append操做以後,對slice的修改並未影響到array。緣由在於append的操做令slice從新分配底層數組,因此此時slice的底層數組再也不指向前面定義的array。
可是很顯然,這種規則對從切片生成的切片也是一樣的,請看代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package main
import ( "fmt" )
func main() { var slice1 = []int32{1, 2, 3, 4, 5} var slice2 = slice1[2:4] fmt.Printf("改變slice2以前: slice1=%+v, slice2=%+v\n", slice1, slice2) slice2[0] = 234 fmt.Printf("改變slice2以後: slice1=%+v, slice2=%+v\n", slice1, slice2) } |
1 2 3 4 5 |
$ cd $GOPATH/src/basictype_test $ go build $ ./basictype_test 改變slice2以前: slice1=[1 2 3 4 5], slice2=[3 4] 改變slice2以後: slice1=[1 2 234 4 5], slice2=[234 4] |
slice1和slice2共用一個底層數組,修改slice2的元素致使slice1也發生變化。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package main
import ( "fmt" )
func main() { var slice1 = []int32{1, 2, 3, 4, 5} var slice2 = slice1[2:4] fmt.Printf("改變slice2以前: slice1=%+v, slice2=%+v\n", slice1, slice2) slice2 = append(slice2, 6, 7, 8) fmt.Printf("改變slice2以後: slice1=%+v, slice2=%+v\n", slice1, slice2) } |
1 2 3 4 5 |
$ cd $GOPATH/src/basictype_test $ go build $ ./basictype_test 改變slice2以前: slice1=[1 2 3 4 5], slice2=[3 4] 改變slice2以後: slice1=[1 2 3 4 5], slice2=[3 4 6 7 8] |
而append操做可令slice1或slice2從新分配底層數組,所以對slice1或slice2執行append操做都不會相互影響。
接口類型
接口在golang中的實現比較複雜,在$GOROOT/src/pkg/runtime/type.h中定義了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
struct Type { uintptr size; uint32 hash; uint8 _unused; uint8 align; uint8 fieldAlign; uint8 kind; Alg *alg; void *gc; String *string; UncommonType *x; Type *ptrto; }; |
在$GOROOT/src/pkg/runtime/runtime.h中定義了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
struct Iface { Itab* tab; void* data; }; struct Eface { Type* type; void* data; }; struct Itab { InterfaceType* inter; Type* type; Itab* link; int32 bad; int32 unused; void (*fun[])(void); }; |
interface其實是一個結構體,包括兩個成員,一個是指向數據的指針,一個包含了成員的類型信息。Eface是interface{}底層使用的數據結構。由於interface中保存了類型信息,因此能夠實現反射。反射其實就是查找底層數據結構的元數據。完整的實如今:$GOROOT/src/pkg/runtime/iface.c 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package main
import ( "fmt" "unsafe" )
func main() { var str interface{} = "Hello World!" p := (*struct { tab uintptr data uintptr })(unsafe.Pointer(&str))
fmt.Printf("%+v\n", p) } |
1 2 3 4 |
$ cd $GOPATH/src/basictype_test $ go build $ ./basictype_test output: &{tab:134966528 data:406847688} |
map類型
golang的map實現是hashtable,源碼在:$GOROOT/src/pkg/runtime/hashmap.c 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
struct Hmap { uintgo count; uint32 flags; uint32 hash0; uint8 B; uint8 keysize; uint8 valuesize; uint16 bucketsize;
byte *buckets; byte *oldbuckets; uintptr nevacuate; }; |
測試代碼以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
package main
import ( "fmt" "unsafe" )
func main() { var m = make(map[string]int32, 10) m["hello"] = 123 p := (*struct { count int flags uint32 hash0 uint32 B uint8 keysize uint8 valuesize uint8 bucketsize uint16
buckets uintptr oldbuckets uintptr nevacuate uintptr })(unsafe.Pointer(&m))
fmt.Printf("output: %+v\n", p) } |
1 2 3 4 |
$ cd $GOPATH/src/basictype_test $ go build $ ./basictype_test output: &{count:407032064 flags:0 hash0:134958144 B:192 keysize:0 valuesize:64 bucketsize:30063 buckets:540701813 oldbuckets:0 nevacuate:0} |
golang的坑仍是比較多的,須要深刻研究底層,不然很容易掉坑裏。
參考資料:
http://blog.csdn.net/sryan/article/details/46514883