Go语言学习
  • README
  • Go 基础
    • go语言介绍
    • go语言主要特性
    • go内置类型和函数
    • init函数和main函数
    • 下划线
    • iota
    • 字符串
    • 数据类型:数组与切片
    • 数据类型:byte、rune与字符串
    • 变量的5种创建方式
    • 数据类型:字典
    • 指针
    • 数据类型:指针
    • 类型断言
    • 流程控制:defer延迟执行
    • 异常机制:panic和recover
    • 函数
    • go依赖管理
    • go中值传递、引用传递、指针传递区别
  • 标准库
    • Go net/http包
  • 数据结构
    • 哈希表
      • 为什么对 map 的 key 或 value 进行取址操作是不允许的
  • Gin
    • gin 快速开始
    • gin-swagger用法
  • Go 进阶
    • Go 指针
    • Go 中的 GC 演变是怎样的?
    • Go 的堆和栈
  • 面向对象
    • make 和 new 的区别
    • new(T) 和 &T{} 有什么区别?
  • 并发编程
    • Channel
    • Go语言 CSP 并发模型
    • GMP 模型原理
      • GMP 模型为什么要有 P ?
    • Go 协程池(goroutine pool)
    • Go语言常见的并发模式
    • Go并发实践:主动停止goroutine
  • 最佳实践
    • 发布Go语言模块
  • 软件包
    • 常用的GoLang包工具
    • Go的UUID生成
    • 现代化的命令行框架Cobra
    • 配置解析神器Viper
    • Go发送邮件gomail
    • Go反射框架Fx
    • NSQ消息队列的使用
    • Go爬虫框架colly
    • grpc-go 的安装和使用
Powered by GitBook
On this page
  • 1. 指针的概念
  • 2. 内存地址说明
  • 3.变量与指针运算理解
  • 4. 指针的使用
  • 5. 零值与nil(空指针)
  • 6. 总结

Was this helpful?

  1. Go 进阶

Go 指针

Previousgin-swagger用法NextGo 中的 GC 演变是怎样的?

Last updated 2 years ago

Was this helpful?

1. 指针的概念

概念
说明

变量

是一种占位符,用于引用计算机的内存地址。可理解为内存地址的标签

指针

表示内存地址,表示地址的指向。指针是一个指向另一个变量内存地址的值

&

取地址符,例如:{指针}:=&{变量}

*

取值符,例如:{变量}:=*{指针}

2. 内存地址说明

2.1. 内存定义

计算机的内存 RAM 可以把它想象成一些有序的盒子,一个接一个的排成一排,每一个盒子或者单元格都被一个唯一的数字标记依次递增,这个数字就是该单元格的地址,也就是内存的地址。

硬件角度:内存是CPU沟通的桥梁,程序运行在内存中。

逻辑角度:内存是一块具备随机访问能力,支持读写操作,用来存放程序及程序运行中产生的数据的区域。

概念
比喻

内存

一层楼层

内存块

楼层中的一个房间

变量名

房间的标签,例如:总经理室

指针

房间的具体地址(门牌号),例如:总经理室地址是2楼201室

变量值

房间里的具体存储物

指针地址

指针的地址:存储指针内存块的地址

2.2. 内存单位和编址

2.2.1. 内存单位

单位
说明

位(bit)

计算机中最小的数据单位,每一位的状态只能是0或1

字节(Byte)

1Byte=8bit,是内存基本的计量单位

字

“字”由若干个字节构成,字的位数叫字长,不同档次的机器有不同的字长

KB

1KB=1024Byte,即1024个字节

MB

1MB=1024KB

GB

1GB=1024MB

2.2.2. 内存编址

计算机中的内存按字节编址,每个地址的存储单元可以存放一个字节的数据,CPU通过内存地址获取指令和数据,并不关心这个地址所代表的空间在什么位置,内存地址和地址指向的空间共同构成了一个内存单元。

2.2.3. 内存地址

内存地址通常用16进制的数据表示,例如0x0ffc1。

3.变量与指针运算理解

编写一段程序,检索出值并存储在地址为 200 的一个块内存中,将其乘以 3,并将结果存储在地址为 201 的另一块内存中

3.1.本质

  1. 检索出内存地址为 200 的值,并将其存储在 CPU 中

  2. 将存储在 CPU 中的值乘以 3

  3. 将 CPU 中存储的结果,写入地址为 201 的内存块中

3.2.基于变量的理解

  1. 获取变量 a 中存储的值,并将其存储在 CPU 中

  2. 将其乘以 3

  3. 将结果保存在变量 b 中

var a = 6 
var b = a * 3

3.3.基于指针的理解

func main() {
    a := 200
    b := &a
    *b++
    fmt.Println(a)
}

以上函数对a进行+1操作,具体理解如下:

1.a:=200

2. b := &a

3. *b++

4. 指针的使用

4.1. 方法中的指针

方法即为有接受者的函数,接受者可以是类型的实例变量或者是类型的实例指针变量。但两种效果不同。

1、类型的实例变量

func main(){
    person := Person{"vanyar", 21}
    fmt.Printf("person<%s:%d>\n", person.name, person.age)
    person.sayHi()
    person.ModifyAge(210)
    person.sayHi()
}
type Person struct {
    name string
    age int
}
func (p Person) sayHi() {
    fmt.Printf("SayHi -- This is %s, my age is %d\n",p.name, p.age)
}
func (p Person) ModifyAge(age int) {
    fmt.Printf("ModifyAge")
    p.age = age
}


//输出结果
person<vanyar:21>
SayHi -- This is vanyar, my age is 21
ModifyAgeSayHi -- This is vanyar, my age is 21

尽管 ModifyAge 方法修改了其age字段,可是方法里的p是person变量的一个副本,修改的只是副本的值。下一次调用sayHi方法的时候,还是person的副本,因此修改方法并不会生效。

即实例变量的方式并不会改变接受者本身的值。

2、类型的实例指针变量

func (p *Person) ChangeAge(age int)  {
    fmt.Printf("ModifyAge")
    p.age = age
}

Go会根据Person的示例类型,转换成指针类型再拷贝,即 person.ChangeAge会变成 (&person).ChangeAge。

指针类型的接受者,如果实例对象是值,那么go会转换成指针,然后再拷贝,如果本身就是指针对象,那么就直接拷贝指针实例。因为指针都指向一处值,就能修改对象了。

5. 零值与nil(空指针)

变量声明而没有赋值,默认为零值,不同类型零值不同,例如字符串零值为空字符串;

指针声明而没有赋值,默认为nil,即该指针没有任何指向。当指针没有指向的时候,不能对(*point)进行操作包括读取,否则会报空指针异常。

func main(){
    // 声明一个指针变量 aPot 其类型也是 string
    var aPot *string
    fmt.Printf("aPot: %p %#v\n", &aPot, aPot) // 输出 aPot: 0xc42000c030 (*string)(nil)
    *aPot = "This is a Pointer"  // 报错: panic: runtime error: invalid memory address or nil pointer dereference
}

解决方法即给该指针分配一个指向,即初始化一个内存,并把该内存地址赋予指针变量,例如:

// 声明一个指针变量 aPot 其类型也是 string
    var aPot *string
    fmt.Printf("aPot: %p %#v\n", &aPot, aPot) // 输出 aPot: 0xc42000c030 (*string)(nil)

    aPot = &aVar
    *aPot = "This is a Pointer"
    fmt.Printf("aVar: %p %#v \n", &aVar, aVar) // 输出 aVar: 0xc42000e240 "This is a Pointer"
    fmt.Printf("aPot: %p %#v %#v \n", &aPot, aPot, *aPot) // 输出 aPot: 0xc42000c030 (*string)(0xc42000e240) "This is a Pointer"

或者通过new开辟一个内存,并返回这个内存的地址。

var aNewPot *int

aNewPot = new(int)
*aNewPot = 217
fmt.Printf("aNewPot: %p %#v %#v \n", &aNewPot, aNewPot, *aNewPot) // 输出 aNewPot: 0xc42007a028 (*int)(0xc42006e1f0) 217

6. 总结

  • Golang提供了指针用于操作数据内存,并通过引用来修改变量。

  • 只声明未赋值的变量,golang都会自动为其初始化为零值,基础数据类型的零值比较简单,引用类型和指针的零值都为nil,nil类型不能直接赋值,因此需要通过new开辟一个内存,或者通过make初始化数据类型,或者两者配合,然后才能赋值。

  • 指针也是一种类型,不同于一般类型,指针的值是地址,这个地址指向其他的内存,通过指针可以读取其所指向的地址所存储的值。

  • 函数方法的接受者,也可以是指针变量。无论普通接受者还是指针接受者都会被拷贝传入方法中,不同在于拷贝的指针,其指向的地方都一样,只是其自身的地址不一样。

参考:

转自:

http://www.jianshu.com/p/d23f78a3922b
http://www.jianshu.com/p/44b9429d7bef
https://blog.csdn.net/huwh_/article/details/77879970
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述