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. 内置类型变量指针的赋值完成的是变量地址的拷贝,对地址所指向的值的改动会影响原变量。

上述“规则”也适用于内置类型在函数间的传递。

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

可以看出:

  1. 切片的大小为 24Bytes(其中,地址指针,长度整型值,容量整型值分别占 8Bytes)。
  2. 对切片赋值,实际上赋值的切片的副本,切片地址指针指向的底层数组并没有动。
  3. 赋切片地址,实际上做的是开辟了内存空间,在内存空间中存储了切片的地址值,这里开辟的内存空间大小为 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

可以看出:

  1. 值接收者和指针接收者方法都可以接收值和指针类型的结构体变量,这是因为 Go 编译器在背后做了“类型转换”。
  2. 值接收者方法接收到的是结构体的副本,指针接收者方法接收到的是结构体的地址。前者不会改变原结构体的数据,后者则会改变。使用值接收者方法和指针接收者方法主要取决于传递类型的本质。

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

可以看出:

  1. 接口类型是引用类型。
  2. 接口方法传值传递的是引用类型的副本,传指针传递的是引用类型的地址。
  3. 内嵌结构体提升的数据,可被外部结构体直接访问,通过访问接口方法可实现继承和多态。

进一步的,将地址和值分别赋给接口 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 指针初探的更多相关文章

  1. TODO:Golang指针使用注意事项

    TODO:Golang指针使用注意事项 先来看简单的例子1: 输出: 1 1 例子2: 输出: 1 3 例子1是使用值传递,Add方法不会做任何改变:例子2是使用指针传递,会改变地址,从而改变地址. ...

  2. Golang指针与unsafe

    前言 我们知道在golang中是存在指针这个概念的.对于指针很多人有点忌惮(可能是因为之前学习过C语言),因为它会导致很多异常的问题.但是很多人学习之后发现,golang中的指针很简单,没有C那么复杂 ...

  3. C++ | 智能指针初探

    智能指针初探 在 c/c++ 语言中有一种特殊的类型--指针类型. 指针作为实体,是一个用来保存一个内存地址的计算机语言中的变量.它可以直接对内存地址中的数据进行操作,是一种非常灵活的变量.指针被誉为 ...

  4. 把《c++ primer》读薄(4-2 c和c++的数组 和 指针初探)

    督促读书,总结精华,提炼笔记,抛砖引玉,有不合适的地方,欢迎留言指正. 问题1.我们知道,将一个数组赋给另一个数组,就是将一个数组的元素逐个赋值给另一数组的对应元素,相应的,将一个vector 赋给另 ...

  5. Golang指针

    学过C语言的老司机都知道,指针就是一个变量,用于存储另一个变量的内存地址. 那么什么是变量呢?在现代计算机体系结构中所有的需要执行的信息代码都需要存储在内存中,为了管理存储在内存的数据,内存是划分为不 ...

  6. Golang - 指针与引用

    ​ Golang有指针 , 那么一切数据都是值传递吗 ? 都需要用户进行指针传递吗, 其实不然, 对于Go语言, 虽然有指针, 但是其也有引用传递. 是不是很绕, 因为引用传递就是指针传递哇 . 我们 ...

  7. GO开发[一]:golang开发初探

    一.Golang的安装 1.https://dl.gocn.io/ (国内下载地址) 2.https://golang.org/dl/ (国外下载地址) 3.现在studygolang中文网也可以了h ...

  8. Golang指针基本介绍及使用案例

    一.指针的相关概念说明 变量:是基本类型,变量存的就是值,也叫值类型 地址:用于引用计算机的内存地址,可理解为内存地址的标签,通俗一点讲就是一间房在小区里的门牌号.如下图① 指针:指针变量存的是一个地 ...

  9. golang 指针在struct里的应用

    type aa struct { b *int c string } func main() { var data int = 0 var ip *int /* 声明指针变量 */ ip = & ...

  10. c++指针初探

    业余时间准备重温一下c++,因为在阅读Android源码到native层的时候感觉有点吃力,只是在大学时候很不用心的学过c++,所以重温下以便打好一些编程基础知识,本篇就很简单的对c++的指针做初步的 ...

随机推荐

  1. winform中也可以这样做数据展示✨

    1.前言 在做winform开发的过程中,经常需要做数据展示的功能,之前一直使用的是gridcontrol控件,今天想通过一个示例,跟大家介绍一下如何在winform blazor hybrid中使用 ...

  2. 7、If分支语句

    1.程序的流程结构 程序的流程控制结构一共有三种: 顺序结构 选择结构 循环结构. 顺序结构: 从上向下 逐行执行 选择结构:条件满足,某些代码才会执行.0-1次 分支语句: if,switch,se ...

  3. 解决GET http://192.168.41.103:9528/sockjs-node/info?t=1678639328658 net::ERR_CONNECTION_TIMED_OUT

    问题现象 解决办法 找到依赖/node_modules/sockjs-client/dist/sockjs.js注释掉下面的一行代码

  4. DataGrip给DateTime类型字段赋值当前系统默认时间

    CURRENT_TIMESTAMP alter table 表名 modify column update_time DATETIME NULL DEFAULT CURRENT_TIMESTAMP O ...

  5. Java使用线程池和缓存提高接口QPS

    1.什么是QPS? QPS:Queries Per Second意思是"每秒查询率",是一台服务器每秒可以相应的查询次数,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标 ...

  6. 实用指南:打造卓越企业BI实施解决方案

    前言 随着大数据时代的到来,商业智能(BI)工具变得非常重要.一个全面的商业智能方案可以支持数据驱动的决策并提高决策效率,同时还可以准确反映企业运行状态,为企业持续增长提供新的动力.本文小编将为大家介 ...

  7. 华企盾DSC编辑文件不加密常见问题

    1.先查看客户端日志主进程是否是加密进程.日志中是不是勾选智能半透明.加密类型是否有添加 2.用procmon监控保存的文件找出writefile的进程是否有添加,进程树是否有父进程,加密类型是否正确 ...

  8. 制造业工厂生产管理MES系统中的设备管理模块

    制造业工厂万界星空科技生产管理MES系统中的设备管理模块介绍: 随时工厂数字化建设的大力推进,设备管理的效率得到了很大的提升,特别是作为机加工企业,设备是整个企业非常重要的核心资产. 1.MES设备管 ...

  9. vscode快速配置汇编环境

    微机原理的课程需要,简单快速记录环境的搭建 找到并安装插件masm. MASM/TASM的汇编工具默认是tasm这样就无法在vscode终端进行debug,打开插件设置如下修改: 测试代码实现小写字母 ...

  10. [Luogu 4998 信号塔] 题解报告

    估计没人看的简化版题意: 给定一个数轴,以及数轴上的 \(n\) 个点(这些点可能坐落在同一坐标上),第 \(i\) 个点的坐标为 \(a_i\) .现在要在数轴上找 \(k\) 个点,第 \(i\) ...