Go語言內置如下這些基礎類型:golang
此外,Go語言也支持如下這些複合類型:數組
golang中,nil只能賦值給指針、channel、func、interface、map或slice類型的變量.緩存
Go 也有基於架構的類型,例如:int、uint 和 uintptr。這些類型的長度都是根據運行程序所在的操做系統類型所決定的:安全
Go 語言中沒有 float 類型。數據結構
與操做系統架構無關的類型都有固定的大小,並在類型的名稱中就能夠看出來:架構
整數:併發
-128 -> 127
)-32768 -> 32767
)-2,147,483,648 -> 2,147,483,647
)-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807
)無符號整數:app
0 -> 255
)0 -> 65,535
)0 -> 4,294,967,295
)0 -> 18,446,744,073,709,551,615
)浮點型(IEEE-754 標準):框架
+- 1e-45 -> +- 3.4 * 1e38
)+- 5 * 1e-324 -> 107 * 1e308
)int 型是計算最快的一種類型。函數
float32 精確到小數點後 7 位,float64 精確到小數點後 15 位。因爲精確度的緣故,你在使用 == 或者 != 來比較浮點數時應當很是當心.
你應該儘量地使用 float64,由於 math 包中全部有關數學運算的函數都會要求接收這個類型。
各種型的最大值最小值在math
包中定義。
類型聲明可在builtin
包中查看。
true、false
var v1 bool v1 = true v2 := (1 == 2) // v2也會被推導爲bool類型
布爾類型不能接受其餘類型的賦值,不支持自動或強制的類型轉換。如下的示例是一些錯誤的用法,會致使編譯錯誤:
var b bool b = 1 // 編譯錯誤 b = bool(1) // 編譯錯誤
如下的用法纔是正確的:
var b bool b = (1!=0) // 編譯正確 fmt.Println("Result:", b) // 打印結果爲Result: true
【注意1】int和int32在Go語言裏被認爲是兩種不一樣的類型,編譯器也不會幫你自動作類型轉換,好比如下的例子會有編譯錯誤:
var value2 int32 value1 := 64 // value1將會被自動推導爲int類型 value2 = value1 // 編譯錯誤
編譯錯誤相似於:
cannot use value1 (type int) as type int32 in assignment。
使用強制類型轉換能夠解決這個編譯錯誤:
value2 = int32(value1) // 編譯經過
固然,開發者在作強制類型轉換時,須要注意數據長度被截短而發生的數據精度損失(好比將浮點數強制轉爲整數)和值溢出(值超過轉換的目標類型的值範圍時)問題
【注意2】兩個不一樣類型的整型數不能直接比較,好比int8類型的數和int類型的數不能直接比較,但各類類型的整型變量均可以直接與字面常量(literal)進行比較,好比:
var i int32 var j int64 i, j = 1, 2 if i == j { // 編譯錯誤 fmt.Println("i and j are equal.") } if i == 1 || j == 2 { // 編譯經過 fmt.Println("i and j are equal.") }
進制
fmt.Println(0x10) // 十六進制 16 fmt.Println(010) // 八進制 8
e
2e3表示2*10^3=2000
前70個已經被緩存起來,可在math.Pow()中查看。
2e3或者2.0e3默認都是float64類型:
var i = 2e3 reflect.TypeOf(i) == float64 var i int = 2e3 reflect.TypeOf(i) == int
int64 和 uint64 的最大位賦值
var big int64 = 1 << 63 fmt.Printf("%b\n", big)
報錯:constant 9223372036854775808 overflows int64
var big uint64 = 1 << 63 fmt.Printf("%b\n", big)
輸出: 1000000000000000000000000000000000000000000000000000000000000000
這多是由於 int64 中符號位也要佔一個 bit。
int佔多少位二進制
在 atoi.go 文件中:
const intSize = 32 << (^uint(0) >> 63)
x << y
左移x >> y
右移x ^ y
異或x & y
與x | y
或^x
取反位運算只能用於整數類型的變量,且需當它們擁有等長位模式時。
%b
是用於表示位的格式化標識符。
【&^】
位清除 &^:將指定位置上的值設置爲 0
n1, n2 := 7, 3 fmt.Printf("%b &^ %b = %b\n", n1, n2, n1&^n2) // 111 &^ 11 = 100 末尾1,2位置零 n1, n2 := 7, 2 fmt.Printf("%b &^ %b = %b\n", n1, n2, n1&^n2) // 111 &^ 10 = 101 末尾2位置零
【^x】
單獨使用取反,結合使用異或。
該運算符與異或運算符一同使用,即 m^x,對於無符號 x 使用「所有位設置爲 1」,對於有符號 x 時使用 m=-1.
var n uint8 = 2 fmt.Printf("^%b = %b\n", n, ^n) // ^10 = 11111101 即 253 var nn uint8 = 0xFF fmt.Printf("%b ^ %b = %b\n", nn, n, nn^n) // 11111111 ^ 10 = 11111101 var n2 int8 = 2 fmt.Printf("^%b = %b\n", n2, ^n2) // ^10 = -11 即 -3 var nn2 int8 = -1 fmt.Printf("%b ^ %b = %b\n", nn2, n2, nn2^n2) // -1 ^ 10 = -11
對於無符號整型,取反至關於與 0xFF 異或
對於有符號整型,取反至關於與 -1 異或。
上面的操做都是以補碼的形式進行的,正數的補碼仍是自身。
取反至關於補碼和 11111111 異或,而後求補碼。
【<<】
3<<2 => 11 << 2 = 1100 -3<<1 => -11 << 1 = -6(不求補碼好像也能夠)
【>>】
var n1 uint8 = 7 var c uint8 = 2 fmt.Printf("%b >> %d = %b\n", n1, c, n1>>c) // 111 >> 2 = 1 var n2 int8 = 7 fmt.Printf("%b >> %d = %b\n", n2, c, n2>>c) // 111 >> 2 = 1 var n3 int8 = -7 fmt.Printf("%b >> %d = %b\n", n3, c, n3>>c) // -111 >> 2 = -2
對於正數,右移時左邊補0.
對於負數,以-7(-0111)爲例,求補碼(1 1001,第一位爲1表示負數,其他位取反+1,或者從右開始遇到第一個1,這個1以前的取反),將補碼右移2位,左邊補1,變成1 1110,再求補碼變成1 0010,即-2。
另外須要注意的是:-1 >> 1 == -1
, 避免陷入死循環。
【位左移常見實現存儲單位的用例】
使用位左移與 iota 計數配合可優雅地實現存儲單位的常量枚舉:
type ByteSize float64 const ( _ = iota // 經過賦值給空白標識符來忽略值 KB ByteSize = 1<<(10*iota) MB GB TB PB EB ZB YB )
【在通信中使用位左移表示標識的用例】
type BitFlag int const ( Active BitFlag = 1 << iota // 1 << 0 == 1 Send // 1 << 1 == 2 Receive // 1 << 2 == 4 ) flag := Active | Send // == 3
標誌位操做
a := 0 a |= 1 << 2 // 0000100: 在bit2設置標誌位 a |= 1 << 6 // 1000100: 在bit6設置標誌位 a = a &^ (1 << 6) // 0000100: 清除bit6標誌位
移位操做右邊的數不能是有符號數
bit := 2 a := 3 << bit fmt.Printf("%b\n", a)
報錯:invalid operation: 3 << bit (shift count type int, must be unsigned integer)
這樣使用:
var bit uint = 2 a := 3 << bit fmt.Printf("%b\n", a) // 1100
Go語言定義了兩個類型float32和float64,其中float32等價於C語言的float類型,float64等價於C語言的double類型
var fvalue1 float32 fvalue1 = 12 fvalue2 := 12.0 // 若是不加小數點,fvalue2會被推導爲整型而不是浮點型
注意!fvalue2默認被推導爲float64
var val float32 val = 3.0 val2 := 12.4 // val2被自動推導爲float64 val = val2 // 錯誤,不能將float64賦給float32 val = float32(val2) // 使用強制轉換
浮點數不是一種精確的表達方式,因此像整型那樣直接用==來判斷兩個浮點數是否相等是不可行的,這可能會致使不穩定的結果。
下面是一種推薦的替代方案:
import "math" // p爲用戶自定義的比較精度,好比0.00001 func IsEqual(f1, f2, p float64) bool{ return math.Fdim(f1, f2) < p }
一般應該優先使用float64類型,由於float32類型的累計計算偏差很容易擴散,而且float32能精確表示的正整數並非很大(譯註:由於float32的有效bit位只有23個,其它的bit位用於指數和符號;當整數大於23bit能表達的範圍時,float32的表示將出現偏差)
var f float32 = 16777216 // 1 << 24 fmt.Println(f == f+1) // "true"!
小數點前面或後面的數字均可能被省略(例如.707或1.)。 很小或很大的數最好用科學計數法書寫,經過e或E來指定指數部分:
const Avogadro = 6.02214129e23 // 阿伏伽德羅常數 const Planck = 6.62606957e-34 // 普朗克常數
for _, n := range []float64{1.4, 1.5, 1.6, 2.0} { fmt.Println(n, "=>", int(n+0.5)) } // 1 // 2 // 2 // 2
複數類型有complex64和complex128
var value1 complex64 // 由2個float32構成的複數類型 value1 = 3.2 + 12i value2 := 3.2 + 12i // value2是complex128類型 value3 := complex(3.2, 12) // value3結果同value2 var value4 complex128 = 3.2 + 12i
real(value1), imag(value1)
複數的一些運算可在math/cmplx包中找到。
使用==或!=進行比較時注意精度。
var str string // 聲明一個字符串變量 str = "Hello world" // 字符串賦值 ch := str[0] // 取字符串的第一個字符 fmt.Printf("The length of \"%s\" is %d \n", str, len(str)) fmt.Printf("The first character of \"%s\" is %c.\n", str, ch)
輸出結果爲:
The length of "Hello world" is 11 The first character of "Hello world" is H.
【字符串嵌套】
var s = "ni 'hao'. " var s = `ni "hao" .`
【字符串修改】
字符串初始化後不能被改變
str := "Hello world" // 字符串也支持聲明時進行初始化的作法 str[0] = 'X' // 編譯錯誤
能夠先將其轉換成[]rune或[]byte,完成後再轉換成string,不管哪一種方式,都會從新分配內存,並複製字節數組。
s := "abc" bs := []byte(s) bs[1] = 'B' println(s, string(bs)) u := "電腦" us := []rune(u) us[1] = '話' println(u, string(us))
輸出:
abc aBc 電腦 電話
【字符串鏈接】
s := "hello" + " world" s := "hello" + " world " + 3 // 錯誤 s := "hello" + " world " + string(31) // 正確,但不會輸出31
+的並非最高效的作法,使用strings.Join()和bytes.Buffer更好些。
【字符串遍歷】
方式一:
str := "hello,世界" for i, n := 0, len(str); i < n; i++ { var ch2 uint8 = str[i] // 類型是byte,即uint8 fmt.Printf("%d = '%c' , ", i, ch2) }
輸出:
0 = 'h' , 1 = 'e' , 2 = 'l' , 3 = 'l' , 4 = 'o' , 5 = ',' , 6 = 'ä' , 7 = '¸' , 8 = '' , 9 = 'ç' , 10 = '' , 11 = '' ,
長度爲12,每一箇中文字符在UTF-8中佔3個字節。
方式二:
str := "hello,世界" for i, ch := range str { fmt.Print(i, "= ") fmt.Print(ch, " ,") // ch的類型是rune fmt.Printf("'%c' .", ch) }
輸出:
0= 104 ,'h' .1= 101 ,'e' .2= 108 ,'l' .3= 108 ,'l' .4= 111 ,'o' .5= 44 ,',' .6= 19990 ,'世' .9= 30028 ,'界' .
【中文字符截取】
strEn := "abcdef" strCn := "中文測試" fmt.Println(strEn[0:3]) // abc fmt.Println(strCn[0:3]) // 中 fmt.Println(string([]rune(strCn)[0:3])) // 中文測 func SubString(str string, begin, length int) (substr string) { // 將字符串的轉換成[]rune rs := []rune(str) lth := len(rs) // 簡單的越界判斷 if begin < 0 { begin = 0 } if begin >= lth { begin = lth } end := begin + length if end > lth { end = lth } // 返回子串 return string(rs[begin:end]) } fmt.Println(SubString("中文測試", 1, 3)) // 文測試 fmt.Println(SubString("abcd", 1, 3)) // bcd
【中文字符串定位】
// 思路:首先經過strings庫中的Index函數得到子串的字節位置,再經過這個位置得到子串以前的字節數組pre, // 再將pre轉換成[]rune,得到[]rune的長度,即是子串以前字符串的長度,也就是子串在字符串中的字符位置 // 這裏用的是string.Index函數,相似的,也能夠寫中文字符串的相似strings中的IndexAny,LastIndex等函數 func UnicodeIndex(str, substr string) int { // 子串在字符串的字節位置 result := strings.Index(str, substr) if result >= 0 { // 得到子串以前的字符串並轉換成[]byte prefix := []byte(str)[0:result] // 將子串以前的字符串轉換成[]rune rs := []rune(string(prefix)) // 得到子串以前的字符串的長度,即是子串在字符串的字符位置 result = len(rs) } return result } fmt.Println(UnicodeIndex("中文測試", "文")) // 1 fmt.Println(UnicodeIndex("abcd", "c")) // 2
【使用"`"定義不作轉義處理的原始字符串,支持跨行】
s := `a b\r\n\x00 c` println(s)
輸出
a b\r\n\x00 c
注意上面第二行是頂行寫的,否則縮進也是會如實反映的。
鏈接跨行字符串時,"+" 必須在上一行末尾,不然致使編譯錯誤
s := "hello, " + "world!" s2 := "hello, " +"World." // invalid operation: + untyped string
【字符串和其餘類型的轉換】
strconv包。
runtime.h
struct String { byte* str; intgo len; };
中文字符串長度
unicode/utf8
包中有一個RuneCountInString()
var buffer bytes.Buffer
或者 new 出一個指針:var r *bytes.Buffer = new(bytes.Buffer)
或者經過函數:func NewBuffer(buf []byte) *Buffer
,這就建立了一個 Buffer 對象而且用 buf 初始化好了;NewBuffer 最好用在從 buf 讀取的時候使用。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")
這種實現方式比使用 += 要更節省內存和 CPU,尤爲是要串聯的字符串數目特別多的時候。
byte和rune
byte(其實是uint8的別名),表明UTF-8字符串的單個字節的值
rune(int32),表明單個Unicode字符
var abc byte abc = 'a' fmt.Print(abc) // 97
若是abc = ‘你’,會提示超出byte的範圍
var abc rune abc = '你' fmt.Print(abc) // 20320
字符數組
string => []byte
[]byte("hello")
[]byte => string
string([]byte)
可以使用的庫:bytes:
bytes.NewBuffer()
rune可作變量名
rune := rune('a') fmt.Println(rune) // 97
十六進制
var ch byte = 65 或 var ch byte = '\x41'
(\x 老是緊跟着長度爲 2 的 16 進制數)
另一種可能的寫法是 \ 後面緊跟着長度爲 3 的十進制數,例如:\377
通常使用格式 U+hhhh 來表示,其中 h 表示一個 16 進制數。其實 rune 也是 Go 當中的一個類型,而且是 int32 的別名。
在書寫 Unicode 字符時,須要在 16 進制數以前加上前綴 \u 或者 \U。
由於 Unicode 至少佔用 2 個字節,因此咱們使用 int16 或者 int 類型來表示。若是須要使用到 4 字節,則會加上 \U 前綴;前綴 \u 則老是緊跟着長度爲 4 的 16 進制數,前綴 \U 緊跟着長度爲 8 的 16 進制數。
var ch int = '\u0041' var ch2 int = '\u03B2' var ch3 int = '\U00101234' fmt.Printf("%d - %d - %d\n", ch, ch2, ch3) // integer fmt.Printf("%c - %c - %c\n", ch, ch2, ch3) // character fmt.Printf("%X - %X - %X\n", ch, ch2, ch3) // UTF-8 bytes fmt.Printf("%U - %U - %U", ch, ch2, ch3) // UTF-8 code point
輸出:
65 - 946 - 1053236 A - β - r 41 - 3B2 - 101234 U+0041 - U+03B2 - U+101234
一些常見的函數見unicode包
[32]byte // 長度爲32的數組,每一個元素爲一個字節 [2*N] struct{ x, y int32} // 複雜類型數組 [1000]*float64 // 指針數組 [3][5]int // 二維數組 [2][2][2]float64 // 等同於[2]([2]([2]float64))
數組長度在定義後就不可更改,在聲明時長度能夠爲一個常量或者一個常量表達式(常量表達式是指在編譯期便可計算結果的表達式)。
【數組長度】
var arr [10]byte len(arr) // 10 var arr [10][23]int fmt.Println(len(arr)) // 10
【初始化】
arr := [5]int{1, 2, 3, 4, 5} fmt.Println(arr[4]) // 5
b := [10]int{1, 2, 3}
聲明瞭一個長度爲10的int數組,其中前三個元素初始化爲一、二、3,其它默認爲0
若是不想寫[5],也可使用[...]代替:
arr := [...]int{1, 2, 3, 4, 5}
也能夠省略,什麼都不寫(這是數組仍是切片?這是切片)
arr := []int{1, 2, 3}
指定索引來初始化
var names = []string{ 1: "a", 2: "b", 4: "d", }
names[0]和names[3]都是""
數組指針
arr1 := new([3]int) fmt.Printf("arr1 type:%T\n", arr1) // arr1 type:*[3]int arr2 := [3]int{} fmt.Printf("arr2 type:%T\n", arr2) // arr2 type:[3]int fmt.Println(arr1, arr2) // &[0 0 0] [0 0 0] arr1[1] = 4 (*arr1)[2] = 1 arr2[1] = 5 fmt.Println(arr1, arr2) // &[0 4 1] [0 5 0]
【遍歷】
方式一:
arr := [5]int{1, 3, 5, 7, 9} for i, n := 0, len(arr); i < n; i++ { fmt.Println(i, "=>", arr[i]) }
方式二:
arr := [5]int{1, 3, 5, 7, 9} for i, v := range arr { fmt.Println(i, "=>", v) }
方式三:(range匿名變量)
for i, v := range [5]int{1, 2, 3, 4} { fmt.Println(i, "=>", v) }
【數組是值類型】
數組是一個值類型(value type)。全部的值類型變量在賦值和做爲參數傳遞時都將產生一次複製動做。若是將數組做爲函數的參數類型,則在函數調用時該參數將發生數據複製。所以,在函數體中沒法修改傳入的數組的內容,由於函數內操做的只是所傳入數組的一個副本。
func main() { arr := [5]int{1, 2, 3, 4, 5} modify(arr) fmt.Println("in main() arr is: ", arr) } func modify(arr [5]int) { arr[0] = 10 fmt.Println("in modify() arr is: ", arr) }
輸出結果:
in modify() arr is: [10 2 3 4 5] in main() arr is: [1 2 3 4 5]
固然,也能夠給函數傳遞一個數組的指針:
func sum(a *[3]float64) (sum float64) { for _, v := range *a { sum += v } return } arr := [...]float64{7.0, 5.4, 9.2} fmt.Println(sum(&arr))
不過,這種風格並不符合Go的語言習慣。相反的,應該使用切片。
count[x] = count[x] * scale 可替換成 count[x] *= scale 這樣能夠省去對變量表達式的重複計算
// 聲明瞭一個二維數組,該數組以兩個數組做爲元素,其中每一個數組中又有4個int類型的元素 doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}} // 上面的聲明能夠簡化,直接忽略內部的類型 easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}} arr := [3][2]int{ {1, 2}, {3, 4}, {5, 6}} // 若是最後的}放到下一行了,則須要使用,({5,6},) fmt.Println(arr[2]) // [5 6] towDimen := [][]string{ []string{"a", "b", "c"}, []string{"d", "e"}, }
數組切片的數據結構能夠抽象爲如下3個變量:
切片持有對底層數組的引用,若是你將一個切片賦值給另外一個,兩者都將引用同一個數組。若是函數接受一個切片參數,那麼其對切片的元素所作的改動,對於調用者是可見的,比如是傳遞了一個底層數組的指針。所以,Read函數能夠接受一個切片參數,而不是一個指針和一個計數;切片中的長度已經設定了要讀取的數據的上限.
注意 絕對不要用指針指向 slice。slice 自己已是一個引用類型,因此它自己就是一個指針!!
【建立切片】
方式一:在已有數組的基礎上建立
arr := [5]int{1, 2, 3, 4, 5} slice := arr[:4] // 前4個元素 0-3 fmt.Println(slice) // [1 2 3 4] slice := arr[1:] // 從1開始到結尾 [2 3 4 5] slice := arr[1:4] // 1-3 [2 3 4] slice := arr[:] // 所有
切片的改變可影響原數組
arr := [3]int{1, 2, 3} slice := arr[:] slice[0] = 5 fmt.Printf("arr=%v, slice=%v", arr, slice) // arr=[5 2 3], slice=[5 2 3]
方式二:直接建立
建立一個初始元素個數爲5的數組切片,元素初始值爲0,cap爲5:
mySlice1 := make([]int, 5)
建立一個初始元素個數爲5的數組切片,元素初始值爲0,並預留10個元素的存儲空間:
mySlice2 := make([]int, 5, 10)
直接建立並初始化包含5個元素的數組切片(len和cap都爲5):
mySlice3 := []int{1, 2, 3, 4, 5}
固然,事實上還會有一個匿名數組被建立出來,只是不須要咱們來操心而已
如下這兩種方式可建立相同的slice:
s1 := make([]int, 5, 10) s2 := new([10]int)[:5] fmt.Printf("s1 type:%T, s2 type:%T\n", s1, s2) // s1 type:[]int, s2 type:[]int fmt.Printf("s1 len:%d,cap:%d, s2 len:%d,cap:%d\n", len(s1), cap(s1), len(s2), cap(s2)) // s1 len:5,cap:10, s2 len:5,cap:10
方法三:基於切片建立切片
oldSlice := []int{1, 2, 3, 4, 5} newSlice := oldSlice[:3] // 基於oldSlice的前3個元素構建新數組切片 fmt.Println(newSlice)
有意思的是,選擇的oldSlicef元素範圍甚至能夠超過所包含的元素個數,好比newSlice能夠基於oldSlice的前6個元素建立,雖然oldSlice只包含5個元素。只要這個選擇的範圍不超過oldSlice存儲能力(即cap()返回的值),那麼這個建立程序就是合法的。newSlice中超出oldSlice元素的部分都會填上0。
然而在這裏 oldSlice 的容量就是5,而 newSlice 的容量也是5,由於這倆用得是同一個數組。
這個和從新分片是同樣的。
由於字符串是純粹不可變的字節數組,它們也能夠被切分紅 slice
s := "hello,世界" s2 := s[6:9] fmt.Println(s2) // 世
【切片的第3個參數】
slice := [...]int{0, 1, 2, 3, 4, 5, 6} fmt.Println(slice) s := slice[1:2:4] fmt.Println(len(s), cap(s)) // 1 3 s1 := slice[1:2] fmt.Println(len(s1), cap(s1)) // 1 6
第3個參數表示容量最大界索引,第三個參數減去第一個參數的差值就是容量。
【查看長度和容量】
slice := []int{1, 2, 3, 4, 5} fmt.Println(len(slice), cap(slice)) // 5 5 slice2 := make([]int, 5, 10) fmt.Println(len(slice2), cap(slice2)) // 5 10
一個 slice s 能夠這樣擴展到它的大小上限:s = s[:cap(s)]
s := make([]int, 5, 10) s = s[:cap(s)] fmt.Println(len(s), cap(s)) // 10,10
【從新分片】
arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0} s := arr[:5] fmt.Println(s) // [1 2 3 4 5] s = arr[5:10] fmt.Println(s) // [6 7 8 9 0]
【新增元素】
slice := []int{1, 2, 3, 4, 5} slice2 := append(slice, 6, 7, 8) fmt.Println(slice) // [1 2 3 4 5] fmt.Println(slice2) // [1 2 3 4 5 6 7 8]
append()是產生了一個新的切片
append()的第二個參數實際上是一個不定參數,咱們能夠按本身需求添加若干個元素,甚至直接將一個數組切片追加到另外一個數組切片的末尾:
slice := []int{1, 2, 3, 4, 5} slice2 := []int{8, 9, 10} slice = append(slice, slice2...) fmt.Println(slice)
第二個參數slice2後面加了三個點,即一個省略號,若是沒有這個省略號的話,會有編譯錯誤,由於按append()的語義,從第二個參數起的全部參數都是待附加的元素。由於slice中的元素類型爲int,因此直接傳遞slice2是行不通的。加上省略號至關於把slice2包含的全部元素打散後傳入。 上述調用等同於:
slice = append(slice, 8, 9, 10)
數組切片會自動處理存儲空間不足的問題。若是追加的內容長度超過當前已分配的存儲空間(即cap()調用返回的信息),數組切片會自動分配一塊足夠大的內存,而後返回指向新數組的切片。
append函數會改變slice所引用的數組的內容,從而影響到引用同一數組的其它slice。 但當slice中沒有剩餘空間(即(cap-len) == 0)時,此時將動態分配新的數組空間。返回的slice數組指針將指向這個空間,而原數組的內容將保持不變;其它引用此數組的slice則不受影響
append interface{}時的坑
var slice []interface{} = make([]interface{}, 0) slice = append(slice, "hello") slice = append(slice, 12) var arr []interface{} = []interface{}{"one", "two"} slice = append(slice, arr...) var arr2 []string = []string{"three", "four"} slice = append(slice, arr2...) // cannot use arr2 (type []string) as type []interface {} in append fmt.Println(slice)
這裏在append arr2時是想讓go作一個隱式轉換,把[]string轉化成[]interface{},但顯然go如今還不支持。
var arr []string = nil arr = append(arr, "hello")
並不會報空指針異常,可能仍是由於類型和值都爲nil時才爲nil,很明顯,這裏的類型不爲nil。
【內容複製】
數組切片支持Go語言的另外一個內置函數copy(),用於將內容從一個數組切片複製到另外一個數組切片。若是加入的兩個數組切片不同大,就會按其中較小的那個數組切片的元素個數進行復制
slice1 := []int{1, 2, 3, 4, 5} slice2 := []int{5, 4, 3} copy(slice2, slice1) // 只會複製slice1的前3個元素到slice2中 fmt.Println(slice2) // [1 2 3] slice2[0], slice2[1], slice2[2] = 7, 8, 9 copy(slice1, slice2) // 只會複製slice2的3個元素到slice1的前3個位置 fmt.Println(slice1) // [7 8 9 4 5]
【刪除】
func (m *MusicManager) Remove(index int) *MusicEntry { if index < 0 || index >= len(m.musics) { return nil } removedMusic := m.musics[index] // 這裏不能加&,不然刪除的時候會被後面的覆蓋,因此須要把元素複製出來而不是隻取地址 // 從切片中刪除 len := len(m.musics) if len == 0 || len == 1 { // 刪除僅有的一個 m.musics = m.musics[0:0] } else if index == 0 { // 以後的長度至少爲2 // 刪除開頭的 m.musics = m.musics[1:] } else if index == len { //最後一個 m.musics = m.musics[:len-1] } else { // 中間的,長度至少爲3 m.musics = append(m.musics[:index], m.musics[index+1:]...) } return &removedMusic }
[start : end]操做並無改變底層的數組,僅僅是改變了開始索引和長度,append操做會覆蓋掉中間的元素,但底層數組仍是沒有改變,只是改變了索引位置和長度。
通常性的代碼:
func DeleteString(slice []string, index int) []string { len := len(slice) if index < 0 || index >= len { return slice } if len == 0 { return slice } if len == 1 { return slice[0:0] } if index == 0 { return slice[1:] } if index == len-1 { return slice[:len-1] } return append(slice[:index], slice[index+1:]...) }
上面代碼有些囉嗦了,下面是簡潔版的:
func DeleteString2(slice []string, index int) []string { if index < 0 || index >= len(slice) { return slice } return append(slice[:index], slice[index+1:]...) }
Go的數組和切片都是一維的。要建立等價的二維數組或者切片,須要定義一個數組的數組或者切片的切片,相似這樣:
type Transform [3][3]float64 // A 3x3 array, really an array of arrays. type LinesOfText [][]byte // A slice of byte slices.
由於切片是可變長度的,因此能夠將每一個內部的切片具備不一樣的長度。這種狀況很常見,正如咱們的LinesOfText例子中:每一行都有一個獨立的長度。
text := LinesOfText{ []byte("Now is the time"), []byte("for all good gophers"), []byte("to bring some fun to the party."), }
有時候是須要分配一個二維切片的,例如這種狀況可見於當掃描像素行的時候。有兩種方式能夠實現。一種是獨立的分配每個切片;另外一種是分配單個數組,爲其 指定單獨的切片們。使用哪種方式取決於你的應用。若是切片們可能會增大或者縮小,則它們應該被單獨的分配以免覆寫了下一行;若是不會,則構建單個分配 會更加有效。做爲參考,這裏有兩種方式的框架。首先是一次一行:
// Allocate the top-level slice. picture := make([][]uint8, YSize) // One row per unit of y. // Loop over the rows, allocating the slice for each row. for i := range picture { picture[i] = make([]uint8, XSize) }
而後是分配一次,被切片成多行:
// Allocate the top-level slice, the same as before. picture := make([][]uint8, YSize) // One row per unit of y. // Allocate one large slice to hold all the pixels. pixels := make([]uint8, XSize*YSize) // Has type []uint8 even though picture is [][]uint8. // Loop over the rows, slicing each row from the front of the remaining pixels slice. for i := range picture { picture[i], pixels = pixels[:XSize], pixels[XSize:] }
key能夠爲任何定義了等於操做符的類型,例如整數,浮點和複數,字符串,指針,接口(只要其動態類型支持等於操做),結構體和數組。切片不能 做爲map的key,由於它們沒有定義等於操做。和切片相似,map持有對底層數據結構的引用。若是將map傳遞給函數,其對map的內容作了改變,則這 些改變對於調用者是可見的。
key能夠是任意數據類型,只要該類型可以用==來進行比較
map和其餘基本型別不一樣,它不是thread-safe,在多個go-routine存取時,必須使用mutex lock機制
【變量聲明】
var person map[string] string
[]內是鍵的類型,後面是值類型
【建立】
person = make(map[string]string)
聲明加+建立:
var person map[string]string = make(map[string]string) person := make(map[string]string)
指定該map的初始存儲能力:
person = make(map[string]string, 100)
建立並初始化map:
var person map[string]string person = map[string]string{ "a": "haha", "b": "ni", // 最後的逗號是必須的 }
【元素賦值/添加元素】
var person map[string]string = make(map[string]string) person["1"] = "abc"
m := make(map[string]int) m["a"]++
不用擔憂map在沒有當前的key時就對其進行++操做會有什麼問題,由於go語言在碰到這種狀況時,會自動將其初始化爲0,而後再進行操做。
【元素刪除】
delete(person, "1")
若是傳入的map是nil,則報錯,若是鍵不存在,則什麼都不發生
【元素查找】
value, ok := person["2"] if ok { fmt.Println(value) } else { fmt.Println("does not find!") }
或者:
if value, ok := person["2"]; ok { fmt.Println(value) } else { fmt.Println("does not find!") }
即使是nil也是能夠查找的:
var m map[string]int = nil if v, ok := m["a"]; ok { fmt.Println(v) } else { fmt.Println("!ok") } // 輸出!ok
map中的元素不會出現nil的現象(很神奇):
func main() { m := make(map[string]entry) fmt.Println(m["a"].name == "") // true } type entry struct { name string }
m[「a」]返回的並非nil,而是{},若是map的元素是指針,則是nil
m := make(map[string]*entry) fmt.Println(m["a"] == nil) // true
【遍歷】
for k, v := range person { fmt.Println("key=", k, "value=", v) }
map也是一種引用類型,若是兩個map同時指向一個底層,那麼一個改變,另外一個也相應的改變:
m := make(map[string]string) m["Hello"] = "Bonjour" m1 := m m1["Hello"] = "Salut" // 如今m["hello"]的值已是Salut了
【清空map】
for k, _ := range m { delete(m, k) }
或者從新賦值:
m = make(map[string]string)
【線程安全的map】
【map中的對象是個拷貝問題】
直接對map對象使用[]操做符得到的對象不能直接修改狀態:
type Person struct { age int } m := map[string]Person{"c": {10}} m["c"].age = 100 // 編譯錯誤:cannot assign to m["c"].age
經過查詢map得到的對象是個拷貝,對此對象的修改不影響原有對象的狀態:
type Person struct { age int } m := map[string]Person{"c": {10}} p := m["c"] p.age = 20 fmt.Println(p.age) // 20 fmt.Println(m["c"].age) // 10
解決方法
type Person struct { age int } m := map[string]*Person{"c": {10}} p := m["c"] p.age = 20 fmt.Println(p.age) // 20 fmt.Println(m["c"].age) // 20
type Person struct { age int } m := map[string]Person{"c": {10}} p := m["c"] p.age = 20 fmt.Println(p.age) // 20 m["c"] = p fmt.Println(m["c"].age) // 20
【分拆map】
分拆map,提升併發能力
下面這張圖來源於Russ Cox Blog中一篇介紹Go數據結構的文章,你們能夠看到這些基礎類型底層都是分配了一塊內存,而後存儲了相應的值。
var p *int = new(int) *p = 5 fmt.Println(*p, p) // 5 0xc0820001d0 q := 6 var pq *int = &q fmt.Println(*pq, pq) // 6 0xc082000200
const i = 5 ptr := &i //error: cannot take the address of i ptr2 := &10 //error: cannot take the address of 10
package main func main() { var p *int = nil *p = 0 } // in Windows: stops only with: <exit code="-1073741819" msg="process crashed"/> // runtime error: invalid memory address or nil pointer dereference
x := 0x12345678 p := unsafe.Pointer(&x) // *int -> Pointer n := (*[4]byte)(p) // Pointer -> *[4]byte, 3,5也能夠 for i := 0; i < len(n); i++ { fmt.Printf("%X ", n[i]) }
輸出:
78 56 34 12
func test() *int { x := 100 return &x // 在堆上分配x內存,但在內聯時,也能夠直接分配在目標棧 }
d := struct { s string x int }{"abc", 100} p := uintptr(unsafe.Pointer(&d)) // *struct -> Pointer -> uintptr p += unsafe.Offsetof(d.x) // uintptr + offset p2 := unsafe.Pointer(p) // uintptr -> Pointer px := (*int)(p2) // Pointer -> *int *px = 200 // d.x = 200 fmt.Printf("%#v\n", d)
輸出:
struct { s string; x int }{s:"abc", x:200}
注意:GC 把 uintptr 當成普通整數對象,它沒法阻止"關聯"對象被回收。