一、指針的概念
1、什么是指針
指針是存儲另一個變量的內(nèi)存地址的變量。
變量是一種使用方便的占位符,用于引用計算機內(nèi)存地址。
一個指針變量可以指向任何一個值的內(nèi)存地址。
2、獲取變量的地址
Go 語言的取地址符是 &,放到一個變量前使用就會返回相應(yīng)變量的內(nèi)存地址。
package main
import (
"fmt"
)
func main() {
var a int = 1
fmt.Println("變量a的內(nèi)存地址是:", &a)
}
運行結(jié)果
變量a的內(nèi)存地址是: 0xc000012090
3、聲明指針
var ptr *type
type 為指針類型,ptr 為指針變量名,* 用于指定變量是作為一個指針。
package main
import (
"fmt"
)
func main() {
var a int = 1
fmt.Println("變量a的內(nèi)存地址是:", &a)
var ptr *int
ptr = &a
fmt.Println("變量ptr的內(nèi)存地址是:", &ptr)
fmt.Println("變量ptr的值為:", ptr)
}
運行結(jié)果
變量a的內(nèi)存地址是: 0xc000012090
變量ptr的內(nèi)存地址是: 0xc000006030
變量ptr的值為: 0xc000012090
4、空指針
當一個指針被定義后沒有分配到任何變量時,它的值為 nil。
nil 指針也稱為空指針。
nil在概念上和其它語言的null、None、nil、NULL一樣,都指代零值或空值。
package main
import (
"fmt"
)
func main() {
var ptr *int
fmt.Println("變量ptr的內(nèi)存地址是:", &ptr)
fmt.Println("變量ptr的值為:", ptr)
fmt.Println(ptr == nil)
}
運行結(jié)果
變量ptr的內(nèi)存地址是: 0xc000006028
變量ptr的值為: <nil>
true
二、指針的使用
1、獲取指針的值
*ptr 訪問指針變量指向的原變量的值
package main
import (
"fmt"
)
func main() {
var a int = 1
var ptr *int
ptr = &a
fmt.Println("a的內(nèi)存地址是", ptr)
fmt.Println("a的值是", *ptr)
}
運行結(jié)果
a的內(nèi)存地址是 0xc000012090
a的值是 1
取地址操作符&和取值操作符*是一對互補操作符,&取出地址,*根據(jù)地址取出地址指向的值。
變量、指針地址、指針變量、取地址、取值的相互關(guān)系和特性如下:
- 對變量進行取地址操作使用
&操作符,可以獲得這個變量的指針變量。 - 指針變量的值是指針地址。
- 對指針變量進行取值操作使用
*操作符,可以獲得指針變量指向的原變量的值。
2、操作指針改變變量的值
package main
import (
"fmt"
)
func main() {
var a int = 1
var ptr *int
ptr = &a
fmt.Println("a的內(nèi)存地址是", ptr)
fmt.Println("a的值是", *ptr)
*ptr = 2
fmt.Println("a的值為", a)
}
運行結(jié)果
a的內(nèi)存地址是 0xc000012090
a的值是 1
a的值為 2
3、指針數(shù)組和數(shù)組指針
-
數(shù)組指針
首先是一個指針,存放的是一個數(shù)組的地址
*[len]type
package main
import "fmt"
func main() {
arr := [4]int{1, 2, 3, 5}
fmt.Println(arr)
var ptr *[4]int
ptr = &arr
fmt.Println(ptr)
fmt.Printf("%p\n", ptr) //數(shù)組arr的地址
fmt.Printf("%p\n", &ptr) //指針變量ptr的地址
(*ptr)[0] = 100 //通過指針操作數(shù)組
fmt.Println(arr)
ptr[0] = 200 //簡化寫法
fmt.Println(arr)
}
運行結(jié)果
[1 2 3 5]
&[1 2 3 5]
0xc0000103e0
0xc000006030
[100 2 3 5]
[200 2 3 5]
-
指針數(shù)組
首先是一個數(shù)組,存儲的數(shù)據(jù)類型是指針
[len]*type
package main
import "fmt"
func main() {
a, b, c, d := 1, 2, 3, 4
var arr [4]int
var ptr [4]*int
arr = [4]int{a, b, c, d}
ptr = [4]*int{&a, &b, &c, &d}
fmt.Println(arr)
fmt.Println(ptr)
*ptr[0] = 100
fmt.Println(arr) // 數(shù)組中存儲的是a的值1,所以改變a變量與arr無關(guān)
fmt.Println(a)
arr[0] = 200
fmt.Println(ptr)
fmt.Println(a) // 數(shù)組中存儲的是a的值1,所以改變arr[0]與變量a無關(guān)
}
運行結(jié)果
[1 2 3 4]
[0xc000012090 0xc000012098 0xc0000120a0 0xc0000120a8]
[1 2 3 4]
100
[0xc000012090 0xc000012098 0xc0000120a0 0xc0000120a8]
100
4、函數(shù)指針和指針函數(shù)
-
函數(shù)指針
一個指針,指向了一個函數(shù)
go語言中,函數(shù)默認看做一個指針,沒有*,引用類型的數(shù)據(jù)如map,slice,function都是如此。
package main
import "fmt"
func main() {
var a func()
a = fun
a()
}
func fun() {
fmt.Println("hello world")
}
運行結(jié)果
hello world
-
指針函數(shù)
一個函數(shù),返回值是一個指針
package main
import "fmt"
func main() {
arr := fun1()
fmt.Printf("%T, %p, %v\n", arr, &arr, arr)
ptr := fun2()
fmt.Printf("%T, %p, %v\n", ptr, &ptr, ptr)
}
func fun1() [4]int {
arr := [4]int{1, 2, 3, 4}
return arr
}
func fun2() *[4]int {
arr := [4]int{1, 2, 3, 4}
return &arr
}
運行結(jié)果
[4]int, 0xc0000103e0, [1 2 3 4]
*[4]int, 0xc000006030, &[1 2 3 4]
5、指針傳參
Go 語言允許向函數(shù)傳遞指針,只需要在函數(shù)定義的參數(shù)上設(shè)置為指針類型即可。
package main
import "fmt"
func main() {
arr := [4]int{1, 2, 3, 4}
fmt.Println("函數(shù)調(diào)用前arr:", arr)
fun1(arr)
fmt.Println("函數(shù)fun1調(diào)用后arr:", arr)
fun2(&arr)
fmt.Println("函數(shù)fun2調(diào)用后arr:", arr)
}
func fun1(arr [4]int) {
fmt.Println("fun1中數(shù)組的值:", arr)
arr[0] = 100
fmt.Println("fun1中數(shù)組的值:", arr)
}
func fun2(arr *[4]int) {
fmt.Println("fun2中數(shù)組的值:", arr)
arr[0] = 200
fmt.Println("fun2中數(shù)組的值:", arr)
}
運行結(jié)果
函數(shù)調(diào)用前arr: [1 2 3 4]
fun1中數(shù)組的值: [1 2 3 4]
fun1中數(shù)組的值: [100 2 3 4]
函數(shù)fun1調(diào)用后arr: [1 2 3 4]
fun2中數(shù)組的值: &[1 2 3 4]
fun2中數(shù)組的值: &[200 2 3 4]
函數(shù)fun2調(diào)用后arr: [200 2 3 4]
6、什么情況下使用指針
- 推薦在方法上使用指針
- 當結(jié)構(gòu)體較大的時候使用指針會更高效,可以避免內(nèi)存拷貝
- 如果要修改結(jié)構(gòu)體內(nèi)部的數(shù)據(jù)或狀態(tài)必須使用指針
- 如果方法的receiver是map、slice 、channel等引用類型不要使用指針
- 小數(shù)據(jù)類型如 bool、int 等沒必要使用指針傳遞
- 如果該函數(shù)會修改receiver或變量等,使用指針
三、make和new
package main
import (
"fmt"
)
func main() {
var a *int
b := 10
*a = b // 報錯,a聲明但是沒有初始化,是nil,所以不能指向b。
// a = &b //不報錯,這里是把b的內(nèi)存地址賦值給a,在賦值的同時給a分配內(nèi)存空間
fmt.Println(a)
}
運行結(jié)果
panic: runtime error: invalid memory address or nil pointer dereference
執(zhí)行上面的代碼會引發(fā)panic,為什么呢?
Go語言中對于引用類型的變量,在使用的時候不僅要聲明它,還要為它分配內(nèi)存空間,否則我們的值就沒辦法存儲。
而對于值類型的變量不需要主動分配內(nèi)存空間,是因為它們在聲明的時候已經(jīng)默認分配好了內(nèi)存空間。
Go語言中new和make是內(nèi)建的兩個函數(shù),主要用來分配內(nèi)存。
new
ptr := new(type)
new() 函數(shù)可以創(chuàng)建一個對應(yīng)類型的指針,創(chuàng)建過程會分配內(nèi)存,被創(chuàng)建的指針指向默認值。
package main
import (
"fmt"
)
func main() {
a := new(int)
b := new(bool)
fmt.Printf("a的類型是%T\n", a)
fmt.Printf("b的類型是%T\n", b)
fmt.Println(*a)
fmt.Println(*b)
}
運行結(jié)果
a的類型是*int
b的類型是*bool
0
false
make
make也是用于內(nèi)存分配的,區(qū)別于new,它只用于slice、map以及chan的內(nèi)存創(chuàng)建,而且它返回的類型就是這三個類型本身,而不是他們的指針類型,因為這三種類型就是引用類型,所以就沒有必要返回他們的指針了。
make(T, args)
new與make的區(qū)別
- 二者都是用來做內(nèi)存分配的。
- make只用于slice、map以及channel的初始化,返回的還是這三個引用類型本身;
- new用于類型的內(nèi)存分配,并且內(nèi)存對應(yīng)的值為類型零值,返回的是指向類型的指針。