上篇文章中詳細介紹了 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) = %d\n", 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) = %d\n", 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) = %d\n", 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
了呢?如果有表述不對的地方,還請指正哈,歡迎一起來公衆號[雪之夢技術驛站]學習交流,每天進步一點點!