go 學習筆記之數組還是切片都沒什麼不一樣
- 2019 年 10 月 3 日
- 筆記
上篇文章中詳細介紹了 Go 的基礎語言,指出了 Go 和其他主流的程式語言的差異性,比較側重於語法細節,相信只要稍加記憶就能輕鬆從已有的程式語言切換到 Go 語言的編程習慣中,儘管這種切換可能並不是特別順暢,但多加練習尤其是多多試錯,總是可以慢慢感受 Go 語言之美!
在學習 Go 的內建容器前,同樣的,我們先簡單回顧一下 Go 的基本語言,溫度而知新可以為師矣!
上節知識回顧
如需了解詳情,請於微信公眾號[雪之夢技術驛站]內查看 go 學習筆記之值得特別關注的基礎語法有哪些 文章,覺得有用的話,順手轉發一下唄!
內建類型種類
bool
布爾類型,可選
true|false,默認初始化零值false.
(u)int,(u)int8,(u)int16,(u)int32,(u)int64,uintptr
2^0=1,2^1=2,2^2=4個位元組長度的整型,包括有符號整型和無符號整型以及uintptr類型的指針類型,默認初始化零值0.
byte(uint8),rune(int32),string
byte是最基礎位元組類型,是uint8類型的別名,而rune是Go中的字元類型,是int32的別名.最常用的字元串類型string應該不用介紹了吧?
float32,float64,complex64,complex128
只有
float類型的浮點型,沒有double類型,同樣是以位元組長度來區分,complex64是複數類型,實部和虛部由float32類型複合而成,因此寫作complex64這種形式.
內建類型特點
- 類型轉換隻有顯示轉換,不存在任何形式的隱式類型轉換
不同變數類型之間不會自動進行隱式類型轉換,
Go語言的類型轉換隻有強制的,只能顯示轉換.
- 雖然提供指針類型,但指針本身不能進行任何形式的計算.
指針類型的變數不能進行計算,但是可以重新改變記憶體地址的指向.
- 變數聲明後有默認初始化零值,變數零值視具體類型而定
int類型的變數的初始化零值是0,string類型的初始化零值是空字元串,並不是nil
基本運算符
- 算術運算符沒有
++i和--i
只有
i++和i--這種自增操作,再也不用擔心兩種方式的差異性了!
- 比較運算符
==可以比較數組是否相等
當兩個數組的維度和數組長度相等時,兩個數組可以進行比較,順序完全一致時,結果為
true,其他情況則是false.
- 位運算符新增按位清零運算符
&^
其他主流的程式語言雖然沒有這種操作符,通過組合命令也可以實現類似功能,但既然提供了按位清零運算符,再也不用自己進行組合使用了!
流程式控制制語句
if條件表達式不需要小括弧並支援變數賦值操作
先定義臨時變數並根據該變數進行邏輯判斷,然後按照不同情況進行分類處理,
Go處理這種臨時變數的情況,直接對條件表達式進行增強,這種情況以後會很常見!
if條件表達式內定義的變數作用域僅限於當前語句塊
條件表達式內定義的變數是為了方便處理不同分支的邏輯,既然是臨時變數,出了當前的
if語句塊就無法使用,也變得可以理解.
switch語句可以沒有break,除非使用了fallthrough
switch語句的多個case結尾處可以沒有break,系統會自動進行break處理.
switch條件表達式不限制為常數或整數
和其他主流的程式語言相比,
Go語言的switch條件表達式更加強大,類型也較為寬鬆.
switch條件表達式可以省略,分支邏輯轉向case語言實現.
省略
switch條件表達式,多個case語言進行分支流程式控制制,功能效果和多重if else一樣.
- 省略
switch條件表達式後,每個case條件可以有多個條件,用逗號分隔.
swicth語句本質上是根據不同條件進行相應的流程式控制制,每個case的條件表達式支援多個,更是增強了流程式控制制的能力.
for循環的條件表達式也不需要小括弧,且沒有其他形式的循環.
Go語言只有for循環,沒有while等其他形式的循環.
for循環的初始條件,終止條件和自增表達式都可以省略或者同時省略
條件表達式進行省略後可以實現
while循環的效果,全部省略則是死循環.
函數和參數傳遞
- 函數聲明按照函數名,入參,出參順序定義,並支援多返回值
不論是變數定義還是函數定義,
Go總是和其他主流的程式語言反著來,如果按照輸入輸出的順序思考就會發現,這種定義方式其實挺有道理的.
- 函數有多個返回值時可以給返回值命名,但對調用者而言沒有差別
函數返回多個值時可以有變數名,見名知意方便調用者快速熟悉函數聲明,但調用者並非一定要按照返回值名稱接收調用結果.
- 函數的入參沒有必填參數,可選參數等複雜概念,只支援可變參數列表
可變參數列表和其他主流的程式語言一樣,必須是入參的最後一個.
- 函數參數傳遞只有值傳遞,沒有引用傳遞,即全部需要重新拷貝變數
參數傳遞只有值傳遞,邏輯上更加簡單,但是處理複雜情況時可以傳遞指針實現引用傳遞的效果.
內建容器有哪些
複習了 Go 語言的基礎語法後,開始繼續學習變數類型的承載者也就是容器的相關知識.
承載一類變數最基礎的底層容器就是數組了,大多數高級的容器底層都可以依靠數組進行封裝,所以先來了解一下 Go 的數組有何不同?
數組和切片
- 數組的聲明和初始化
數組的明顯特點就是一組特定長度的連續存儲空間,聲明數組時必須指定數組的長度,聲明的同時可以進行初始化,當然不指定數組長度時也可以使用 ... 語法讓編譯器幫我們確定數組的長度.
func TestArray(t *testing.T) { var arr1 [3]int arr2 := [5]int{1, 2, 3, 4, 5} arr3 := [...]int{2, 4, 6, 8, 10} // [0 0 0] [1 2 3 4 5] [2 4 6 8 10] t.Log(arr1, arr2, arr3) var grid [3][4]int // [[0 0 0 0] [0 0 0 0] [0 0 0 0]] t.Log(grid) }
[3]int指定數組長度為3,元素類型為int,當然也可以聲明時直接賦值[5]int{1, 2, 3, 4, 5},如果懶得指定數組長度,可以用[...]int{2, 4, 6, 8, 10}表示.
- 數組的遍歷和元素訪問
最常見的 for 循環進行遍歷就是根據數組的索引進行訪問,range arr 方式提供了簡化遍歷的便捷方法.
func TestArrayTraverse(t *testing.T) { arr := [...]int{2, 4, 6, 8, 10} for i := 0; i < len(arr); i++ { t.Log(arr[i]) } for i := range arr { t.Log(arr[i]) } for i, v := range arr { t.Log(i, v) } for _, v := range arr { t.Log(v) } }
range arr可以返回索引值和索引項,如果僅僅關心索引項而不在乎索引值的話,可以使用_佔位符表示忽略索引值,如果只關心索引值,那麼可以不寫索引項.這種處理邏輯也就是函數的多返回值順序接收,不可以出現未使用的變數.
- 數組是值類型可以進行比較
數組是值類型,這一點和其他主流的程式語言有所不同,因此相同緯度且相同元素個數的數組可以比較,關於這方面的內容前面也已經強調過,這裡再次簡單回顧一下.
func printArray(arr [5]int) { arr[0] = 666 for i, v := range arr { fmt.Println(i, v) } } func TestPrintArray(t *testing.T) { var arr1 [3]int arr2 := [5]int{1, 2, 3, 4, 5} arr3 := [...]int{2, 4, 6, 8, 10} // [0 0 0] [1 2 3 4 5] [2 4 6 8 10] t.Log(arr1, arr2, arr3) // cannot use arr1 (type [3]int) as type [5]int in argument to printArray //printArray(arr1) fmt.Println("printArray(arr2)") printArray(arr2) fmt.Println("printArray(arr3)") printArray(arr3) // [1 2 3 4 5] [2 4 6 8 10] t.Log(arr2, arr3) }
因為參數傳遞是值傳遞,所以
printArray函數無法更改調用者傳遞的外部函數值,如果想要在函數printArray內部更改傳遞過來的數組內容,可以通過指針來實現,但是有沒有更簡單的做法?
想要在 printArrayByPointer 函數內部修改參數數組,可以通過數組指針的方式,如果有不熟悉的地方,可以翻看上一篇文章回顧查看.
func printArrayByPointer(arr *[5]int) { arr[0] = 666 for i, v := range arr { fmt.Println(i, v) } } func TestPrintArrayByPointer(t *testing.T) { var arr1 [3]int arr2 := [5]int{1, 2, 3, 4, 5} arr3 := [...]int{2, 4, 6, 8, 10} // [0 0 0] [1 2 3 4 5] [2 4 6 8 10] t.Log(arr1, arr2, arr3) fmt.Println("printArrayByPointer(arr2)") printArrayByPointer(&arr2) fmt.Println("printArrayByPointer(arr3)") printArrayByPointer(&arr3) // [666 2 3 4 5] [666 4 6 8 10] t.Log(arr2, arr3) }
修改數組的元素可以通過傳遞數組指針來實現,除此之外,
Go語言中數組還有一個近親slice,也就是切片,它可以實現類似的效果.
- 切片的聲明和初始化
切片和數組非常類似,創建數組時如果沒有指定數組的長度,那麼最終創建的其實是切片並不是數組.
func TestSliceInit(t *testing.T) { var s1 [5]int // [0 0 0 0 0] t.Log(s1) var s2 []int // [] t.Log(s2,len(s2)) }
[]int沒有指定長度,此時創建的是切片,默認初始化零值是nil,並不是空數組!
同理,數組可以聲明並初始化,切片也可以,並且語法也很類似,稍不注意還以為是數組呢!
func TestSliceInitValue(t *testing.T) { var s1 = [5]int{1, 3, 5, 7, 9} // [1 3 5 7 9] t.Log(s1) var s2 = []int{1, 3, 5, 7, 9} // [1 3 5 7 9] t.Log(s2) }
僅僅是沒有指定
[]中的長度,最終創建的結果就變成了切片,真的讓人眼花繚亂!
數組和切片如此相像,讓人不得不懷疑兩者之間有什麼見不得人的勾當?其實可以從數組中得到切片,下面舉例說明:
func TestSliceFromArray(t *testing.T) { arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} // arr = [0 1 2 3 4 5 6 7 8 9] t.Log("arr = ", arr) // arr[2:6] = [2 3 4 5] t.Log("arr[2:6] = ", arr[2:6]) // arr[:6] = [0 1 2 3 4 5] t.Log("arr[:6] = ", arr[:6]) // arr[2:] = [2 3 4 5 6 7 8 9] t.Log("arr[2:] = ", arr[2:]) // arr[:] = [0 1 2 3 4 5 6 7 8 9] t.Log("arr[:] = ", arr[:]) }
arr[start:end]截取數組的一部分得到的結果就是切片,切片的概念也是很形象啊!
和其他主流的程式語言一樣,[start:end] 是一個左閉右開區間,切片的含義也非常明確:
忽略起始索引 start 時,arr[:end] 表示原數組從頭開始直到終止索引 end 的前一位;
忽略終止索引 end 時,arr[ start:] 表示原數組從起始索引 start 開始直到最後一位;
既忽略起始索引又忽略終止索引的情況,雖然不常見但是含義上將應該就是原數組,但是記得類型是切片不是數組喲!
目前為止,我們知道切片和數組很相似,切片相對於數組只是沒有大小,那麼切片和數組的操作上是否一樣呢?
func updateSlice(s []int) { s[0] = 666 } func TestUpdateSlice(t *testing.T) { arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} // arr = [0 1 2 3 4 5 6 7 8 9] t.Log("arr = ", arr) s1 := arr[2:6] // s1 = [2 3 4 5] t.Log("s1 = ", s1) s2 := arr[:6] // s2 = [0 1 2 3 4 5] t.Log("s2 = ", s2) updateSlice(s1) // s1 = [666 3 4 5] t.Log("s1 = ", s1) // arr = [0 1 666 3 4 5 6 7 8 9] t.Log("arr = ", arr) updateSlice(s2) // s2 = [666 1 666 3 4 5] t.Log("s2 = ", s2) // arr = [666 1 666 3 4 5 6 7 8 9] t.Log("arr = ", arr) }
切片竟然可以更改傳遞參數,這一點可是數組沒有做到的事情啊!除非使用數組的指針類型,切片竟然可以輕易做到?除非切片內部是指針,因為參數傳遞只有值傳遞,根本沒有引用傳遞方式!
切片和數組在參數傳遞的表現不同,具體表現為數組進行參數傳遞時無法修改數組,想要想改數組只有傳遞數組指針才行,而切片卻實現了數組的改變!
由於參數傳遞只有值傳遞一種方式,因此推測切片內部肯定存在指針,參數傳遞時傳遞的是指針,所以函數內部的修改才能影響到到函數外部的變數.

slice的內部實現中有三個變數,指針ptr,個數len和容量cap,其中ptr指向真正的數據存儲地址.
正是由於切片這種內部實現,需要特性也好表現形式也罷才使得切換和數組有著千絲萬縷的聯繫,其實這種數據結果就是對靜態數組的擴展,本質上是一種動態數組而已,只不過 Go 語言叫做切片!
切片是動態數組,上述問題就很容易解釋了,參數傳遞時傳遞的是內部指針,因而雖然是值傳遞拷貝了指針,但是指針指向的真正元素畢竟是一樣的,所以切片可以修改外部參數的值.
數組可以在一定程度上進行比較,切片是動態數組,能不能進行比較呢?讓接下來的測試方法來驗證你的猜想吧!

不知道你有沒有猜對呢?切片並不能進行比較,只能與
nil進行判斷.
- 切片的添加和刪除
數組是靜態結構,數組的大小不能擴容或縮容,這種數據結構並不能滿足元素個數不確定場景,因而才出現動態數組這種切片,接下來重點看下切片怎麼添加或刪除元素.
func printSlice(s []int) { fmt.Printf("s = %v, len(s) = %d, cap(s) = %dn", s, len(s), cap(s)) } func TestSliceAutoLonger(t *testing.T) { var s []int // [] t.Log(s) for i := 0; i < 10; i++ { s = append(s, i) printSlice(s) } // [0 1 2 3 ...,98,99] t.Log(s) for i := 0; i < 10; i++ { s = s[1:] printSlice(s) } // [0 1 2 3 ...,98,99] t.Log(s) }
添加元素
s = append(s, i)需要擴容時,每次以2倍進行擴容,刪除元素s[1:]時,遞減縮容.
s = append(s, i) 向切片中添加元素並返回新切片,由於切片是動態數組,當切片內部的數組長度不夠時會自動擴容以容納新數組,擴容前後的內部數組會進行元素拷貝過程,所以 append 會返回新的地址,擴容後的地址並不是原來地址,所以需要用變數接收添加後的切片.
當不斷進行切片重新截取時 s[1:] ,切片存儲的元素開始縮減,個數遞減,容量也遞減.

其實除了基於數組創建切片和直接創建切片的方式外,還存在第三種創建切片的方式,也是使用比較多的方式,那就是 make 函數.
func TestMakeSlice(t *testing.T) { s1 := make([]int,10) // s1 = [0 0 0 0 0 0 0 0 0 0], len(s1) = 10, cap(s1) = 10 t.Logf("s1 = %v, len(s1) = %d, cap(s1) = %d", s1, len(s1), cap(s1)) s2 := make([]int, 10, 32) // s2 = [0 0 0 0 0 0 0 0 0 0], len(s2) = 10, cap(s2) = 32 t.Logf("s2 = %v, len(s2) = %d, cap(s2) = %d", s2, len(s2), cap(s2)) }
通過 make 方式可以設置初始化長度和容量,這是字面量創建切片所不具備的能力,並且這種方式創建的切片還支援批量拷貝功能!
func TestCopySlice(t *testing.T) { var s1 = []int{1, 3, 5, 7, 9} var s2 = make([]int, 10, 32) copy(s2, s1) // s2 = [1 3 5 7 9 0 0 0 0 0], len(s2) = 10, cap(s2) = 32 t.Logf("s2 = %v, len(s2) = %d, cap(s2) = %d", s2, len(s2), cap(s2)) var s3 []int copy(s3, s1) // s3 = [], len(s3) = 0, cap(s3) = 0 t.Logf("s3 = %v, len(s3) = %d, cap(s3) = %d", s3, len(s3), cap(s3)) }
func copy(dst, src []Type) int是切片之間拷貝的函數,神奇的是,只有目標切片是make方式創建的切片才能進行拷貝,不明所以,有了解的小夥伴還請指點一二!
切片的底層結構是動態數組,如果切片是基於數組截取而成,那麼此時的切片從效果上來看,切片就是原數組的一個視圖,對切片的任何操作都會反映到原數組上,這也是很好理解的.
那如果對切片再次切片呢,或者說切片會不會越界,其實都比較簡單了,還是稍微演示一下,重點就是動態數組的底層結構.
func TestSliceOutOfBound(t *testing.T) { arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7} s1 := arr[2:6] // s1 = [2 3 4 5], len(s1) = 4, cap(s1) = 6 t.Logf("s1 = %v, len(s1) = %d, cap(s1) = %d", s1, len(s1), cap(s1)) s2 := s1[3:5] // s2 = [5 6], len(s2) = 2, cap(s2) = 3 t.Logf("s2 = %v, len(s2) = %d, cap(s2) = %d", s2, len(s2), cap(s2)) }
[]只能訪問len(arr)範圍內的元素,[:]只能訪問cap(arr)範圍內的元素,一般而言cap >= len所以某些情況看起來越界,其實並不沒有越界,只是二者的標準不同!
我們知道切片 slice 的內部數據結構是基於動態數組,存在三個重要的變數,分別是指針 ptr,個數 len 和容量 cap ,理解了這三個變數如何實現動態數組就不會掉進切片的坑了!
個數 len 是通過下標訪問時的有效範圍,超過 len 後會報越界錯誤,而容量 cap 是往後能看到的最大範圍,動態數組的本質也是控制這兩個變數實現有效數組的訪問.

因為
s1 = [2 3 4 5], len(s1) = 4, cap(s1) = 6,所以[]訪問切片s1元素的範圍是[0,4),因此最大可訪問到s1[3],而s1[4]已經越界了!

因為
s1 = [2 3 4 5], len(s1) = 4, cap(s1) = 6,所以[:]根據切片s1創建新切片的範圍是[0,6],因此最大可訪問範圍是s1[0:6],而s1[3:7]已經越界!
集合 map
集合是一種鍵值對組成的數據結構,其他的主流程式語言也有類似概念,相比之下,Go 語言的 map 能裝載的數據類型更加多樣化.
- 字面量創建
map換行需保留逗號,
func TestMap(t *testing.T) { m1 := map[string]string{ "author": "snowdreams1006", "website": "snowdreams1006", "language": "golang", } // map[name:snowdreams1006 site:https://snowdreams1006.github.io] t.Log(m1) }
一對鍵值對的結尾處加上逗號
,可以理解,但是最後一個也要有逗號這就讓我無法理解了,Why?
make創建的map和字面量創建的map默認初始化零值不同
func TestMapByMake(t *testing.T) { // empty map m1 := make(map[string]int) // map[] false t.Log(m1, m1 == nil) // nil var m2 map[string]int // map[] true t.Log(m2, m2 == nil) }
make函數創建的map是空map,而通過字面量形式創建的map是nil,同樣的規律也適合於切片slice.
range遍歷map是無序的
func TestMapTraverse(t *testing.T) { m := map[string]string{ "name": "snowdreams1006", "site": "https://snowdreams1006.github.io", } // map[name:snowdreams1006 site:https://snowdreams1006.github.io] t.Log(m) for k, v := range m { t.Log(k, v) } t.Log() for k := range m { t.Log(k) } t.Log() for _, v := range m { t.Log(v) } }
這裡再一次遇到
range形式的遍歷,忽略鍵或值時用_佔位,也是和數組,切片的把遍歷方式一樣,唯一的差別就是map沒有索引,遍歷結果也是無序的!
- 獲取元素時需判斷元素是否存在
func TestMapGetItem(t *testing.T) { m := map[string]string{ "name": "snowdreams1006", "site": "https://snowdreams1006.github.io", } // snowdreams1006 t.Log(m["name"]) // zero value is empty string t.Log(m["author"]) // https://snowdreams1006.github.io if site, ok := m["site"]; ok { t.Log(site) } else { t.Log("key does not exist ") } }
Go語言的map獲取不存在的鍵時,返回的是值對應類型的零值,map[string]string返回的默認零值就是空字元串,由於不會報錯進行強提醒,這也就要求我們調用時多做一步檢查.當鍵值對存在時,第二個返回值返回true,不存在時返回false.
- 刪除鍵值對時用
delete函數
func TestMapDeleteItem(t *testing.T) { m := map[string]string{ "name": "snowdreams1006", "site": "https://snowdreams1006.github.io", } // map[name:snowdreams1006 site:https://snowdreams1006.github.io] t.Log(m) delete(m, "name") // map[site:https://snowdreams1006.github.io] t.Log(m) delete(m, "id") // map[site:https://snowdreams1006.github.io] t.Log(m) }
delete(map,key)用於刪除map的鍵值對,如果想要驗證是否刪除成功,別忘了使用value,ok := m[k]確定是否存在指定鍵值對
- 除
slice,map,func外,其餘類型均可鍵
因為
map是基於哈希表實現,所以遍歷是無序的,另一方面因為slice,map,func不可比較,因為也不能作為鍵.當然若自定義類型struc不包含上述類型,也可以作為鍵,並不要求實現hashcode和equal之類的.
value可以承載函數func類型
func TestMapWithFunValue(t *testing.T) { m := map[int]func(op int) int{} m[1] = func(op int) int { return op } m[2] = func(op int) int { return op * op } m[3] = func(op int) int { return op * op * op } // 1 4 27 t.Log(m[1](1), m[2](2), m[3](3)) }
再一次說明函數是一等公民,這部分會在以後的函數式編程中進行詳細介紹.
沒有 set
Go 的默認類型竟然沒有 set 這種數據結構,這在主流的程式語言中算是特別的存在了!
正如 Go 的循環僅支援 for 循環一樣,沒有 while 循環一樣可以玩出 while 循環的效果,靠的就是增強的 for 能力.
所以,即使沒有 set 類型,基於現有的數據結構一樣能實現 set 效果,當然直接用 map 就可以封裝成 set.
func TestMapForSet(t *testing.T) { mySet := map[int]bool{} mySet[1] = true n := 3 if mySet[n] { t.Log("update", mySet[n]) } else { t.Log("add", mySet[n]) } delete(mySet, 1) }
使用
map[type]bool封裝實現set禁止重複性元素的特性,等到講解到面向對象部分再好好封裝,這裡僅僅列出核心結構.
知識點總結梳理
Go 語言是十分簡潔的,不論是基礎語法還是這一節的內建容器都很好的體現了這一點.
數組作為各個程式語言的基礎數據結構,Go 語言和其他主流的程式語言相比沒有什麼不同,都是一片連續的存儲空間,不同之處是數組是值類型,所以也是可以進行比較的.
這並不是新鮮知識,畢竟上一節內容已經詳細闡述過該內容,這一節的重點是數組的衍生版切片 slice .
因為數組本身是特定長度的連續空間,因為是不可變的,其他主流的程式語言中有相應的解決方案,其中就有不少數據結構的底層是基於數組實現的,Go 語言的 slice 也是如此,因此個人心底里更願意稱其為動態數組!
切片 slice 的設計思路非常簡單,內部包括三個重要變數,包括數組指針 ptr,可訪問元素長度 len 以及已分配容量 cap .
當新元素不斷添加進切片時,總會達到已最大分配容量,此時切片就會自動擴容,反之則會縮容,從而實現了動態控制的能力!
- 指定元素個數的是數組,未指定個數的是切片
func TestArrayAndSlice(t *testing.T) { // array var arr1 [3]int // slice var arr2 []int // [0 0 0] [] t.Log(arr1,arr2) }
- 基於數組創建的切片是原始數組的視圖
func TestArrayAndSliceByUpdate(t *testing.T) { arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} // arr = [0 1 2 3 4 5 6 7 8 9] t.Log("arr = ", arr) s := arr[2:6] // before update s = [2 3 4 5], arr = [0 1 2 3 4 5 6 7 8 9] t.Logf("before update s = %v, arr = %v", s, arr) s[0] = 666 // after update s = [666 3 4 5], arr = [0 1 666 3 4 5 6 7 8 9] t.Logf("after update s = %v, arr = %v", s, arr) }
- 添加或刪除切片元素都返回新切片
func TestArrayAndSliceIncreasing(t *testing.T) { var s []int fmt.Println("add new item to slice") for i := 0; i < 10; i++ { s = append(s, i) fmt.Printf("s = %v, len(s) = %d, cap(s) = %dn", s, len(s), cap(s)) } fmt.Println("remove item from slice") for i := 0; i < 10; i++ { s = s[1:] fmt.Printf("s = %v, len(s) = %d, cap(s) = %dn", s, len(s), cap(s)) } }
[index]訪問切片元素僅僅和切片的len有關,[start:end]創建新切片僅僅和原切片的cap有關
func TestArrayAndSliceBound(t *testing.T) { arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} s1 := arr[5:8] // s1[0] = 5, s1[2] = 7 t.Logf("s1[0] = %d, s1[%d] = %d", s1[0], len(s1)-1, s1[len(s1)-1]) // s1 = [5 6 7], len(s1) = 3, cap(s1) = 5 t.Logf("s1 = %v, len(s1) = %d, cap(s1) = %d", s1, len(s1), cap(s1)) s2 := s1[3:5] // s2[0] = 8, s2[1] = 9 t.Logf("s2[0] = %d, s2[%d] = %d", s2[0], len(s2)-1, s2[len(s2)-1]) // s2 = [8 9], len(s2) = 2, cap(s2) = 2 t.Logf("s2 = %v, len(s2) = %d, cap(s2) = %d", s2, len(s2), cap(s2)) }
- 只有
map沒有set
func TestMapAndSet(t *testing.T) { m := map[string]string{ "name": "snowdreams1006", "site": "https://snowdreams1006.github.io", "lang": "go", } // https://snowdreams1006.github.io if site, ok := m["site"]; ok { t.Log(site) } else { t.Log("site does not exist ") } s := map[string]bool{ "name": true, "site": true, "lang": true, } // Pay attention to snowdreams1006 if _, ok := m["isFollower"]; ok { t.Log("Have an eye on snowdreams1006") } else { s["isFollower"] = true t.Log("Pay attention to snowdreams1006") } }
delete函數刪除集合map鍵值對
func TestMapAndSetByDelete(t *testing.T) { m := map[string]string{ "name": "snowdreams1006", "site": "https://snowdreams1006.github.io", "lang": "go", } delete(m, "lang") // delete lang successfully if _,ok := m["lang"];!ok{ t.Log("delete lang successfully") } }
關於 Go 語言中內建容器是不是都已經 Get 了呢?如果有表述不對的地方,還請指正哈,歡迎一起來公眾號[雪之夢技術驛站]學習交流,每天進步一點點!

