go指針的一個小坑


幾乎可以肯定的說,go語言中除了閉包在引用外部變量的時候是傳引用的,其他的時候都是傳值的。如果你說形參可以定義為指針。好吧,那么告訴你這個指針的值其實是按照傳值的方式使用的。

下面看個很淺顯的例子:

func stillTest(v int) {
    v = v + 100
}
i := 100
fmt.Println("i ", i)
stillTest(i)
fmt.Println("after i ", i)

輸出:

i  100
after i  100

兩個值是不會有什么區別的。但是指針就會有什么區別么?

func anotherStillTest(v *int) {
    *v = *v + 100
}
fmt.Println("i ", i)
anotherStillTest(&i)
fmt.Println("after i ", i)

輸出:

i  100
after i  200

你看到i的值改了,你大喊這難道不是傳的引用嗎。man,仔細看看下面的例子。

func addressStillTest(v *int) {
    x := 456
    v = &x
}
x := 1000
fmt.Println("x ", x)
addressStillTest(&x)
fmt.Println("after x ", x)

輸出:

x  1000
after x  1000

是的,第一個方法中傳了一個地址進去,但是我們明顯不是對地址做的任何修改操作,而是做了一個dereference操作。然后修改了變量的值。而在上面的這個例子中才是對地址的操作。我們在函數addressStillTest中試圖修改x指向的地址,由於x的地址是傳值操作的,也就是拷貝過來的,所以修改是無效的。最后的輸出結果也說明了這一點。

所以在函數操作方面,任何的參數都是按照傳值操作的方式執行的。不管是穿的指針還是一般的一個值都是傳值使用的。

下面再看看這個結構體的例子。首先需要有這個:

type Dog struct {
    Name string
    Type string
}
func addressTest(d *Dog) {
    a := &Dog{"another cute dog", "another type"}
    d = a 
}

輸出:

Dog  5 6
Another Dog  5 6

對結構體直接做更換地址的操作還是不起作用。再一次表面函數的指針也是傳值操作的。

如果要修改一個結構體呢?

func anotherTest(d *Dog) {
    a := &Dog{"another cute dog", "another type"}
    d.Name = a.Name
    d.Type = a.Type
}

輸出:

Dog  cute dog ...
Another Dog  another cute dog another type

 

最后說明一個問題。在c,c++里如果從函數內部返回一個局部變量的指針的話是不對的。但是在Go里是可以的。Go的編譯器會檢查函數的局部變量指針是否會作為返回值給外部使用,如果是的話則將這個變量放在heap上延長其生命周期。

func test() *Dog {
    return &Dog{"cute dog", "..."}
}
d := test()
fmt.Println("Dog ", d.Name, d.Type)

輸出:

Dog  cute dog ...

 

坑已填平!

 

補充

坑其實只是勉強的算是填平了。比如,我現在需要在一個方法中修改一個結構體實例的值。

type Person struct {
        Name string
        Phone string
}

func main() {
        session, err := mgo.Dial("server1.example.com,server2.example.com")
        if err != nil {
                panic(err)
        }
        defer session.Close()

        // Optional. Switch the session to a monotonic behavior.
        session.SetMode(mgo.Monotonic, true)

        c := session.DB("test").C("people")
        err = c.Insert(&Person{"Ale", "+55 53 8116 9639"},
                   &Person{"Cla", "+55 53 8402 8510"})
        if err != nil {
                log.Fatal(err)
        }

        result := Person{} err = c.Find(bson.M{"name": "Ale"}).One(&result) if err != nil {
                log.Fatal(err)
        }

        fmt.Println("Phone:", result.Phone)
}

比如上例中,我需要從mongodb中取出結構體實例result的具體值,把一個指針傳進,然后用給這個實例的每個成員分別賦值的方式可以得到數據庫搜出來的具體的值。但是,如果我們一定要用指針替換的方式來取得這樣的值該怎么辦呢?

還是沿用最開始的例子里的type Dog struct結構體來定義測試方法:

func anotherAddressTest(d **Dog) {
    a := &Dog{"address dog", "address dog type"}
    *d = a 
}
    // get address out of a func
    var aad = &Dog{"8", "9"}
    fmt.Println("Dog ", aad.Name, aad.Type)
    anotherAddressTest(&aad)
    fmt.Println("Address Dog ", aad.Name, aad.Type)

輸出:

Dog  8 9
Address Dog  address dog address dog type

可以看到,值被修改了。整個的東西其實在原理上來說都是一樣的,作為函數的參數直接拷貝過來的指針如果被修改了是不會傳回去任何的東西的。但是,如果指針所指向的內容被修改了,可以帶到函數的外部。所以,這里使用了指向指針的指針,也就是二級指針。根據上面得出的院里二級指針作為參數如果被修改了不會帶出道函數的外部,但是整個二級指針指向的內容如果修改了卻可以帶導函數的外部。

這些都是很淺顯的東西,平時日日重復的代碼生活過得居然都疏忽了。與諸君共勉吧,stay hungry,stay foolish!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM