go中值傳遞和指針傳遞的使用
1、& 和 *
- &后跟一個變量名,得到的是這個變量的內(nèi)存地址
*int
類型的變量,代表這個變量里存的值是int類型的變量的內(nèi)存地址- 數(shù)據(jù)類型的指針類型,即在其前面加
*
號 - 指針就是內(nèi)存地址
package main import( "fmt" ) func main(){ var age int = 18 //&符號+變量 就可以獲取這個變量內(nèi)存的地址 fmt.Println(&age) //0xc0000a2058 //ptr是一個變量,自身也有內(nèi)存地址 //&age就是一個地址,是ptr變量的具體的值 var ptr *int = &age //這樣直接輸出,是ptr這個指針變量的值,即0xc0000a2058 fmt.Println(ptr) //ptr這個指針變量自身的地址 fmt.Println("ptr本身這個存儲空間的地址為:",&ptr) //想獲取ptr這個指針或者這個地址指向的那個數(shù)據(jù): fmt.Printf("ptr指向的數(shù)值為:%v",*ptr) //ptr指向的數(shù)值為:18 }
對指針類型的變量再加*
,是取真實值,即解引用
x := 10 a := &x // 取變量x的地址,a是一個指向int的指針 (*int 類型) fmt.Println(*a) // 輸出a指向的整數(shù)值,即變量x的值,這里將輸出 10
2、空指針
*
雖然可以取到指針類型的真實值(解引用),但對nil解引用,會空指針:panic: runtime error: invalid memory address or nil pointer dereference
比如以下情況:
聲明了一個指針變量,未初始化就直接解引用
var a *int fmt.Println(*a) // 這里將會導致空指針錯誤
給一個指針變量賦值nil后解引用
var a *int = nil fmt.Println(*a) // 這里將會導致空指針錯誤
調(diào)用了一個返回值是指針類型,但返回結(jié)果是nil的函數(shù)。此時直接解引用會空指針。
func returnNilPointer() *int { return nil } func main() { var a *int = returnNilPointer() fmt.Println(*a) // 這里將會導致空指針錯誤 }
對指針類型解引用的正確做法是,先判空:
var ptr *int if ptr != nil { fmt.Println(*ptr) // 安全地解引用ptr } else { fmt.Println("Pointer is nil") // 避免解引用nil指針 }
3、nil
源碼:
- nil是go語言SDK中預先定義好的
- 可以使用
==
操作符來比較指針、切片、映射、通道和接口變量是否為 nil - nil是指針、接口、切片、映射、通道和函數(shù)類型的空值
// 指針 var ptr *int fmt.Println(ptr) // 輸出: nil,即不指向任何有效的內(nèi)存地址
// 接口 var iface fmt.Stringer fmt.Println(iface == nil) // 輸出: true,即接口變量不指向任何具體的實現(xiàn)類對象
// 切片 var s []int fmt.Println(s == nil) // 輸出: true,即表示一個空切片,即長度和容量都為0的切片
// 映射 var m map[string]int fmt.Println(m == nil) // 輸出: true,表示一個空映射,即不包含任何鍵值對的映射
// 通道 var ch chan int fmt.Println(ch == nil) // 輸出: true,即未初始化的通道默認為 nil
// 定義一個函數(shù)類型 HandlerFunc type HandlerFunc func(int) string // 聲明一個 HandlerFunc 類型的變量 handler,但未賦值,其值為nil var handler HandlerFunc
因此,在未初始化的通道中發(fā)送或者接口數(shù)據(jù)、在未初始化的map中進行存儲或者取值,就會panic,但切片有一點不同
var ch chan int ch <- 1 // 嘗試向空通道發(fā)送數(shù)據(jù)會導致panic
var m map[string]int m["key"] = 1 // 嘗試在空映射中存儲值會導致panic
只定義,未初始化的切片,其值為nil,表示一個空切片,即長度和容量都為0的切片,此時,直接s[0] = 1就會發(fā)生下標越界panic,但用append方法一切正常,append 函數(shù)會根據(jù)需要自動初始化切片并分配內(nèi)存
var s []int s[0] = 1 // 越界panic
var s []int s = append(s, 1) // 不會panic或者空指針
注意,自定義的結(jié)構(gòu)體的空值不是nil,這一點和Java中的null不一樣(但如果加了&,即取地址,那就是自定義結(jié)構(gòu)體的指針類型,其空值為nil)
此外,go中,所有的變量 (包括結(jié)構(gòu)體變量) 在聲明時如果沒有顯式賦值,會被賦予其類型的零值。比如:
4、用值傳遞還是指針傳遞?
什么時候用值傳遞,什么時候用指針傳遞?比如向函數(shù)調(diào)用棧里的下一個方法傳遞對象A,二者的區(qū)別在于,指針傳遞,傳的是對象A的內(nèi)存地址,傳的是一個小巧的地址。值傳遞,是復制對象A的數(shù)據(jù)傳下去。
之前有個說法:想在調(diào)用的函數(shù)內(nèi)部改變對象A的值,就用指針傳遞,但這句話也不全對,因為用值傳遞,照樣可以實現(xiàn)同樣的效果。比如對象A為:
// 矩形 type Rectangle struct { Width, Height int }
此時,要改變矩形的寬,值傳遞和引用傳遞(指針傳遞)的實現(xiàn)如下:
注意看二者的返回值,值傳遞,因為不能直接修改原對象,因此,需要將副本對象整個都返回,引用傳遞,則是一個void方法,因為其接收一個原對象的內(nèi)存地址,可以直接修改原對象。這兩種實現(xiàn)方式,在此時,沒有誰優(yōu)誰劣。
考慮優(yōu)先使用值傳遞,原因如下:
對于固定大小的類型(整數(shù)、浮點數(shù)、小型結(jié)構(gòu)體、小型數(shù)組),它們占用的內(nèi)存大小固定且小,大小與指針大小相當
值傳遞,代表的意志是:函數(shù)收到的是一個副本數(shù)據(jù),我只是需要操作這份數(shù)據(jù),不會改動你的原始數(shù)據(jù)
- 對于較小的對象,直接值傳遞,可以避免引用傳遞時對指針解引用的額外步驟
此外,從底層分析原因:
最后,如果傳遞的是一個很大的結(jié)構(gòu)體,那用指針傳遞更優(yōu)。
5、補充
關(guān)于使用指針類型的場景,還有:insert數(shù)據(jù)時,數(shù)據(jù)庫中默認值不對的時候:
到此這篇關(guān)于go中值傳遞和指針傳遞的使用的文章就介紹到這了,更多相關(guān)go 值傳遞和指針傳遞內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang自定義開發(fā)Prometheus?exporter詳解
Exporter是基于Prometheus實施的監(jiān)控系統(tǒng)中重要的組成部分,承擔數(shù)據(jù)指標的采集工作,這篇文章主要為大家介紹了如何自定義編寫開發(fā)?Prometheus?exporter,感興趣的可以了解一下2023-06-06golang之數(shù)據(jù)驗證validator的實現(xiàn)
這篇文章主要介紹了golang之數(shù)據(jù)驗證validator的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-10-10淺談Go語言中的結(jié)構(gòu)體struct & 接口Interface & 反射
下面小編就為大家?guī)硪黄獪\談Go語言中的結(jié)構(gòu)體struct & 接口Interface & 反射。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-07-07