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++的指针做初步的 ...
随机推荐
- 数字孪生融合GIS系统将为交通领域带来什么改变?
随着科技的不断发展,数字孪生和GIS技术正成为交通领域的新宠.数字孪生是指通过数学建模.数据采集和实时仿真等技术手段,将实体世界与数字世界相互关联,形成一个全新的虚拟系统.而GIS(地理信息系统)则是 ...
- 【C#】【IO】【实例】接上一个统计的新功能
先用Python来创建多层级文件夹: import os root_path = r"C:\Users\Desktop\文案整理\Practice" for item in ran ...
- NC65单据模板公式使用
单据模板公式使用 (一) 公式使用场景 用户使用产品时,往往对单据上的字段取值有各种不同的需求.为此单据模板提供 了模板公式功能,可以让实施顾问或者用户通过配置各种公式,并且不用修改代码,从 而满足用 ...
- ElasticSearch之cat health API
命令样例如下: curl -X GET "https://localhost:9200/_cat/health?v=true&pretty" --cacert $ES_HO ...
- 从传统行业到半导体行业开发(YMS,DMS,EAP,EDA)
一线开发人: 今天半导体YMS 项目快要收尾了,我的心情有点高兴,多年来我一直保持着写作的习惯,总是想写一些什么,今天但是又不知道从何说起.自己从传统的行业转向左半导体行业开发.从电*机如软件开发到电 ...
- GetView介绍 以及 GetxController生命周期
etView 只是对已注册的 Controller 有一个名为 controller 的getter的 const Stateless 的 Widget,如果我们只有单个控制器作为依赖项,那我们就可以 ...
- 4种Python中基于字段的不使用元类的ORM实现方法
本文分享自华为云社区<Python中基于字段的不使用元类的ORM实现>,作者: 柠檬味拥抱 . 不使用元类的简单ORM实现 在 Python 中,ORM(Object-Relational ...
- KubeEdge在边缘计算领域的安全防护及洞察
摘要:着重介绍Kubeedge在安全防护方面的实践,并介绍OpenSSF在开源软件安全方面的计划与目标. 本文分享自华为云社区<KubeEdge在边缘计算领域的安全防护及洞察>,作者:华为 ...
- NDPQ(NDP+PQ),定义分布式数据库新方向
摘要:云服务提供商构建新的云原生关系数据库系统,专门为云基础架构设计,通常采用将计算和存储分离到独立扩展的分布式层的设计. 本文分享自华为云社区<性能提升100倍!GaussDB(for MyS ...
- Go语言逆向技术:恢复函数名称算法
摘要:在对程序做安全审计.漏洞检测时,通常都需要对程序做逆向分析,本文在没有符号表的情况下,提出了一种恢复函数名称的算法,方便对go语言二进制文件进行逆向分析,提升分析效率. 本文分享自华为云社区&l ...