GoLang 指针初探
1. 内置类型和引用类型
Go 中内置类型包括数值类型,字符串类型和布尔类型。引用类型包括切片,映射,通道,接口和函数类型。其中,引用类型表示创建的变量包含一个指向底层数据结构的指针和一组管理底层数据结构的字段。
1.1 内置类型:数值类型
以数值类型举例,查看数值类型的指针变化情况。
var x int = 1
y := x
fmt.Printf("x: %d, &x: %p, y: %d, &y: %p\n", x, &x, y, &y)
y = 0
fmt.Printf("x: %d, &x: %p, y: %d, &y: %p\n", x, &x, y, &y)
var yp *int = &y
fmt.Printf("\ny: %d, &y: %p, yp: %p, &yp: %p, *yp: %d\n", y, &y, yp, &yp, *yp)
*yp = 1
fmt.Printf("y: %d, &y: %p, yp: %p, &yp: %p, *yp: %d\n", y, &y, yp, &yp, *yp)
var ypn *int = new(int)
fmt.Printf("\nypn: %p, &ypn: %p, *ypn: %v\n", ypn, &ypn, *ypn)
*ypn = 1
fmt.Printf("ypn: %p, &ypn: %p, *ypn: %v\n", ypn, &ypn, *ypn)
执行结果:
x: 1, &x: 0xc000014088, y: 1, &y: 0xc0000140a0
x: 1, &x: 0xc000014088, y: 0, &y: 0xc0000140a0
y: 0, &y: 0xc0000140a0, yp: 0xc0000140a0, &yp: 0xc000006030, *yp: 0
y: 1, &y: 0xc0000140a0, yp: 0xc0000140a0, &yp: 0xc000006030, *yp: 1
ypn: 0xc0000140d0, &ypn: 0xc000006038, *ypn: 0
ypn: 0xc0000140d0, &ypn: 0xc000006038, *ypn: 1
可以看出:
- 内置类型变量的赋值完成的是变量值的拷贝,对拷贝的副本的改动不影响原变量。
- 内置类型变量指针的赋值完成的是变量地址的拷贝,对地址所指向的值的改动会影响原变量。
上述“规则”也适用于内置类型在函数间的传递。
1.2 引用类型:切片
切片创建的变量包含一个指向底层数组的指针。切片的结构是指向底层数组的指针,切片长度和容量的数组。
创建切片并查看其指针变化情况:
s := []int{1, 2, 3}
for index, value := range s {
fmt.Printf("s index %d address: %p, the copy address: %p, value:%d\n", index, &s[index], &value, value)
}
fmt.Printf("s address: %p, s length: %d\n\n", &s, unsafe.Sizeof(s))
// fmt.Printf("s len: %d, cap: %d\n", len(s), cap(s))
sc := s
for index, value := range sc {
fmt.Printf("sc index %d address: %p, the copy address: %p, value:%d\n", index, &sc[index], &value, value)
}
fmt.Printf("sc address: %p, sc length: %d\n\n", &sc, unsafe.Sizeof(sc))
// fmt.Printf("sc len: %d, cap: %d\n", len(sc), cap(sc))
sp := &s
for index, value := range *sp {
fmt.Printf("sp index %d address: %p, the copy address: %p, value:%d\n", index, &(*sp)[index], &value, value)
}
fmt.Printf("sp address: %p, sp length: %d\n\n", &sp, unsafe.Sizeof(sp))
执行结果:
s index 0 address: 0xc00000a198, the copy address: 0xc000014088, value:1
s index 1 address: 0xc00000a1a0, the copy address: 0xc000014088, value:2
s index 2 address: 0xc00000a1a8, the copy address: 0xc000014088, value:3
s address: 0xc000004078, s length: 24
sc index 0 address: 0xc00000a198, the copy address: 0xc0000140a8, value:1
sc index 1 address: 0xc00000a1a0, the copy address: 0xc0000140a8, value:2
sc index 2 address: 0xc00000a1a8, the copy address: 0xc0000140a8, value:3
sc address: 0xc000004090, sc length: 24
sp index 0 address: 0xc00000a198, the copy address: 0xc0000140d0, value:1
sp index 1 address: 0xc00000a1a0, the copy address: 0xc0000140d0, value:2
sp index 2 address: 0xc00000a1a8, the copy address: 0xc0000140d0, value:3
sp address: 0xc000006030, sp length: 8
可以看出:
- 切片的大小为 24Bytes(其中,地址指针,长度整型值,容量整型值分别占 8Bytes)。
- 对切片赋值,实际上赋值的切片的副本,切片地址指针指向的底层数组并没有动。
- 赋切片地址,实际上做的是开辟了内存空间,在内存空间中存储了切片的地址值,这里开辟的内存空间大小为 8Bytes。
上述“规则”在函数的传递中也适用。
1.3 结构类型和方法
介绍引用类型之前有必要介绍下结构类型。结构类型用来描述一组数据值,数据值可以是原始,也可以是非原始的。
创建结构类型和方法并查看指针变化情况:
func (p person) eat() {
fmt.Printf("eating %s\n", p.food)
// fmt.Printf("p: %p, &p: %p, p.sex: %p, p.age: %p, p.food: %p\n", p, &p, &(*p).sex, &(*p).age, &(*p).food)
fmt.Printf("p: %v, &p: %p, p.sex: %p, p.age: %p, p.food: %p\n", p, &p, &p.sex, &p.age, &p.food)
}
func (sp *super_person) eat() {
fmt.Printf("%s is eating %s\n", sp.name, sp.food)
fmt.Printf("sp: %p, &sp: %p, sp.sex: %p, sp.age: %p, sp.food: %p\n", sp, &sp, &(*sp).sex, &(*sp).age, &(*sp).food)
}
chunqiu := person{"male", 27, "dogLiang"}
fmt.Printf("chunqiu address: %p, chunqiu.sex: %p, chunqiu.age: %p, chunqiu.food: %p\n", &chunqiu, &chunqiu.sex, &chunqiu.age, &chunqiu.food)
chunqiu.eat()
(&chunqiu).eat()
p := super_person{
person: person{
sex: "male",
age: 27,
food: "catLiang",
},
name: "chunqiu",
}
fmt.Printf("\nchunqiu address: %p, chunqiu.sex: %p, chunqiu.age: %p, chunqiu.food: %p, chunqiu.name: %p\n", &p, &p.sex, &p.age, &p.food, &p.name)
p.eat()
(&p).eat()
执行结果:
chunqiu address: 0xc0000723c0, chunqiu.sex: 0xc0000723c0, chunqiu.age: 0xc0000723d0, chunqiu.food: 0xc0000723d8
eating dogLiang
p: {male 27 dogLiang}, &p: 0xc0000723f0, p.sex: 0xc0000723f0, p.age: 0xc000072400, p.food: 0xc000072408
eating dogLiang
p: {male 27 dogLiang}, &p: 0xc000072450, p.sex: 0xc000072450, p.age: 0xc000072460, p.food: 0xc000072468
chunqiu address: 0xc000046040, chunqiu.sex: 0xc000046040, chunqiu.age: 0xc000046050, chunqiu.food: 0xc000046058, chunqiu.name: 0xc000046068
chunqiu is eating catLiang
sp: 0xc000046040, &sp: 0xc000006030, sp.sex: 0xc000046040, sp.age: 0xc000046050, sp.food: 0xc000046058
chunqiu is eating catLiang
sp: 0xc000046040, &sp: 0xc000006038, sp.sex: 0xc000046040, sp.age: 0xc000046050, sp.food: 0xc000046058
可以看出:
- 值接收者和指针接收者方法都可以接收值和指针类型的结构体变量,这是因为 Go 编译器在背后做了“类型转换”。
- 值接收者方法接收到的是结构体的副本,指针接收者方法接收到的是结构体的地址。前者不会改变原结构体的数据,后者则会改变。使用值接收者方法和指针接收者方法主要取决于传递类型的本质。
1.4 引用类型:接口
Go 语言中的接口是引用类型,具体的实现由方法定义。
给 eat() 方法添加 eater 接口:
type eater interface {
eat()
}
func NewEater(e eater) {
fmt.Printf("e: %p, &e: %p\n", e, &e)
e.eat()
}
var handsome_boy eater = chunqiu
fmt.Printf("chunqiu address: %p, chunqiu.sex: %p, chunqiu.age: %p, chunqiu.food: %p\n", &chunqiu, &chunqiu.sex, &chunqiu.age, &chunqiu.food)
fmt.Printf("handsome_boy address: %p, handsome value: %v, handsome length: %d\n", &handsome_boy, handsome_boy, unsafe.Sizeof(handsome_boy))
NewEater(handsome_boy)
var bold_boy eater = &p
fmt.Printf("\nchunqiu address: %p, chunqiu.sex: %p, chunqiu.age: %p, chunqiu.food: %p, chunqiu.name: %p\n", &p, &p.sex, &p.age, &p.food, &p.name)
fmt.Printf("bold_boy address: %p, bold_boy value: %p, bold length: %d\n", &bold_boy, bold_boy, unsafe.Sizeof(bold_boy))
NewEater(bold_boy)
执行结果:
chunqiu address: 0xc0000723c0, chunqiu.sex: 0xc0000723c0, chunqiu.age: 0xc0000723d0, chunqiu.food: 0xc0000723d8
handsome_boy address: 0xc00003a240, handsome value: {male 27 dogLiang}, handsome length: 16
e: %!p(main.person={male 27 dogLiang}), &e: 0xc00003a250
eating dogLiang
p: {male 27 dogLiang}, &p: 0xc000072420, p.sex: 0xc000072420, p.age: 0xc000072430, p.food: 0xc000072438
chunqiu address: 0xc000046040, chunqiu.sex: 0xc000046040, chunqiu.age: 0xc000046050, chunqiu.food: 0xc000046058, chunqiu.name: 0xc000046068
bold_boy address: 0xc00003a270, bold_boy value: 0xc000046040, bold length: 16
e: 0xc000046040, &e: 0xc00003a280
chunqiu is eating catLiang
sp: 0xc000046040, &sp: 0xc000006030, sp.sex: 0xc000046040, sp.age: 0xc000046050, sp.food: 0xc000046058
可以看出:
- 接口类型是引用类型。
- 接口方法传值传递的是引用类型的副本,传指针传递的是引用类型的地址。
- 内嵌结构体提升的数据,可被外部结构体直接访问,通过访问接口方法可实现继承和多态。
进一步的,将地址和值分别赋给接口 handsome_boy 和 bold_boy 如下:
var handsome_boy eater = &chunqiu
var bold_boy eater = p
执行结果:
src\Blog\go_variable.go:163:6: cannot use p (type super_person) as type eater in assignment:
super_person does not implement eater (eat method has pointer receiver)
报错了,从报错信息可以看到不能将值赋值给实现接口指针接收者的方法,这和前面的指针接收者接收的参数是不一样的。列出接口方法和方法的接收参数如下:
接口方法 | 类型 |
---|---|
值接收者 | T / *T |
指针接收者 | *T |
方法 | 类型 |
---|---|
值接收者 | T / *T |
指针接收者 | T / *T |
更新:函数返回指针类型。
package main
import "fmt"
func translet_string(sp *string, st *string) *string {
fmt.Printf("\n&sp: %v, sp: %v\n", &sp, sp)
fmt.Printf("&st: %v, st: %v, *st: %v\n", &st, st, *st)
if sp == nil {
sp = st
}
return sp
}
func main() {
var s *string
fmt.Printf("&s: %v, s: %v\n", &s, s)
var str string = "hxia"
fmt.Printf("&str: %v, str: %v\n", &str, str)
st := translet_string(s, &str)
fmt.Printf("\n&st: %v, st: %v, *st: %v\n", &st, st, *st)
}
&s: 0xc00000e028, s: <nil>
&str: 0xc000010230, str: hxia
&sp: 0xc00000e040, sp: <nil>
&st: 0xc00000e048, st: 0xc000010230, *st: hxia
&st: 0xc00000e038, st: 0xc000010230, *st: hxia
GoLang 指针初探的更多相关文章
- TODO:Golang指针使用注意事项
TODO:Golang指针使用注意事项 先来看简单的例子1: 输出: 1 1 例子2: 输出: 1 3 例子1是使用值传递,Add方法不会做任何改变:例子2是使用指针传递,会改变地址,从而改变地址. ...
- Golang指针与unsafe
前言 我们知道在golang中是存在指针这个概念的.对于指针很多人有点忌惮(可能是因为之前学习过C语言),因为它会导致很多异常的问题.但是很多人学习之后发现,golang中的指针很简单,没有C那么复杂 ...
- C++ | 智能指针初探
智能指针初探 在 c/c++ 语言中有一种特殊的类型--指针类型. 指针作为实体,是一个用来保存一个内存地址的计算机语言中的变量.它可以直接对内存地址中的数据进行操作,是一种非常灵活的变量.指针被誉为 ...
- 把《c++ primer》读薄(4-2 c和c++的数组 和 指针初探)
督促读书,总结精华,提炼笔记,抛砖引玉,有不合适的地方,欢迎留言指正. 问题1.我们知道,将一个数组赋给另一个数组,就是将一个数组的元素逐个赋值给另一数组的对应元素,相应的,将一个vector 赋给另 ...
- Golang指针
学过C语言的老司机都知道,指针就是一个变量,用于存储另一个变量的内存地址. 那么什么是变量呢?在现代计算机体系结构中所有的需要执行的信息代码都需要存储在内存中,为了管理存储在内存的数据,内存是划分为不 ...
- Golang - 指针与引用
Golang有指针 , 那么一切数据都是值传递吗 ? 都需要用户进行指针传递吗, 其实不然, 对于Go语言, 虽然有指针, 但是其也有引用传递. 是不是很绕, 因为引用传递就是指针传递哇 . 我们 ...
- GO开发[一]:golang开发初探
一.Golang的安装 1.https://dl.gocn.io/ (国内下载地址) 2.https://golang.org/dl/ (国外下载地址) 3.现在studygolang中文网也可以了h ...
- Golang指针基本介绍及使用案例
一.指针的相关概念说明 变量:是基本类型,变量存的就是值,也叫值类型 地址:用于引用计算机的内存地址,可理解为内存地址的标签,通俗一点讲就是一间房在小区里的门牌号.如下图① 指针:指针变量存的是一个地 ...
- golang 指针在struct里的应用
type aa struct { b *int c string } func main() { var data int = 0 var ip *int /* 声明指针变量 */ ip = & ...
- c++指针初探
业余时间准备重温一下c++,因为在阅读Android源码到native层的时候感觉有点吃力,只是在大学时候很不用心的学过c++,所以重温下以便打好一些编程基础知识,本篇就很简单的对c++的指针做初步的 ...
随机推荐
- LLM面面观之LLM复读机问题及解决方案
1. 背景 关于LLM复读机问题,本qiang~在网上搜刮了好几天,结果是大多数客观整理的都有些支离破碎,不够系统. 因此,本qiang~打算做一个相对系统的整理,包括LLM复读机产生的原因以及对应的 ...
- django-celery-beat插件使用
该插件从 Django 管理界面管理celery的定期任务,您可以在其中动态****创建.编辑和删除定期任务以及它们的运行频率. django-celery-beat提供了几种添加定时或周期性任务的方 ...
- hszxoj ATM [tarjan]
hszxoj ATM 题目描述:\(Siruseri\) 城中的道路都是单向的.不同的道路由路口连接.按照法律的规定, 在每个路口都设立了一个 \(Siruseri\) 银行的 \(ATM\) 取款机 ...
- Javascript Ajax总结——其他跨域技术之图像Ping和JSONP
在CORS出现之前,为实现跨域Ajax通信,开发人员利用DOM中能够执行跨域请求的功能,在不依赖XHR对象的情况下也能发送某种请求.1.图像Ping这里使用<img>标签.一个网页可以从任 ...
- 有意思,我的GitHub账号值$23806.2,快来试试你的?
睡不着,看到一个有意思的网站:Estimate Github Worth Generator. 它可以用来估算 GitHub 账号的价值.马上试了一下. 我的账号估值:$23806.2 操作很简单,点 ...
- 解决C#连接MySQL数据库报错 MySqlConnector
如果主机不支持 SSL 连接,则不会使用 SSL 连接. 连接不上. 解决方案:在连接字符串后添加 sslmode = none. <add key="connstring" ...
- Python——第五章:hashlib模块
hashlib 模块(hash)算法 hashlib 模块是 Python 中用于加密散列(hash)算法的模块.它提供了对常见的哈希算法(如MD5.SHA-1.SHA-256等)的支持,使得开发者可 ...
- 9.mysql的数据迁移到es中
背景 从开发的角度说,就是老板叫我用es了,没那么多为什么,爸爸说了算 从业务角度,mysql已经不能满足我对全文检索的需求了.我需要检索某一个字段包含"圣诞节刚刚过去"这一字符串 ...
- 文心一言 VS 讯飞星火 VS chatgpt (178)-- 算法导论13.4 1题
一.用go语言,在执行 RB-DELETE-FIXUP 之后,证明:树根一定是黑色的. 文心一言: 在证明 RB-DELETE-FIXUP 之后,树根一定是黑色的的过程中,我们首先需要理解红黑树的性质 ...
- 使用 C# 在Word中插入图表
Word中的图表功能将数据可视化地呈现在文档中.这为展示数据和进行数据分析提供了一种方便且易于使用的工具,使作者能够以直观的方式传达信息.要通过C#代码来实现在Word中绘制图表,可以借助 Spire ...