在Go語言中,數據類型分為**靜態類型**和**底層類型**,感覺底層類型是golang實現的時候所使用的C語言的類型,而靜態類型僅僅是針對于go語言本身而言所定義好的類型。具體的信息可以查看$
GOROOT/src/runtime/runtime.h 可以看到golang中的byte類型,通過`typedef uint8 byte`來定義,這樣在golang中直接使用反射
package main
import (
"fmt"
"reflect"
)
func main() {
var b byte = 'D'
fmt.Println(reflect.TypeOf(b))
}
//output: uint8
這里通過反射得到的是uint8,8個bit表示的無符號整數類型。
string
可以看到string類型的結構體就是
struct String
{
byte* str;
intgo len;
}
字符數組存儲實際的數據,len存儲長度。通過結構體就可以看到,定義好string變量之后是不能動態增加的,因為len已經寫入進去了。
strconv golang中string與基本數據類型之間的轉化
fmt.sprintf 函數也可以將interface類型轉化為string類型
http://blog.csdn.NET/siddontang/article/details/23541587
string之間的類型的比較 strings.Equal??
slice
slice為了實現動態增長,又多添加了一個元素cap,這個元素表示已經分配的元素,就是相當于總的空間,len表示對于用戶使用而言這個slice的實際長度。這個和JAVA中的Arraylist感覺有點類似,就是先分配一個空間,之后看空間不夠的話,再動態擴展,但是擴展的過程對使用者來說是不可見的:
struct Slice
{
byte* array;
uintgo len;
uintgo cap;
}
注意slice的第一個參數是一個byte指針,實際上指向的就是底層數組的對應的位置。
這里補充一點,關于初始化的問題,make函數可以用于對slice、map 以及 channel對象進行初始化操作,這三個對象的背后使用了必須要初始化的結構,比如對于slice , 要是不初始化的話,各個值都是nil 顯然是沒沒法使用的。關于make與new的區別可以參考這個博客
下面是一個slice和string轉化的例子,可以用來更好地了解slice與string以及底層數組的關系,不同的生成slice的方式,內部的操作是不同的。
package main
import (
"fmt"
"unsafe"
)
func main() {
//example 1
var slice []int32 = make([]int32, 5, 10)
//p是指向slice類型的一個指針
//這里是模擬一個slice類型 然后轉化過來
p := (*struct {
array uintptr
len int
cap int
})(unsafe.Pointer(&slice))
//para v will print the go type
//If the value is a struct,
//the %+v variant will include the struct’s field names
fmt.Printf("output:%+vn", p)
//example 2
//聲明數組的時候 沒有顯式指定數組的長度 而是通過特殊的標記 ...
//告訴編譯器 讓編譯器去自動地計算
var array = [...]int32{1, 2, 3, 4, 5}
//數組進行截取 轉化為slice 此時自動給len 和 cap 賦了值
var sliceb = array[2:4]
fmt.Printf("before modify: array=%+v, slice = %+vn", array, sliceb)
//此時 slice 中用的底層數組和 array 是同一個
//此時 slice[0]的位置 指向的實際是底層數組中 元素3所在的位置
sliceb[0] = 678
fmt.Printf("after modify: array=%+v, slice = %+vn", array, sliceb)
//example 3
//注意 如果使用的是Append方式生成新的slice
//就不會有類似的效果 因為采用append方式會分配新的底層數組
array = [...]int32{1, 2, 3, 4, 5}
var slicec = array[1:3]
slicec = append(slicec, 6, 7, 8)
//可以對比這次結果 發現 兩次array的輸出是一樣的 并沒有因為 slice的修改而對array造成影響
fmt.Printf("before modify: array=%+v, slice = %+vn", array, sliceb)
slicec[0] = 123
fmt.Printf("after modify: array=%+v, slice = %+vn", array, sliceb)
//注意 從slice 生成slice 的時候原理也是類似的 直接用slicea=sliceb[para1:para2]的語法
//使用的是同一個底層的數組 要是通過append方式 則會重新分配底層數組
}
/* 輸出:
output:before modify: array=[1 2 3 4 5], slice = [3 4]
after modify: array=[1 2 678 4 5], slice = [678 4]
before modify: array=[1 2 3 4 5], slice = [3 4]
after modify: array=[1 2 3 4 5], slice = [3 4]
*/
map
map類型、slice類型、以及通道類型,都是引用類型,引用類型就是說這個結構總會持有一個指針,保持對底層某個結構的引用。作為對比,數組類型就是值類型。對于引用類型,在使用的時候,具體聲明了之后,還要進行初始化,只有這樣才能建立起和底層數據結構之間的聯系。使用字典的時候,要不就直接聲明+初始化,要不就聲明之后使用make初始化。
//直接聲明加賦值
mb := map[int]string{1: "hello", 2: "go"}
//直接聲明并初始化
m := make(map[string]int)
之后就可以直接使用了。
array
array定義的時候,通過[n]類型進行定義,可以在定義的時候就把值賦進去,比如[3]strng{"a","b","c"} 還可以在[]中使用...標記,這樣編譯器會自動識別數組的長度,并且進行初始化。
interface以及類型轉化
接口類型的實現比較復雜,整理出幾條關于接口的要點,實際中,關于斷言表達式的地方總是用的不太好。這個帖子關于斷言說的比較好。這個文章介紹也比較好。
go 語言是編譯型的,有一次寫了這樣的代碼 :
containerpid = fmt.Sprintf("%d", containerpid)
之后是對containerpid進行一些字符串的操作,比如字符串的拼接,在編譯的時候就會報如下的錯誤:
invalid operation: "teststring" + containerpid (mismatched types string and interface {})
主要原因是因為go并不是那種動態執行的語言,因為是要先編譯好再執行,在編譯器看來,這個contaierid還是沒有轉化成strig類型(??) 除非不使用同名的變量,另外新起一個變量名字,這樣編譯時才能通過。
(1)普通類型向接口類型的轉化是隱式的,接口類型向普通類型轉換需要類型斷言。由于interface{}包含了0個方法,所以任何類型都實現了interface{}接口,這就是為什么可以將任意類型值賦值給interface{}類型的變量,包括nil,下面是隱式轉化的例子:
package main
import (
"fmt"
"reflect"
)
func main() {
var val interface{} = "hello"
fmt.Println(reflect.TypeOf(val)) //output: string
fmt.Println(val)
val = []byte{'a', 'b', 'c'}
fmt.Println(reflect.TypeOf(val)) //output: string
fmt.Println(val)
}
(2)在顯式的類型轉化中,一種方式是使用comma-ok的斷言語法,由于golang中不像java哪有那樣采用泛型聲明的方式,因此好多時候不能確定這個類型到底實現了哪些接口,于是可以采用斷言表達式,注意 x.(T) 這樣的斷言類型轉化,x 必須是一個接口類型的變量,T 是一個對應的實際的類型,比如下面這個程序,生成一個interface{}類型的數組,之后通過隱式轉化,可以往里面放入任何類型的變量,之后還可以再使用用斷言表達式檢測對應的類型。
package main
import (
"fmt"
)
type Test []interface{}
func main() {
test := make(Test, 5)
test[0] = "a"
test[1] = 2
test[2] = true
test[3] = []byte("test")
for index, element := range test {
if value, ok := element.(string); ok {
fmt.Printf("test[%d] is type of string the value is %vn", index, value)
} else if value, ok := element.([]byte); ok {
fmt.Printf("test[%d] is type of string the []byte is %vn", index, value)
}
}
}
(3)在使用實例實現interface的時候,還要注意值方法和指針方法的區別。interface這里的使用,多多少少可以看出一些動態語言的設計風格,就是所謂的"鴨子類型",即不是通過顯式的指明繼承自某個接口或者類,而是由當前類型實現方法的屬性集合來決定。理解上就是,感覺比靜態的類型逼格高一點,總之我的interface就寫在那里,你要是想繼承我,使用我的方法,就把這些方法實現了就行,并不要求你顯示的聲明,繼承自我,怎樣怎樣。有些動態類型的語言在運行期才能進行類型的語法檢測,go語言在編譯期間就可以檢測。比如下面這個例子:
package main
import (
"fmt"
"reflect"
"sort"
)
type Sortable interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
type SortStringA [3]string
type SortStringB [3]string
func (self SortStringA) Len() int {
return len(self)
}
func (self SortStringA) Less(i, j int) bool {
return self[i] < self[j]
}
func (self SortStringA) Swap(i, j int) {
self[i], self[j] = self[j], self[i]
}
func (self SortStringA) Sort() {
sort.Sort(self)
}
func (self *SortStringB) Len() int {
return len(self)
}
func (self *SortStringB) Less(i, j int) bool {
return self[i] < self[j]
}
func (self *SortStringB) Swap(i, j int) {
self[i], self[j] = self[j], self[i]
}
func (self *SortStringB) Sort() {
//調用sort包中的 Sort方法 傳入的參數只要是一個實現了 sort.interface的類型的實例即可
sort.Sort(self)
}
func main() {
sa := SortStringA{"2", "3", "1"}
sb1 := &SortStringB{"2", "3", "1"}
sb2 := SortStringB{"2", "3", "1"}
fmt.Println(reflect.TypeOf(sa))
sorta, ok := interface{}(sa).(Sortable)
fmt.Println(reflect.TypeOf(sorta))
fmt.Println(ok) //output:true
sa.Sort()
fmt.Printf("SortStringA after sort %v:n", sa)
sort.Sort(sa)
fmt.Printf("SortStringA after sort %v:n", sa)
//在golang 的源碼包中
fmt.Println(reflect.TypeOf(sb1))
sort.Sort(sb1)
sorted := sort.IsSorted(sb1)
fmt.Printf("sb1 (type:SortStringB) after sort %v :, is sorted : %v n", *sb1, sorted)
sb2.Sort()
fmt.Printf("sb2 (type:SortStringB) after sort : %vn", sb2)
}
/*output:
main.SortStringA
main.SortStringA
true
SortStringA after sort [2 3 1]:
SortStringA after sort [2 3 1]:
*main.SortStringB
sb1 (type:SortStringB) after sort [1 2 3] :, is sorted : true
sb2 (type:SortStringB) after sort : [1 2 3]
感覺這一段代碼還是能夠說明一些問題的:
- 首先定義了一個sortstable的 接口 這個接口中有三個方法,這三個方法同sort包中的基本類型sort.interface中定義的三個方法是一樣的,之后分別用值方法與指針方法對這三個方法進行了實現。
- 可以看到,采用值方法時,因為方法調用的時候,采用的是指傳遞,原來的變量中sa的數據并沒有排序。同時還測試了一下斷言表達式,通過comma-ok的表達式,sa類型可以轉化為sortable類型,這里體現了其動態性,但是采用反射輸出的時候,還顯示的是其本身聲明時候的類型main.SortStringA,這里又體現了其強靜態類型(類型一但確定就不再改變),兩者結合起來使Go語言在保持強靜態類型的安全和高效的同時,也能靈活安全地在不同相容類型之間轉換。(這個文章講一些interface原理性的內容,比較好)注意里面的典型的interface+switch+斷言的使用方式。
- 由于sortable中的三個方法和sort.interface中定義的三個方法一樣,因此sa sb相當于都實現了sort.interface方法,可以直接調用sort包中的函數。但是要注意,具體實現的時候,前一個是SortStringA本身實現了三個方法,后一個是SortStringB指針實現了三個方法,因此 SortStringB指針類型實現了 sort.Interface 調用的時候傳遞過去的要是指針才行,否則編譯報錯。比如直接sort.Sort(sb2)會報錯
SortStringB does not implement sort.Interface (Len method has pointer receiver)