背景
每一門開發語言的基礎都是從資料類型開始學起,Java轉成Golang,是以小編的學習之路又從零開始了。Golang和其他開發語言一樣分為資料類型分為兩種值類型和引用類型,值類型比較簡單就是一些基本資料類型,無論是否有過其他語言基礎,大概看一下也是可以明白的,是以本文主要介紹Golang的引用類型。
基礎
值類型:變量直接存儲值,内容通常在棧中配置設定
引用類型:變量存儲的是一個位址,這個位址存儲最終的值,内容通常在堆上配置設定,通過GC回收
從Golang的設計思想上來說,Golang是函數式程式設計,函數式程式設計最明顯的特點不可變,是以Golang的傳參特點值傳遞,預設傳遞是值,那怎麼處理引用傳遞呢?主要利用本文講解的這幾種引用類型。
Golang中引用類型:指針、slice(切片)、map、chan,chan和并發程式設計聯系比較緊密,放到後面的并發程式設計中,主要講解指針、slice、map
數組
1. 數組:是同一種資料類型的固定長度的序列。
2. 數組定義:var a [len]int,比如:var a [5]int,數組長度必須是常量,且是類型的組成部分。一旦定義,長度不能變。
3. 長度是數組類型的一部分,是以,var a[5] int和var a[10]int是不同的類型。
4. 數組可以通過下标進行通路,下标是從0開始,最後一個元素下标是:len-1
for i := 0; i < len(a); i++ {
}
for index, v := range a {
}
5. 通路越界,如果下标在數組合法範圍之外,則觸發通路越界,會panic
6. 數組是值類型,指派和傳參會複制整個數組,而不是指針。是以改變副本的值,不會改變本身的值。
7.支援 "=="、"!=" 操作符,因為記憶體總是被初始化過的。
8.指針數組 [n]*T,數組指針 *[n]T。
數組初始化方式
package main
import (
"fmt"
)
//全局
var arr0 [5]int = [5]int{1, 2, 3}
var arr1 = [5]int{1, 2, 3, 4, 5}
var arr2 = [...]int{1, 2, 3, 4, 5, 6}
var str = [5]string{3: "hello world", 4: "tom"}
func main() {
//局部
a := [3]int{1, 2} // 未初始化元素值為 0。
b := [...]int{1, 2, 3, 4} // 通過初始化值确定數組長度。
c := [5]int{2: 100, 4: 200} // 使用引号初始化元素。
d := [...]struct {
name string
age uint8
}{
{"user1", 10}, // 可省略元素類型。
{"user2", 20}, // 别忘了最後一行的逗号。
}
//列印全局
fmt.Println(arr0, arr1, arr2, str)
//列印局部
fmt.Println(a, b, c, d)
}
注意點
1、數組是值類型,指派和傳參會複制整個數組,而不是指針。是以改變副本的值,不會改變本身的值。
package main
import (
"fmt"
)
func test(x [2]int) {
fmt.Printf("x: %p\n", &x)
x[1] = 1000
}
func main() {
a := [2]int{}
fmt.Printf("a: %p\n", &a)
test(a)
fmt.Println(a)
}
//列印結果
a: 0xc42007c010
x: 0xc42007c030
[0 0]
通過這段代碼可以推出數組是值類型,在傳輸的過程中都是複制,記憶體位址已經不同。
值拷貝行為會造成性能問題,如何解決這個問題,則引入slice,和指針
slice(切片)
slice 并不是數組或數組指針。它通過内部指針和相關屬性引用數組片段,以實作變長方案。
1. 切片:切片是數組的一個引用,是以切片是引用類型。但自身是結構體,值拷貝傳遞。
2. 切片的長度可以改變,是以,切片是一個可變的數組。
3. 切片周遊方式和數組一樣,可以用len()求長度。表示可用元素數量,讀寫操作不能超過該限制。
4. cap可以求出slice最大擴張容量,不能超出數組限制。0 <= len(slice) <= len(array),其中array是slice引用的數組。
5. 切片的定義:var 變量名 []類型,比如 var str []string var arr []int。
6. 如果 slice == nil,那麼 len、cap 結果都等于 0。
建立切片的方式
//1.聲明切片
var s1 []int
if s1 == nil {
fmt.Println("是空")
} else {
fmt.Println("不是空")
}
// 2.:=
s2 := []int{}
// 3.make()
var s3 []int = make([]int, 0)
//make建立具體參數明顯
var slice []type = make([]type, len)
slice := make([]type, len)
slice := make([]type, len, cap)
s1 := []int{0, 1, 2, 3, 8: 100} // 通過初始化表達式構造,可使用索引号。
fmt.Println(s1, len(s1), cap(s1))
s2 := make([]int, 6, 8) // 使用 make 建立,指定 len 和 cap 值。
fmt.Println(s2, len(s2), cap(s2))
s3 := make([]int, 6) // 省略 cap,相當于 cap = len。
make建立切片的記憶體配置設定
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5SM0gDNzMTOxQTY1gTM1MTZyYzX5IDOxETM5IzLcdDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
切片初始化
全局:
var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
var slice0 []int = arr[start:end]
var slice1 []int = arr[:end]
var slice2 []int = arr[start:]
var slice3 []int = arr[:]
var slice4 = arr[:len(arr)-1] //去掉切片的最後一個元素
局部:
arr2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
slice5 := arr[start:end]
slice6 := arr[:end]
slice7 := arr[start:]
slice8 := arr[:]
slice9 := arr[:len(arr)-1] //去掉切片的最後一個元素
數組和切片的記憶體布局
使用 make 動态建立slice,避免了數組必須用常量做長度的麻煩。還可用指針直接通路底層數組,退化成普通數組操作。
package main
import "fmt"
func main() {
s := []int{0, 1, 2, 3}
p := &s[2] // *int, 擷取底層數組元素指針。
*p += 100
fmt.Println(s)
}
輸出結果:
[0 1 102 3]
用append内置函數操作切片
append :向 slice 尾部添加資料,傳回新的 slice 對象。
package main
import (
"fmt"
)
func main() {
s1 := make([]int, 0, 5)
fmt.Printf("%p\n", &s1)
s2 := append(s1, 1)
fmt.Printf("%p\n", &s2)
fmt.Println(s1, s2)
}
輸出結果:
0xc42000a060
0xc42000a080
[] [1]
超出原 slice.cap 限制,就會重新配置設定底層數組,即便原數組并未填滿
package main
import (
"fmt"
)
func main() {
data := [...]int{0, 1, 2, 3, 4, 10: 0}
s := data[:2:3]
s = append(s, 100, 200) // 一次 append 兩個值,超出 s.cap 限制。
fmt.Println(s, data) // 重新配置設定底層數組,與原數組無關。
fmt.Println(&s[0], &data[0]) // 比對底層數組起始指針。
}
輸出結果
[0 1 100 200] [0 1 2 3 4 0 0 0 0 0 0]
0xc4200160f0 0xc420070060
指針
Go語言中的函數傳參都是值拷貝,當我們想要修改某個變量的時候,我們可以建立一個指向該變量位址的指針變量。傳遞資料使用指針,而無須拷貝資料。
Go語言中的指針操作非常簡單,隻需要記住兩個符号:
&
(取位址)和
*
(根據位址取值)。
每個變量在運作時都擁有一個位址,這個位址代表變量在記憶體中的位置。Go語言中使用&字元放在變量前面對變量進行“取位址”操作。 Go語言中的值類型
(int、float、bool、string、array、struct)
都有對應的指針類型,如:
*int、*int64、*string
等。
變量指針
ptr := &v // v的類型為T
v:代表被取位址的變量,類型為T
ptr:用于接收位址的變量,ptr的類型就為*T,稱做T的指針類型。*代表指針。
從記憶體角度看下差別
func main() {
a := 10
b := &a
fmt.Printf("a:%d ptr:%p\n", a, &a) // a:10 ptr:0xc00001a078
fmt.Printf("b:%p type:%T\n", b, b) // b:0xc00001a078 type:*int
fmt.Println(&b) // 0xc00000e018
}
可以看到b的實際存儲的是a的變量的記憶體位址,是以b被稱為指針類型,那麼如何擷取指針類型真正的資料值呢?即指針取值
func main() {
//指針取值
a := 10
b := &a // 取變量a的位址,将指針儲存到b中
fmt.Printf("type of b:%T\n", b)
c := *b // 指針取值(根據指針去記憶體取值)
fmt.Printf("type of c:%T\n", c)
fmt.Printf("value of c:%v\n", c)
}
type of b:*int
type of c:int
value of c:10
總結: 取位址操作符&和取值操作符
*
是一對互補操作符,
&
取出位址,
*
根據位址取出位址指向的值。
變量、指針位址、指針變量、取位址、取值的互相關系和特性如下:
1.對變量進行取位址(&)操作,可以獲得這個變量的指針變量。
2.指針變量的值是指針位址。
3.對指針變量進行取值(*)操作,可以獲得指針變量指向的原變量的值。
空指針是所有程式員逃不開的困難,Go中何為空指針
- 當一個指針被定義後沒有配置設定到任何變量時,它的值為 nil
- 空指針的判斷,判斷是為nil即可
new和make的使用
new是一個内置的函數,它的函數簽名如下:
func new(Type) *Type
1.Type表示類型,new函數隻接受一個參數,這個參數是一個類型
2.*Type表示類型指針,new函數傳回一個指向該類型記憶體位址的指針。
new函數不太常用,使用new函數得到的是一個類型的指針,并且該指針對應的值為該類型的零值
make也是用于記憶體配置設定的,差別于new,它隻用于slice、map以及chan的記憶體建立,而且它傳回的類型就是這三個類型本身,而不是他們的指針類型,因為這三種類型就是引用類型,是以就沒有必要傳回他們的指針了
func make(t Type, size ...IntegerType) Type
make函數是無可替代的,我們在使用slice、map以及channel的時候,都需要使用make進行初始化,然後才可以對它們進行操作。
var b map[string]int // 隻是聲明變量b是一個map類型的變量 此時b=nil
b = make(map[string]int, 10) //需要make進行初始化後才能使用,否會panic
new和make的差別
1.二者都是用來做記憶體配置設定的。
2.make隻用于slice、map以及channel的初始化,傳回的還是這三個引用類型本身;
3.而new用于類型的記憶體配置設定,并且記憶體對應的值為類型零值,傳回的是指向類型的指針。
map
//Go中map定義
map[KeyType]ValueType
KeyType:表示鍵的類型。
ValueType:表示鍵對應的值的類型。
map類型的變量預設初始值為nil,需要使用make()函數來配置設定記憶體
make(map[KeyType]ValueType, [cap])
cap是make的容量,非必填,但是我們應該在初始化map的時候就為其指定一個合适的容量
map的基本使用
func main() {
//填充
scoreMap := make(map[string]int, 8)
scoreMap["張三"] = 90
scoreMap["小明"] = 100
fmt.Println(scoreMap)
fmt.Println(scoreMap["小明"])
fmt.Printf("type of a:%T\n", scoreMap)
//在聲明時填充
userInfo := map[string]string{
"username": "pprof.cn",
"password": "123456",
}
//判斷key是否存在,定義 value, ok := map[key]
// 如果key存在ok為true,v為對應的值;不存在ok為false,v為值類型的零值
v, ok := scoreMap["張三"]
if ok {
fmt.Println(v)
} else {
fmt.Println("查無此人")
}
//map周遊 range
//同時需要k,v
for k, v := range scoreMap {
fmt.Println(k, v)
}
//隻需要k
for k := range scoreMap {
fmt.Println(k)
}
//删除指定key、delete(map, key)
//map:表示要删除鍵值對的map
//key:表示要删除的鍵值對的鍵
delete(scoreMap, "小明")//将小明:100從map中删除
}