Go Pointer

  • Go语言为开发人员提供了控制数据结构指针的能力,但并不能进行指针运算。
  • Go允许开发人员控制特定集合的数据结构、分配数量、内存访问模式,对构建运行良好的系统非常重要。
  • 指针对于性能的影响不言而喻,对系统编程、操作系统、网络应用,指针是不可或缺的一部分。

变量是用来存储数据的,变量的本质是给存储数据的内存地址起了一个好记得别名。指针也是变量,但它是一种特殊的变量,因为指针存储的数据并非普通的值,而是另一个变量的内存地址。

变量与指针

Go语言中将指针分拆为两个概念

  1. 类型指针
  • 类型指针允许对指定指针类型的数据进行修改
  • 传递数据可直接使用指针,而无需拷贝数据。
  • 类型指针不能进行偏移和运算

C语言中指针可进行运算,Go语言中不存在指针操作,进而避免内存溢出。

  1. 切片
  • 切片是由指向起始元素的原始指针、元素数量和容量组成
  • 切片比原始指针具备更加强大的特性,更加安全。
  • 切片在发生越界时,运行时会报出宕机,并打印出堆栈,原始指针则只会崩溃。

Go语言的指针类型拥有指针高效访问的特定,又不会发生指针偏移,从而避免非法修改关键性数据的问题。同时,垃圾回收也比较容易对不会发生偏移的指针进行索引和回收。

指针

每个变量在运行时都会拥有一个内存地址,内存地址代表变量在内存中的具体位置,指针的值就是变量的内存地址。

基本数据类型中变量保存的是值,因此又称为值类型。指针类型属于引用类型,因为指针类型的变量保存的并非是一个值,而是一个内存地址,内存地址指向的空间保存的才是最终的值。

Golang中使用&取地址符获取变量的内存地址,使用*获取指针类型所指向的值。

声明指针

创建变量时会在内存中分配内存空间去存储变量的值,每个内存块都具有一个地址以表示当前位置,通常内存地址以16进制数表示。

var varname *vartype

vartype表示指针类型,varname表示指针变量,*星号用于指定变量作为一个指针。

var ptr *int
fmt.Printf("ptr: value = %v, type = %T, addr = %v\n", ptr, ptr, &ptr)
ptr: value = <nil>, type = *int, addr = 0xc000006028

使用指针前需要定义指针变量,然后为指针变量赋值,进而才能访问指针变量中指向地址的值。

i := 10
fmt.Printf("i = %v, type = %T, addr = %p\n", i, i, &i) 
i = 10, type = int, addr = 0xc0000aa058
p := &i
fmt.Printf("p = %v, type = %T, addr = %p, value = %v\n", p, p, &p, *p) 
p = 0xc0000aa058, type = *int, addr = 0xc0000d4020, value = 10

每个变量都拥有内存地址,指针的值就是变量的内存地址。对变量使用&取地址符后会获取变量对应的内存地址,对内存地址使用*后可以获取该地址对应的值。&*是一对互补的操作符,&用于取出地址,*则根据地址取出地址指向的值。

package main

import (
    "fmt"
    "reflect"
)

func main() {
    id := 1
    fmt.Printf("id = %d, addr = %p\n", id, &id)//id = 1, addr = 0xc0000100b0

    addr := &id
    println(addr)//0xc0000100b0
    println(reflect.TypeOf(addr))//(0xf6e8e0,0xf2ea80)

    val := *addr
    println(val)//1
}

创建指针(new)

var p *int
fmt.Printf("p = %v, type = %T\n", p, p) //p = <nil>, type = *int

*p = 100
fmt.Println(*p) //panic: runtime error: invalid memory address or nil pointer dereference

Go语言通过new()函数对指针类型创建一个指针,即是用new()函数创建指针变量,new()函数可以创建一个对应类型的指针,创建过程中会分配内存,被创建的指针指向默认值。

i := new(int)
fmt.Printf("i = %v, type = %T, addr = %p\n", i, i, &i)
i = 0xc0000140a0, type = *int, addr = 0xc000006028

变量是一种使用方便的占位符,用于引用计算机内存地址。变量存储的是一个值,值在内存中拥有一个地址,而指针则保存着变量的内存地址,通过内存地址可以获取变量的值。

Go语言提供的取地址符&放到变量前即可获取变量的内存地址,通过*符号可以将指针的值取出。

i := new(int)
*i = 100
fmt.Printf("i = %v, type = %T, addr = %p, value = %v\n", i, i, &i, *i)
i = 0xc000126058, type = *int, addr = 0xc000150018, value = 100

使用注意

  • 所有的值类型都具有对应的指针类型,其形式为*数据类型
  • 值类型包括基本数据类型中int系列、float系列、boolstring、数组、结构体struct

make函数也可以用于内存分配,与new函数不同的是,make只用于slicemapchan的内存创建,其返回的类型是这三种类型本身,而非其指针类型。因为这三种类型是引用类型,因此没有必要返回指针。

func make(t Type, size ...IntegerType) Type

newmake之间的区别在于,new用来给基本类型申请内存,返回的是指针类型。而make只能给slicemapchan申请内存,且返回的是类型本身。

指针变量

指针变量就是保存变量内存地址的变量

  • 指针变量通常会缩写为ptr
  • 指针变量指向任何值的内存地址,禁止将值直接赋给指针变量。
var i int = 10
var ptr *int = i//cannot use i (type int) as type *int in assignment
cannot use i (type int) as type *int in assignment
  • 指针变量的类型必须和赋值变量的类型保持一致
var i int = 10
var ptr *float32 = &i// cannot use &i (type *int) as type *float32 in assignment
cannot use &i (type *int) as type *float32 in assignment
  • 指针变量占用的字节大小与所指向的值的大小无关
  • 指针变量指向的内存地址在32位机器上占用4个字节,在64位机器上占用8个字节。
var i int = 10                           //基本类型
fmt.Printf("i = %v, addr = %p\n", i, &i) //i = 10, addr = 0xc000126058

var ptr *int = &i //ptr表示指针类型的变量,其类型是指向int类型的指针
fmt.Printf("ptr = %v, addr = %p, value = %v\n", ptr, &ptr, *ptr)
i = 10, addr = 0xc000126058
ptr = 0xc000126058, addr = 0xc000150020, value = 10
  • 指针定义后若没有分配给任何变量则默认值为nil
var p *int//nil
*p = 100
fmt.Println(*p) 
//panic: runtime error: invalid memory address or nil pointer dereference

取地址符(&)

Go语言中变量前添加&操作符(取地址符)来获取变量的内存地址,指针的值是带有0x十六进制前缀的一组数据。

var v int = 100
fmt.Printf("v address is %p", &v)//v address is 0xc0000100b0

当使用&取地址操作符对普通变量进行取地址操作时,会得到变量的指针。

var v int = 100
ptr := &v
fmt.Printf("pointer type is %T", ptr)//pointer type is *int

v表示被取地址的变量,变量v的地址使用变量ptr接收,变量ptr的类型为*T,称为T的指针类型,*表示指针。

指针取值(*)

当使用&取地址操作符获取变量的指针后,可对指针使用*指针取值操作符获取对应的值。

var v int = 100

ptr := &v
fmt.Printf("pointer type is %T\n", ptr)//pointer type is *int
fmt.Printf("pointer address is %p\n", ptr)//pointer address is 0xc0000100b0

val := *ptr
fmt.Printf("pointer value type is %T\n", val)//pointer value type is int
fmt.Printf("pointer value is %d\n", val)//pointer value is 100

&取地址操作符和*取值操作符是一对互补操作符,&取出地址,*根据地址获取地址指向的值。

  • 对变量进行取地址操作时使用&以获取变量的指针变量
  • 指针变量的值是指针地址
  • 对指针变量进行取值操作时使用*,以获取指针变量指向的原变量的值。
var i int = 10
var ptr *int = &i
*ptr = 100

var j int = 20
ptr = &j
*ptr = 200

fmt.Printf("i = %d, j = %d, *ptr = %d\n", i, j, *ptr)//i = 100, j = 200, *ptr = 200

例如:使用指针实现变量交换

package main

import "fmt"

func swap(x, y *int){
    tmp := *x
    *x = *y
    *y = tmp
}

func main(){
    x, y := 1, 2
    swap(&x, &y)
    fmt.Printf("x = %d, y = %d\n", x, y)//x = 2, y = 1
}
  • *取值操作符作为右值时表示获取指针的值,作为左值时即放在赋值操作符左边时表示指针指向的变量。
  • *取值操作符的根本作用是操作指针指向的变量,当操作在右值时获取指向变量的值,当操作在左侧时是将值设置给指向的变量。

例如:使用指针变量获取命令行的输入参数

Go语言内置的flag包实现了对命令行参数的解析,使用flag.String注册名为mode的命令行参数,flag底层会解析命令行并将值赋给mode *string指针。解析完毕后直接通过注册的mode指针获取到最终的值。

package main

import (
    "flag"
    "fmt"
)
//定义命令行参数:go run --mode = fast
var mode = flag.String("mode", "", "process mode")
func main(){
    //解析命令行参数
    flag.Parse()
    //输出命令行参数
    fmt.Printf(*mode)//fast
}

空指针

当指针被定义后没有分配到任何变量此时其值为nilnil指针又称为空指针。nil在概念上等同于其他编程语言中的nullNonenilNULL,都是指代零值或空值。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容