Go - 容器類型

常量

const name [type] = value

用於存儲不會改變的數據,常量是在編譯時被創建的,即使定義在函數內部也是如此,並且只能是布爾型、數字型(整數型、浮點型和複數)和字符串型。由於編譯時的限制,定義常量的表達式必須爲能被編譯器求值的常量表達式。

func t1() {
	const (
		e  = 2.7182818
		pi = 3.1415926
		a=1
		b
		c=2
		d
	)
	fmt.Println(a,b,c,d)	//1 1 2 2
}

//iota 常量生成器
func iotaTest()  {
	type Weekday int		//定義一個命名類型
	const(
		a Weekday = iota
		b
		c
		d Weekday = 10
		e
		f
		g Weekday = iota
		h
		i
	)

	fmt.Println(a,b,c,d,e,f,g,h,i)	//0 1 2 10 10 10 6 7 8
}

無類型常量 編譯器爲這些沒有明確的基礎類型的數字常量提供比基礎類型更高精度的算術運算,可以認爲至少有 256bit 的運算精度。這裏有六種未明確類型的常量類型,分別是無類型的布爾型、無類型的整數、無類型的字符、無類型的浮點數、無類型的複數、無類型的字符串。

var x float32 = math.Pi
var y float64 = math.Pi
var z complex128 = math.Pi

當然Go語言中的變量、函數、常量名稱的首字母也可以大寫,如果首字母大寫,則表示它可以被其它的包訪問(類似於 Java 中的 public);如果首字母小寫,則表示它只能在本包中使用 (類似於 Java 中 private)。

container

數組

var 數組變量名 [元素數量]Type

  • 元素數量:數組的元素數量,可以是一個表達式,但最終通過編譯期計算的結果必須是整型數值,元素數量不能含有到運行時才能確認大小的數值。
  • Type:可以是任意基本類型,包括數組本身,類型爲數組本身時,可以實現多維數組。
	const size = 5
	//數據定義
	var arr1 [3]int
	var arr2 [size]string
	var arr3 [size * 2]string
	var arr4 [2]string = [2]string{"a", "b"}
	arr5 := [5]string{"a", "b"}
	arr6 := [...]float64{1, 2, 3}

	fmt.Println(arr1[0])
	fmt.Println(arr1[len(arr1)-1])
	fmt.Printf("%T\n", arr6) //[3]float64

	for i, v := range arr2 {
		fmt.Printf(" %d  %s \n", i, v)
	}

	for i := range arr3 {
		fmt.Printf(" %d \n", i)
	}

	for _, v := range arr4 {
		fmt.Printf("%s \n", v)
	}

	for _, v := range arr5 {
		fmt.Printf("%s \n", v)
	}

	

多維數組

	arr1 := [4][2]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}}
	arr2 := [4][2]int{1: {2, 2}, 2: {3, 3}}
	arr3 := [4][2]int{1: {0: 1}, 3: {1: 4}}
	var a [2]int = arr2[0]

切片

是對數組的一個連續片段的引用,所以切片是一個引用類型, 切片默認指向一段連續內存區域,可以是數組,也可以是切片本身。

  • //取出的元素數量爲:結束位置 - 開始位置
	arr := [...]string{"a", "b", "c", "d", "e"}
	s1 := arr[2:4]          //索引從0開始,切片內容不包含結束索引
	s2 := arr[4:len(arr)]   //切片最後一個元素使用 slice[len(x)] 獲取;
	s3 := arr[:2]           //從0-2
	s4 := arr[2:]           //從2-最後
	s5 := arr[:]            //等於本身
	s6 := arr[0:0]          //空切片
	fmt.Println(s1)         //[c d]
	fmt.Println(s2)         //[e]
	fmt.Println(s3)         //[a b]
	fmt.Println(s4)         //[c d e]
	fmt.Println(s5)         //[a b c d e]
	fmt.Println(s6)         //[]
	fmt.Printf("%T\n", s1)  //[]string
	fmt.Printf("%T\n", &s1) //*[]string

直接聲明新的切片

var name []Type

	var str []string
	var i []int
	var empty = []int{}
	fmt.Println(str == nil)   //true
	fmt.Println(i == nil)     //true
	fmt.Println(empty == nil) //false

使用 make() 函數構造切片

make( []Type, size, cap )

  • Type 是指切片的元素類型
  • size 指的是爲這個類型分配多少個元素
  • cap 爲預分配的元素數量,這個值設定後不影響 size,只是能提前分配空間,降低多次分配空間造成的性能問題。

使用 make() 函數生成的切片一定發生了內存分配操作,但給定開始與結束位置(包括切片復位)的切片只是將新的切片結構指向已經分配好的內存區域,設定開始與結束位置,不會發生內存分配操作。

	arr1 := make([]int, 2)
	arr2 := make([]int, 2, 10)
	fmt.Println(arr1, len(arr1))//[0 0] 2
	fmt.Println(arr2, len(arr2))//[0 0] 2

append

在使用 append() 函數爲切片動態添加元素時,如果空間不足以容納足夠多的元素,切片就會進行“擴容”,此時新切片的長度會發生改變。 切片在擴容時,容量的擴展規律是按容量的 2 倍數進行擴充,例如 1、2、4、8、16……,

var s1 []int
	s1 = append(s1, 1)                 //追加一個元素
	s1 = append(s1, 1, 2, 3)           //追加多個元素
	s1 = append(s1, []int{1, 2, 3}...) //追加一個切片,切片需要解包

	//在開始處添加元素
	//在切片開頭添加元素一般都會導致內存的重新分配,而且會導致已有元素全部被複制 1 次,因此,從切片的開頭添加元素的性能要比從尾部追加元素的性能差很多。
	var s2 = []int{1, 2, 3}
	s2 = append([]int{0}, s2...)          // 在開頭添加1個元素
	s2 = append([]int{-3, -2, -1}, s2...) // 在開頭添加1個切片

	//鏈式操作
	//var a []int
	//a = append(a[:i], append([]int{x}, a[i:]...)...) // 在第i個位置插入x
	//a = append(a[:i], append([]int{1,2,3}, a[i:]...)...) // 在第i個位置插入切片

copy

可以將一個數組切片複製到另一個數組切片中,如果加入的兩個數組切片不一樣大,就會按照其中較小的那個數組切片的元素個數進行復制。

copy( destSlice, srcSlice []T) int

  • srcSlice 爲數據來源切片
  • destSlice 爲複製的目標(也就是將 srcSlice 複製到 destSlice)
  • 目標切片必須分配過空間且足夠承載複製的元素個數,並且來源和目標的類型必須一致,copy() 函數的返回值表示實際發生複製的元素個數。
	s1 := []int{1, 2, 3, 4, 5}
	s2 := []int{6, 7}

	effect := copy(s1, s2)
	fmt.Println(effect, s1) //2 [6 7 3 4 5]

	copy(s2, s1) //複製s1 的前兩個元素到 s2中

	//刪除第一個元素
	s1 = s1[1:]
	//刪除最後一個元素
	s1 = s1[:len(s1)-1]
	//刪除中間第 3個元素
	var	s3 = []int{1, 2, 3,4,5 }
	s3 = append(s3[:2], s3[2+1:]...) // 刪除中間第 3個元素

range

s1:=[]int{1,2,3,4}

	fmt.Printf("%T,%x,%x\n",s1[0],&s1[0],&s1[1])	//int,c000014180,c000014188
	fmt.Println(s1)	//[1 2 3 4]

	for _, val := range s1 {
		val=0
		fmt.Println(val)
	}
	//不會修改 s1 的值
	fmt.Println(s1) //[1 2 3 4]

	for i := 0; i < len(s1); i++ {
		s1[i]=0
	}
	fmt.Println(s1)//[0 0 0 0]

#map

map 是引用類型,可以使用如下方式聲明:

var mapname map[keytype]valuetype

  • mapname 爲 map 的變量名。
  • keytype 爲鍵類型。
  • valuetype 是鍵對應的值類型。

map 可以根據新增的 key-value 動態的伸縮,因此它不存在固定長度或者最大限制,但是也可以選擇標明 map 的初始容量 capacity ,如果再增加新的 key-value,map 的大小會自動加 1

遍歷輸出元素的順序與填充順序無關,不能期望 map 在遍歷時返回某種期望順序的結果。

	var m1 map[int]string
	m2 := map[string]int{"b": 2, "a": 1, "c": 3}
	m3 := make(map[string]int)      //= map[string]int{}
	m4 := make(map[string]int, 100) //指定初始容量
	mp5 := make(map[int][]int)
	mp6 := make(map[int]*[]int)

	fmt.Println(m1)          //map[]
	fmt.Println(m2)          //map[a:1 b:2]
	fmt.Println(m3, m4, mp5) //map[] map[] map[]
	fmt.Println(m2["a"])     //1

	mp6[0] = &[]int{1, 2, 3}
	fmt.Println(mp6[0])  //&[1 2 3]
	fmt.Println(*mp6[0]) //[1 2 3]
	*mp6[0] = []int{0, 0, 0}
	fmt.Println(*mp6[0]) //[0 0 0]

	//遍歷,遍歷輸出元素的順序與填充順序無關,不能期望 map 在遍歷時返回某種期望順序的結果。
	for key, val := range m2 {
		fmt.Printf("%s %d \n", key, val)
	}

	//排序
	keys := make([]string, len(m2))
	for key := range m2 {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	for _, key := range keys {
		fmt.Printf("%s %d \n", key, m2[key])
	}

	//刪除,使用 delete() 函數從 map 中刪除鍵值對
	delete(m2,"a")

map 的線程案例

Go語言中的 map 在併發情況下,只讀是線程安全的,同時讀寫是線程不安全的 sync.Map 沒有提供獲取 map 數量的方法,替代方法是在獲取 sync.Map 時遍歷自行計算數量,sync.Map 爲了保證併發安全有一些性能損失,因此在非併發情況下,使用 map 相比使用 sync.Map 會有更好的性能。

	var syncMap sync.Map
	//寫
	syncMap.Store("a", 1)
	syncMap.Store("b", 2)
	syncMap.Store("c", 3)
	//讀
	syncMap.Load("a")
	//刪
	syncMap.Delete("a")
	//遍歷
	syncMap.Range(func(key, value interface{}) bool {
		fmt.Println(key,value)
		return true
	})

列表

列表是一種非連續的存儲容器,由多個節點組成,節點通過一些變量記錄彼此之間的關係,列表有多種實現方法,如單鏈表、雙鏈表等。

	var list1 list.List
	list2 := list.New()

	fmt.Printf("%T \n", list1) //list.List

	//插入
	el := list1.PushBack("插入到列表尾部")
	el = list1.PushFront("插入到列表首部")
	//插入指定位置
	list1.InsertAfter("插入指定元素的後面", el)
	list1.InsertBefore("插入指定元素的前面", el)
	//插入列表
	list1.PushBackList(list2)
	list1.PushFrontList(list2)
	//刪除
	list1.Remove(el)
	//遍歷
	for i := list1.Front(); i!=nil;i=i.Next() {
		fmt.Println(i.Value)
	}

nil

在Go語言中,布爾類型的零值(初始值)爲 false,數值類型的零值爲 0,字符串類型的零值爲空字符串"",而指針、切片、映射、通道、函數和接口的零值則是 nil。

  • nil 標識符是不能比較的 如 nil==nil
  • nil 不是關鍵字或保留字
  • nil 沒有默認類型
  • 不同類型 nil 的指針是一樣的
  • 不同類型的 nil 是不能比較的
  • 兩個相同類型的 nil 值也可能無法比較 map、slice 和 function 類型的 nil 值不能比較
  • nil 是 map、slice、pointer、channel、func、interface 的零值
  • 不同類型的 nil 值佔用的內存大小可能是不一樣的
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章