前言
一直對Service Mesh相關內容比較感興趣,後面一路學習了Dcoker、Kubernetes等相關內容,可以說是對基本概念和使用有一定了解,隨着開始學習一些相關的組件的時候,發現基本上全部都是Go語言編寫,雖然這幾年國內還是Java這一套微服務很流行,但是我相信未來是Service Mesh的天下,居於這種背景我開始學習Go的一些語法,會做一些Java語法和Go語法的對比,廢話不多說開始吧。
變量
關於變量使用分三個方面講解,分別是變量定義、基本類型以及類型轉換;
變量定義
關於變量的定義可以分爲三種方式,使用var關鍵字、編譯器自動決定以及使用:=;
使用var關鍵字
使用var聲明變量的格式:var 變量名 變量類型,使用var可以放在函數內部也可以直接放到包內,如果覺得每行都用 var 聲明變量比較煩瑣,可以使用關鍵字 var 和括號,可以將一組變量定義放在一起。
var (
aaa int = 123
bbb string = "123"
)
func main() {
var a, b, c string = "a", "b", "c"
var (
ccc int = 333
ddd string = "444"
)
fmt.Println(a, b, c)
fmt.Println(aaa, bbb, ccc, ddd)
}
編譯器自動決定類型
如果覺得每次都要定義變量的類型,可以使用不指定變量的類型;
func main() {
var e, d, f = "aaa", 789, true
fmt.Println(e, d, f)
}
使用:=聲明變量
如果覺得使用var也很麻煩,也支持不使用var聲明變量;
func main() {
e, d, f := "aaa", 789, true
fmt.Println(e, d, f)
}
類型轉換
Go語言不存在隱式類型轉換,因此所有的類型轉換都必須顯式的聲明,string、int、float類型相互轉換,相對於Java優勢來說這個,Go會將錯誤的類型返回出來。
func main() {
// string轉int
c := "123"
d, err := strconv.Atoi(c)
//如果異常則 err不爲空
if err != nil {
fmt.Println(err)
}
fmt.Println("string轉int", d)
// string轉int64 第二參數表示進制 第三個參數表示int位數
f := "101"
e, er := strconv.ParseInt(f, 2, 64)
if er != nil {
fmt.Println(er)
}
fmt.Println("string轉int64", e)
// string轉float64 第二個參數表示float位數
h := "123.66"
g, er2 := strconv.ParseFloat(h, 64)
if er2 != nil {
fmt.Println(er2)
}
fmt.Println("string轉float64", g)
// int轉string
i := 100
j := strconv.Itoa(i)
fmt.Println("int轉string", j)
// int轉float64
k := float64(i)
fmt.Printf("int轉float64%T\n", k)
fmt.Println("int轉float64", k)
// float轉int 精度丟失
a := 3.1245926
b := int(a)
fmt.Printf("float轉int%T\n", a)
fmt.Println("float轉int", b)
// float轉string
// 第二個參數格式標記 第三個參數數字位數 第四個參數float類型
l := strconv.FormatFloat(a, 'G', 3, 64)
fmt.Println(l)
}
基本類型
布爾值
對於布爾值和其他語言一樣,布爾型數據只有true和 false兩個值,需要注意的是,Go語言中布爾型無法參與數值運算,也無法與其他類型轉換。
func main() {
a := true
var b bool
fmt.Printf("%T\n", a)
fmt.Printf("%T value:%v\n", b, b)
}
整型
整型分爲以下兩個大類: 按長度分爲:int8、int16、int32、int64 對應的無符號整型:uint8、uint16、uint32、uint64,int64對應Java的Long,int32對應Java的int。
特殊整型
注意: 在使用int和 uint類型時,不能假定它是32位或64位的整型,而是考慮int和uint可能在不同平臺上的差異。
數字字面量語法
Go1.13版本之後引入了數字字面量語法,使用如下:
func main() {
m := 10
fmt.Printf("十進制%d \n", m)
fmt.Printf("二進制%b \n", m)
fmt.Printf("八進制%o \n", m)
fmt.Printf("十六進制%x \n", m)
}
浮點型
Go語言支持兩種浮點型數:float32和float64。 float32的浮點數的最大範圍約爲 3.4e38,可以使用常量定義:math.MaxFloat32。 float64的浮點數的最大範圍約爲 1.8e308,可以使用一個常量定義:math.MaxFloat64。
func main() {
n := 10.11
fmt.Printf("浮點型%f\n", n)
fmt.Printf("浮點型%.2f\n", n)
}
複數
Go相比於其他語言多了一個複數的類型,複數的類型有兩種,分別是 complex128(64 位實數和虛數)和 complex64(32 位實數和虛數),其中 complex128 爲複數的默認類型。方便我們對一些公式進行表示,側面說明了Go語言想充分涉獵多個領域。
func main() {
c2 := 2 + 3i
fmt.Println("複數聲明", c2)
fmt.Println("獲取複數的實部", real(c2), ", 獲取複數的虛部", imag(c2))
}
字符串
Go語言中的字符串不像其他語言佔固定的字節數,Go語言的字符串是一個用UTF-8編碼的變寬字符序列,它的每一個字符都用一個或多個字節表示 ,因此Go語言中字符串根據需要佔用 1 至 4 個字節。在Go語言中,沒有字符類型,字符類型是rune類型,rune實際是一個int32。可使用 []byte() 獲取字節數,使用 []rune()獲取字符,可對中文進行轉換。
func main() {
s := "a我沒菜了" //UTF-8編碼
fmt.Println("遍歷字節")
for _, b := range []byte(s) {
fmt.Printf("%X ", b)
}
fmt.Println()
fmt.Println("獲取字符串個數", utf8.RuneCountInString(s))
fmt.Println("獲取字節數", len(s))
fmt.Println("遍歷字符")
bytes := []byte(s)
for len(bytes) > 0 {
k, v := utf8.DecodeRune(bytes)
bytes = bytes[v:]
fmt.Printf("%c ", k)
}
fmt.Println()
for k, v := range []rune(s) {
fmt.Printf("(%d,%c)", k, v)
}
fmt.Println()
}
定義字符串
可以使用雙引號""來定義字符串,字符串中可以使用轉義字符來實現換行、縮進等效果,常用的轉義字符包括:
反引號定義字符串,這些字符串可能由多行組成(不支持任何轉義序列),原生的字符串字面量多用於書寫多行消息、HTML以及正則表達式,不支持做任何轉義的原始內容;
func main() {
m := "Go語言字符串\n不能跨行賦值"
fmt.Println(m)
n := `Go原生原格式字符串
可以跨行`
fmt.Println(n)
}
連接字符串
使用+號連接字符串,Go裏面的字符串都是不可變的,每次運算都會產生一個新的字符串,所以會產生很多臨時的無用的字符串,不僅沒有用,還會給 gc帶來額外的負擔,所以性能比較差;
str := "hello" + "go"
使用 fmt.Sprintf() 連接字符串,內部使用 []byte 實現,不像直接運算符這種會產生很多臨時的字符串,但是內部的邏輯比較複雜,有很多額外的判斷,還用到了interface,性能也不是很好;
str := fmt.Sprintf("%s,%s", "hello", "go")
使用 buffer.WriteString()連接,可以當成可變字符使用;
var buffer bytes2.Buffer
buffer.WriteString("hello")
buffer.WriteString(",")
buffer.WriteString("go")
str := buffer.String()
fmt.Println(str)
byte和rune類型
Go語言中使用單引號用來表示單個的字符,字符也是組成字符串的元素。Go語言的字符有兩種:
uint8類型,或者叫 byte 型,代表了ASCII碼的一個字符; rune類型,代表一個 UTF-8字符;
rune類型的值在底層都是由一個 UTF-8 編碼值來表達的。Unicode字符,我們平時接觸到的中英日文,或者複合字符,都是Unicode字符。UTF-8 編碼方案會把一個 Unicode 字符編碼爲一個長度在 1~4 以內的字節。所以,一個rune類型值代表了1~4個長度的byte數組。因此一個string類型的值既可以被拆分爲一個包含多個字符的序列,也可以被拆分爲一個包含多個字節的序列。前者可以由一個以rune爲元素類型的切片來表示,而後者則可以由一個以byte爲元素類型的切片代表。
修改字符串
要修改字符串,需要先將其轉換成[]rune或[]byte,完成後再轉換爲string。無論哪種轉換,都會重新分配內存,並複製字節數組。
func main() {
s1 := "wtz"
byteS1 := []byte(s1)
byteS1[0] = 'p'
fmt.Println(string(byteS1))
s2 := "綠葉"
runeS2 := []rune(s2)
runeS2[0] = '白'
fmt.Println(string(runeS2))
}
常量
常量是指編譯期間就已知且不可改變的值,在Go語言中我們可以通過 const 關鍵字來定義常量,常量只能是布爾、整數、浮點、複數和字符串。
常量定義
通過 const 關鍵字定義常量時,可以指定常量類型,也可以省略(底層會自動推導),如果在運行時修改常量的值,則會在編譯期報錯。常見的常量定義方式如下:
//通過一個const關鍵字定義多個常量,和 var 類似
const (
size int64 = 1024
// 無類型整型常量
eof = -1
)
//無類型整型和字符串常量
const a, b, c = 3, 4, "foo"
iota
iota是一個特殊常量,可以認爲是一個可以被編譯器修改的常量。在每一個const關鍵字出現時,被重置爲0,然後再下一個const出現之前,每出現一次iota,其所代表的數字會自動增加1。
const (
a = iota
b = iota
c = iota
)
對於上面常量聲明我們也可以簡寫爲:
const (
a = iota
b
c
)
此外我們也可以進行運算,如下:
import "fmt"
const (
i=1<<iota
j=3<<iota
k
l
)
func main() {
fmt.Println("i=",i)
fmt.Println("j=",j)
fmt.Println("k=",k)
fmt.Println("l=",l)
}
枚舉
對於Go語言來說不存在枚舉類型,但是可以使用常量來表示枚舉。枚舉中包含了一系列相關的常量,比如下面關於一個星期中每天的定義。Go 語言並不支持其他語言用於表示枚舉的 enum 關鍵字,而是通過在 const 後跟一對圓括號定義一組常量的方式來實現枚舉。
const (
Sunday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
條件判斷
if
Go關於if的判斷做的更加簡潔,去掉了括號,此外還可以增加一個表達式,語法如下:
if 布爾表達式 {
/* 在布爾表達式爲 true 時執行 */
}
if 初始化表達式; 條件表達式 {
/* 在布爾表達式爲 true 時執行 */
}
func main() {
m := 100
if m > 20 {
fmt.Println(m)
} else {
fmt.Println(m * 2)
}
if content, err := ioutil.ReadFile(""); err == nil {
fmt.Println(string(content))
} else {
fmt.Println(" file error", err)
}
}
switch
Go的switch的語法和其他類似,但是不同的在於默認情況下 case 最後自帶 break 語句,匹配成功後就不會執行其他 case,如果我們需要執行後面的 case,可以使用 fallthrough ,此處支持一個case裏面多種情況,也可以不傳入參數,語法如下:
switch var1 {
case val1:
...
case val2:
...
default:
...
}
func grade(score int) string {
g := ""
switch {
case score < 60:
g = "B"
case score > 70:
g = "A"
default:
panic("錯誤了")
}
return g
}
for
對於循環語句,在Go語言中只存在for循環,沒有while和do...while的語法,此外對於for循環也是可以省略括號的,語法結構如下
for init; condition; post { }
初始化語句只執行一次。在初始化循環之後,將檢查該條件。如果條件計算爲true,那麼{}中的循環體將被執行,然後是post語句。post語句將在循環的每次成功迭代之後執行。在執行post語句之後,該條件將被重新檢查。如果它是正確的,循環將繼續執行,否則循環終止。在for循環中聲明的變量僅在循環範圍內可用。因此,i不能在外部訪問循環。對於fo循環的r所有的三個組成部分,即初始化、條件和post都是可選的。
對於for實現while的語法如下,該語法類似於Java中for(,,)的效果:
for condition { }
此外for可以循環的 range 格式可以對 slice、map、數組、字符串等進行迭代循環。
func main() {
var b int = 15
var a int
numbers := [6]int{1, 2, 3, 5}
for a := 0; a < 10; a++ {
fmt.Printf("a 的值爲: %d\n", a)
}
for a < b {
a++
fmt.Printf("a 的值爲: %d\n", a)
}
for i,x:= range numbers {
fmt.Printf("第 %d 位 x 的值 = %d\n", i,x)
}
}
數組、切片、Map
數組
Go語言中與Java不同的是數組是一個值類型,對於其他數組是一個固定長度不可以變。在內存上開闢的空間是連續的。
聲明數組
Go語言中,數組的聲明方式爲var arr_name [5]int。聲明時沒有指定數組的初始化值,因此所有的元素都會被自動初始化爲默認值 0;
func main() {
//聲明一個包含5個元素的數組 默認值爲0
var arry [5]int
//聲明一個包含5個元素的數組,並初始化每個元素
arry1 := [5]int{1, 2, 3, 4, 5}
//容量由初始化數量決定
arry2 := [...]int{100, 200, 300}
//聲明一個包含5個元素的數組 指定特定位置的數組初始化
arry3 := [5]int{1: 20, 4: 40}
fmt.Println(arry)
fmt.Println(arry1)
fmt.Println(arry2)
fmt.Println(arry3)
}
使用數組
Go語言普通的數組無法通過下標直接訪問數組,這是與Java不一致的地方,可以通過下標修改數組中的值,也可以將一個數組賦值給另外一個相同的數組,可以通過range關鍵字遍歷數組。在Go語言中數組是一個值類型,所以在函數傳遞的時候需要在棧上分配相同大小的內存,這也是與Java不一樣的地方,關於如何向Java一樣使用在後續指針時候介紹。
func main() {
arry := [5]int{10, 20, 30, 40, 50}
//修改索引爲2的數組
arry[2] = 35
fmt.Println(arry)
//相同類型數組賦值給另外一個數組
var arry1 [5]int
arry1 = arry
fmt.Println(arry1)
//遍歷數組
for k, v := range arry {
fmt.Printf("下標 %d 值 %d \n", k, v)
}
//函數傳遞
foo(arry)
}
func foo(arry [5]int) {
//遍歷數組
for k, v := range arry {
fmt.Printf("下標 %d 值 %d \n", k, v)
}
}
多維數組
多維數組與數組的區別在於有多個維度,使用上就是通過多個座標決定位置。
func main() {
//多維數組聲明
var arry3 [4][2]int
arry2 := [2][2]int{{10, 11}, {10, 12}}
fmt.Println(arry3)
fmt.Println(arry2)
}
切片
關於切片這個概念是Go語言獨有的,Go語言的切片可理解爲對數組的一種抽象。由於Go語言的數組長度不可改變,在特定場景中就不太適用,Go提供了切片,與數組相比切片的長度是不固定的,可以追加元素,在追加時可能使切片的容量增大。slice 並不是數組或數組指針,它通過內部指針和相關屬性引用數組片段 ,以實現變長方案。
切片的結構體由3部分構成,Pointer 是指向一個數組的指針,len 代表當前切片的長度,cap 是當前切片的容量。cap 總是大於等於 len 的。
聲明切片
使用make
使用make創建切片的語法如下:
var slice1 []type = make([]type, length, capacity)
//也可以簡化爲
slice1 := make([]type, length, capacity)
需要注意的是當cap值大於len時候創建會報錯,下面演示了三種創建的方式;
make函數創建; 賦值符 := 創建,通過索引和之間初始化的方式; 通過數組;
func main() {
//創建一個int類型切片 len cap 都爲5
slice := make([]int, 5)
fmt.Println(slice)
//創建一個int類型切片 len爲3 cap爲5
slice1 := make([]int, 3, 5)
fmt.Println(slice1)
//通過字面量來創建切片 這樣cap和len都等於5 與數組不同地方在於不需要指定長度
slice2 := []int{10, 20, 30, 40, 50}
fmt.Println(slice2)
//通過下標來創建 與正常字面量創建不太一樣的地方是中間使用: 在下標的第10個位置初始化一個8
slice3 := []int{10: 8}
fmt.Println(slice3)
//通過數組來創建 截取數組下標0到1的元素爲一個新切片
arry := [5]int{1, 2, 3, 4, 5}
slice4 := arry[0:2]
fmt.Println(slice4)
}
nil和空切片
工作中我們可能需要創建一個nil切片或者空切片,Go語言的nil相當於Java的null,我們可以使用以下方式創建:
func main() {
//創建nil切片
var slice4 []int
if slice4 == nil {
fmt.Println("nil切片", slice4)
}
//創建空切片
slice5 := make([]int, 0)
slice6 := []int{}
if slice5 != nil && slice6 != nil {
fmt.Println("空切片", slice5)
fmt.Println("空切片", slice6)
}
}
空切片和 nil 切片的區別在於,空切片指向的地址不是 nil,指向的是一個內存地址,但是它沒有分配任何內存空間,即底層元素包含0個元素。不管是使用 nil 切片還是空切片,對其調用內置函數 append,len 和 cap 的效果都是一樣的。
初始化
關於直接創建的切片與通過數組創建的切片還是有一些不一樣的,這裏來闡述一下不同。
聲明的同時初始化
聲明的同時就完成了初始化操作,採用該方式的結構是這樣,同時或默認創建一個數組,cap=len=6;
slice2 := []int{10, 20, 30, 40, 50, 60}
數組初始化切片
採用數組進行切片的初始化,這個時候切片指向的數組的引用,關於arry[1:3:5]的意思是,從數組下標1位置開始到,len長度等於3-1,cap長度等於5-1,len代表切片的長度,cap代表切片總容量。
arry := [6]int{10, 20, 30, 40, 50, 60}
slice := arry[2:5:5]
slice2 := arry[1:3:5]
fmt.Println(slice)
fmt.Println(slice2)
關於採用數組聲明方式介紹如下:
總結:切片的本質是數組,但是並不是數組,它只是引用了數組的一段。創建一個切片,系統會自動爲你創建一個底層數組,然後引用這個底層數組生成一個切片。操作的是切片本身,但實際上操作的是它所依託的那個底層的數組。
使用切片
介紹數組的時候你會發現我們介紹一些關於數組增加元素、刪除元素的操作,在Go語言中這些都是通過切片實現的,關於使用切片這裏主要介紹常用函數的使用:
len 和 cap
len獲取切片的長度,cap獲取切片最大的容量。
func main() {
arry := [6]int{10, 20, 30, 40, 50, 60}
slice := arry[2:5:5]
fmt.Println("slice len=%d cap=%d ", len(slice), cap(slice), slice)
}
append
關於append是切片的尾部添加數據,需要注意的是如果切片超過最大的容量會重新創建新的切片,對於通過數組創建的切片也是一樣的。
func main() {
a := []int{1, 2, 3}
b := []int{4, 5, 6}
//向後添加元素
c := append(a, b...)
fmt.Printf("c對象 %p\n", c)
fmt.Print(c)
fmt.Println()
d := append(c, 7)
//c和d已經不是同一個對象 新建一個數組
fmt.Printf("d對象 %p\n", d)
fmt.Println(d)
fmt.Println()
arry := [6]int{10, 20, 30, 40, 50, 60}
e := arry[2:5]
fmt.Printf("e對象 %p\n", e)
fmt.Println(e)
f := append(e, 51, 52)
//e和f對象不一樣 底層數組已經發生改變
fmt.Printf("f對象 %p\n", f)
fmt.Print(f)
}
copy
切片的拷貝分爲2種,一種是淺拷貝,一種是深拷貝。淺拷貝:源切片和目的切片共享同一底層數組空間,源切片修改,目的切片同樣被修改。深拷貝:源切片和目的切片各自都有彼此獨立的底層數組空間,各自的修改,彼此不受影響。淺拷貝核心就是共享底層數組,修改某個切片的值也會影響到另外一個切片,案例如下
func main() {
slice := []int{10, 20, 30, 40, 50, 60}
fmt.Println("原切片", slice)
slice1 := slice[1:3]
fmt.Println("淺拷貝切片", slice1)
slice1[1] = 21
fmt.Println("淺拷貝切片賦值以後", slice1)
fmt.Println("原切片", slice)
}
深拷貝的核心是底層數組不相互依賴,相互的修改互不影響,案例如下:
func main() {
slice := []int{1, 2, 3, 4, 5}
fmt.Println("slice 初始化",slice)
slice1 := []int{10, 20, 30, 40, 50, 60}
fmt.Println("slice1 初始化",slice1)
fmt.Println("slice 拷貝 slice1",copy(slice, slice1))
slice[0] = 11
slice1[0] = 12
//這個地方我們會發現輸出以後的值不相互影響
fmt.Println("slice 深拷貝完以後修改值",slice)
fmt.Println("slice1 後修改值",slice1)
}
切片遍歷
func main() {
slice := []int{1, 2, 3, 4, 5}
for k, v := range slice {
fmt.Printf("key : %v , value : %v\n", k, v)
}
}
Map
聲明Map
聲明Map的方式有兩種,通過make或者map可以聲明,Map是一個引用類型的,如果只聲明,而不創建 map,那麼就會創建一個 nil map。nil map 不能用來存放鍵值對,如果對nil map 進行操作會報錯。聲明之後,map類型的變量默認初始值爲 nil,需要使用 make() 函數來分配內存。
func main() {
//採用字面量聲明並初始化
m := map[string]string{"A": "A", "B": "B"}
fmt.Println(m)
//make函數創建的是是個空的map
m2 := make(map[string]int)
fmt.Println(m2)
//使用map聲明的時候 是一個nil 這個時候必須通過make進行初始化
var m3 map[string]int
//初始化10個容量
m3 = make(map[string]int, 10)
m3["A"] = 1
fmt.Println(m3)
}
使用Map
獲取和使用
獲取和使用對應Java就是get和put函數,對於Go語言來說這個就很簡單了。
func main() {
//採用字面量聲明並初始化
m := map[string]string{"A": "A", "B": "B"}
fmt.Println(m)
//put一個值
m["C"] = "C"
fmt.Println(m)
//獲取一個值
fmt.Println(m["A"])
}
delete
delete函數用於刪除集合 map 中的元素,類似於Java的remove操作。
func main() {
//採用字面量聲明並初始化
m := map[string]string{"A": "A", "B": "B"}
fmt.Println(m)
//刪除
delete(m, "A")
fmt.Println(m)
}
判斷某個元素是否存在
Go語言中有個判斷 map 中某個鍵是否存在的特殊寫法,格式如下:value, ok := map[key],如果鍵存在,那麼會返回 相應的值 和 true,如果鍵不存在,那麼會空和false。
func main() {
//採用字面量聲明並初始化
m := map[string]string{"A": "A", "B": "B"}
fmt.Println(m)
//判斷一個值是否存在
value, ok := m["A"]
if ok {
fmt.Println(value)
}
}
遍歷Map
對於Map的遍歷是無序的,同樣也需要採用range遍歷,如果需要有序的遍歷我們需要使用對鍵進行排序的方法是把所有的鍵放在一個切片裏,然後用sort包中的函數進行排序。
func main() {
//採用字面量聲明並初始化
m := map[string]string{"A": "A", "B": "B", "E": "E", "C": "C", "D": "D"}
fmt.Println(m)
//遍歷 k v
for k, v := range m {
fmt.Printf("key %s value %s \n", k, v)
}
//遍歷 k
//將key存放到一個切片中
keys := make([]string, 5, 10)
for k := range m {
fmt.Printf("key %s \n", k)
keys = append(keys, k)
}
//key排序
sort.Strings(keys)
//有序遍歷
fmt.Printf("有序遍歷")
for _, v := range keys {
fmt.Printf("key %s value %s \n", v, m[v])
}
}
指針
我們上面介紹基本類型,在函數傳遞的過程中都是值傳遞,也就是說必須在棧上開闢對應的空間,在Go語言引入指針的概念,讓我們可以實現引用傳遞,在傳遞過程中傳遞數據使用指針,而無須拷貝數據,區別於C/C++中的指針,Go語言中的指針不能進行偏移和運算,是安全指針,也是一種簡化的指針。在Go語言中的指針使用非常簡單,只需要記住兩個符號:&(取地址)和*(根據地址取值)。
指針是什麼
Go語言的指針變量就是一個值的內存地址。那麼就可以通過這個變量的地址去訪問它。
func main() {
a := 10
b := &a
//&a獲取的是地址
fmt.Printf("a的值:%d a指針地址:%p\n", a, &a)
//*b取值
fmt.Printf("變量b:%p b的類型:%T b變量的存儲的值%d\n", b, b, *b)
fmt.Printf("變量b指針地址:%p \n", &b)
}
指針聲明以及使用
指針的聲明的格式如下: var-type 爲指針類型,name 爲指針名,* 號用於指定變量是一個指針。Go語言中的值類型(int、float、bool、string、array、struct)都可以聲明爲指針類型。
var name *var-type
指針的使用
指針的使用:
定義指針變量; 爲指針變量賦值; 訪問指針變量中指向地址的值;
需要注意的是,如果只聲明指針卻不初始化,直接使用會報錯。對於Go語言來說,值類型聲明的時候就已經創建好內存空間,對於引用類型來說,我們聲明的時候只是一個指針變量,沒有進行初始化,也就是沒進行內存空間的開闢,直接使用自然會報錯。當一個指針被定義後沒有分配到任何變量時,它的值爲 nil。nil 指針也稱爲空指針,這時候就和Java是一樣的了。
func main() {
//聲明變量
a := 20
//聲明指針變量
var ip *int
//指針變量的存儲地址
ip = &a
fmt.Printf("a 變量的地址是: %x\n", &a)
//指針變量的存儲地址
fmt.Printf("ip 變量儲存的指針地址: %x\n", ip)
//使用指針訪問值
fmt.Printf("*ip 變量的值: %d\n", *ip)
}
在Go語言中爲我們提供new函數,可以讓我們完成一次性完成上面的三個步驟。
func main() {
a := new(int)
fmt.Println(a)
fmt.Println(*a)
*a = 100
fmt.Println(*a)
}
在上面這個案例中new函數得到的是指向一個類型的指針,並且該指針對應的值爲該類型的零值。這裏還要提一下make函數,make函數也是用於內存分配的。區別於new,它只用於slice、map以及chan的內存創建,而且它返回的類型就是這三個類型本身,而不是他們的指針類型,因爲這三種類型就是引用類型,所以就沒有必要返回他們的指針了。
函數間傳遞
在函數間使用指針傳遞,也就是我們常說的引用傳遞,在函數調用時可以改變這塊地址中存儲的變量的值。
func main() {
a := 10
//值傳遞
modify(a)
fmt.Println("值傳遞", a)
//引用傳遞
modify2(&a)
fmt.Println("引用傳遞", a)
}
func modify(x int) {
x = 100
}
func modify2(x *int) {
*x = 100
}
結束
歡迎大家點點關注,點點贊!