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