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++的指针做初步的 ...
随机推荐
- 项目实战接口开发SpringBoot
目录 一.springboot官方demo开发 二.使用SpringBoot开发get方法接口 三.一个要求携带cookie信息访问的get接口开发 四.需要携带参数的get请求两种开发方式 4.1 ...
- 深度学习前沿 | 利用GAN预测股价走势
本文是对于medium上Boris博主的一篇文章的学习笔记,这篇文章中利用了生成对抗性网络(GAN)预测股票价格的变动,其中长短期记忆网络LSTM是生成器,卷积神经网络CNN是鉴别器,使用贝叶斯优化( ...
- 关于Secure Hash Algorithm加密算法
一.概述 SHA(Secure Hash Algorithm)加密算法是一种广泛应用的密码散列函数,由美国国家安全局(NSA)设计,用于保障数据的安全性和完整性.SHA算法经历了多个版本的更新,目前主 ...
- 前端异步编程 —— Promise对象
在前端编程中,处理一些简短.快速的操作,在主线程中就可以完成. 但是,在处理一些耗时比较长以至于比较明显的事情,比如读取一个大文件或者发出一个网络请求,就需要异步编程来实现,以避免只用主线程时造成页面 ...
- Educational Codeforces Round 159 总结
最失败的一集. C 开题顺序搞错,不小心先开了C,以为是A.还好C不难. 题意大概是在给定的数组最后添一个数(所有数两两不同),再自定义一个数 \(k\) ,数组中每个数可以加上若干个 \(k\) , ...
- ElasticSearch之cat templates API
命令样例如下: curl -X GET "https://localhost:9200/_cat/templates?v=true&pretty" --cacert $ES ...
- 【2】从零玩转OSS阿里云存储服务之Java代码操作-2-cong-ling-wan-zhuan-oss-a-li-yun-cun-chu-fu-wu-zhi-java-dai-ma-cao-zuo
title: [2]从零玩转OSS阿里云存储服务之Java代码操作 date: 2021-06-09 17:37:14.486 updated: 2021-12-26 17:43:12.779 url ...
- 神经网络优化篇:详解梯度检验(Gradient checking)
梯度检验 梯度检验帮节省了很多时间,也多次帮发现backprop实施过程中的bug,接下来,看看如何利用它来调试或检验backprop的实施是否正确. 假设的网络中含有下列参数,\(W^{[1]}\) ...
- [LitCTF 2023]1zjs
打开环境: 一个魔方♂ 习惯性打开 F12,之后发现有个./dist/index.umd.js Ctrl u 打开 把这个蓝色的点开 0.o? 这里眼神好的话就能看到有个" /f@k3f1a ...
- 《RAPL: A Relation-Aware Prototype Learning Approach for Few-Shot Document-Level Relation Extraction》阅读笔记
代码 原文地址 预备知识: 1.什么是元学习(Meta Learning)? 元学习或者叫做"学会学习"(Learning to learn),它是要"学会如何学 ...