【go語言學習】指針pointer

一、指針的概念

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語言中newmake是內(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)的值為類型零值,返回的是指向類型的指針。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容