GO 中的指针?

news2024/11/15 23:20:32

本文也主要聊聊在 GO 中的指针和内存,希望对你有点帮助

如果你学习过 C 语言,你就非常清楚指针的高效和重要性

使用 GO 语言也是一样,项目代码中,不知道你是否会看到函数参数中会传递各种 map,slice ,自定义的结构等等

这些参数数据量如果比较小的话就算了,可偏偏工作中你能看到很多这种数据量大的结构,也是这样以传值的方式就这样放到函数参数上了

而且这个数据量大的结构可能会来来回回传递很多次,就会导致同一份数据被活生生的拷贝了 N 次,对系统的内存资源真是妥妥的浪费啊

必须要学会指针的使用,能够让你写的服务性能会更加的好,那么,我们开始吧

以下分别从如下三个方面来聊聊

  • 基本的变量和指针是什么?
  • 指针的那点事

基本的变量和指针是什么?

首先,xdm 对于变量有没有一个很清晰的认知?无论我们是在写 C 语言还是 GO 语言的时候,我们都离不开定义变量

可是我们知道这些变量实际上都是对应这内存的某一个地址,这一个地址上存储了我们需要的数据

那么我们访问变量的时候,就会去找到这一个地址,然后取出数据

那么直接记录数据存放的地址不就好了吗?

咱们的内存地址是十六进制表示的,你确定你可以把每一个变量的地址都记得下来?因此才会引入一种占位符,他就是变量

对我们人来说,让你记录一个 变量 num 方便呢?还是让你去记录一个 0xFAEBF9C7 的地址方便呢?变量的好处就不言而喻了

例如,定义了一个变量 num

那么指针又是什么呢?

实际上指针他也是一种变量,只不过,他存放的是其他变量的地址,会觉得绕吗?

简单来看看有这么一块内存

例如存放了一个整型的数据:var num int = 200

这个时候有一个指针变量指向 num 的地址:var ptr * int = &num

在内存中,可能是这样的

通过上面的内容,我们就可以很清晰的知道,变量他是一个标识符,对应着实际数据的地址,让我们很方便的去拿到具体的数据

指针他也是一个变量,只不过用于存放其他变量的地址,这样能够让我们更加低成本的找到指针指向的数据

指针的那点事

那么,我们继续来细聊指针

首先结论先行

  1. 默认声明一个指针,未初始化的时候,默认零值为 nil,要使用指针的话,请初始化,或者让他指向一个变量的地址
  1. 指针一般占用的空间是 4 个字节或者 8个字节,根据你的系统是32 位的还是 64 位的,指针占用的字节也不尽相同
  1. 函数中的传参,传递指针,那也是一个拷贝,指针的拷贝而已
  1. 指针他也是一种变量类型,只不过存放的是别的变量的地址,那么指针当然也是可以存放别的指针变量的地址
  1. C 语言中,咱们可以通过操作指针偏移,去移动指针,但是 Go 中不支持
  1. Go 中的普通指针和 unsafe 包里面的 Pointer 有啥不一样

用指针为啥高效?

首先先来简单的聊聊这个高效的问题

例如还是上面的图,给一个参数中传入指针,实际上也是传入指针的拷贝,只不过这个拷贝,指向的也是原有指针指向的地址

那么对于内存消耗来说,如果是 64 位机器,拷贝的这一个指针就只占用 8 个字节

图中只是一个简单的例子,如果指针指向的是一块比较大的内存 ,例如这片内存为 M

那么如果传参的时候,不是传指针,那么就会先对这一片内存进行拷贝,传到函数中,那么这个时候,就会多占用一些内存空间了,例如总共占用 M+M

给一个未初始化的指针赋值,会 panic

虽然说使用指针方便高效,但是也要注意,使用的时候记得初始化,否则轻轻松松就会 panic

例如 给一个未初始化的指针进行赋值操作,你的程序就会崩溃 invalid memory address or nil pointer dereference

初始化可以这样做:

  • 给指针 new() 一下,分配一块内存
  • 或者直接让指针指向某一个变量的地址

事物都是有两面性的,只有我们能够看到事物的全貌,我们才能更好的理解和使用他

自然,我们也可以通过解引用的方式去修改指针指向地址上的值

func main(){
   var a int = 100
   ptr := &a
   fmt.Println("ptr == ", *ptr)

   *ptr = 200
   fmt.Println("ptr == ", *ptr)
}

此处的 *ptr 就相当于 上述的标识符 a ,给 *ptr 赋值,就相当于 给 a 赋值,是一个道理

二级指针

指针存放的是其他变量的地址,那么自然也是可以存放其他指针变量的地址的,这样的指针就可以称之为二级指针

当然,如法炮制,就会有多级指针,使用这样的指针的时候,一定要将其弄清楚,否则你会被多级指针搞的云里雾里

func main() {
   var a int = 100

   ptr := &a
   fmt.Println("*ptr == ", *ptr)
   fmt.Println("&ptr == ", &ptr)

   pptr := &ptr
   fmt.Println("**pptr == ", **pptr)
   fmt.Println("pptr == ", &pptr)

   **pptr = 200
   fmt.Println("**pptr == ", **pptr)
}

此处就可以看到,实际上一级指针和二级指针也没有啥太大的区别,仅仅是二级指针,存放的是一级指针的地址,一级指针存放的是其他变量的地址

那么如果对二级指针解引用的话,就需要先从二级指针处找到一级指针的地址,再找到具体变量的地址

因此 **ptr 也就相当于是 标识符 a,对 **pptr 赋值,就相当于是给 a 赋值

尝试指针运算??

C 语言中,我们知道,指针是可以这样玩的,例如一个指针指向的是一个 int 类型的数组,那么我们从指针的第一个元素开始偏移,就可以是 ptr+1

ptr +1 在这里表示的意思是,让 ptr 向下移动一个 int 类型的地址,而不是数值上的 +1 而已

那么,如果是 Go 语言,你就不能这么玩了

sli := []int{0,1,2,3}
ptr := &sli
fmt.Println(ptr)
ptr+1    // 很显然是不行的

可以中 GOLAND 的提示中可以看到,Go 语言中是不允许我们直接对指针这么干的,此处需要注意哈

自然 Go 语言 中不同类型的也是不可以直接赋值的,在这里就不在过多的演示了

Go 中的指针和 unsafe 包里的指针

在 C 语言中,不同类型指针是可以相互转换的,不同类型的指针也是可以进行比较的,那么你觉得你在 Go 语言里面可以吗?

显然是不行,正是因为不行,所以就避免了对指针了解不深入的初学者犯错,就可以尽量减少程序员对指针的不安全使用

因此 Go 中的指针,他是一个安全指针

但是 Go 语言中也给我们提供了一个 unsafe 包,这个包就可以让我们玩一些花的

例如,我们想将一个 int 类型的数据,转成 int64 类型的数据,显然通过直接指针赋值或者变量赋值的方式是不行的

但是我们通过 unsafe 包中的 Pointer 就可以做到

num := 200
var num64 int64

ptr := (*int64)(unsafe.Pointer(&num))
num64 = *ptr
fmt.Println("num64 == ",num64)

这里可以看到我们将 *int 转成了 unsafe.Pointer,再将 unsafe.Pointer 转成 *int64

但是 unsafe.Pointer 一样是不能直接进行数学运算的,如果我们需要让他进行数学运算,那么我们还需要将 unsafe.Pointer 转换成 uintptr

例如这样:

func main(){
    a := [3]int{1,2,3}
    res := *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&a)) + unsafe.Sizeof(a[0])))
    fmt.Println(res)
}

此处我们就可以看到先是将 a 的地址 &a( 是 *int 类型) 的转换成 unsafe.Pointer ,再将 unsafe.Pointer 转换成 uintptr

此时 uintptr 就可以进行数学运算,我们偏移 a 数组中一个元素的地址

偏移之后,将 uintptr 转换成 unsafe.Pointer ,再将 unsafe.Pointer 转换成 *int ,最终对取到的指针解引用,得到一个 int 类型的数据 , 即 2 ,也就是将 a 数组从第一位向后偏移一位,得到 2 没有毛病

通过上述案例,我们可以知道,如果期望实现 C 语言那样的玩法,那么就需要进行如下转换

普通类型的指针 与 unsafe.Pointer 相互转换

unsafe.Pointer 与 uintptr 相互转换

再回过头来看 unsafe 包中的定义

type ArbitraryType int

type Pointer *ArbitraryType

实际上也非常简单,其中 ArbitraryType 表示的意思也就是任意类型

提醒一波,使用指针偏移的时候,需要注意你需要偏移的结构是什么样的

例如,结构体和数组在做偏移的时候,使用的偏移字节计算方式就不一样

可以看到,我们例子中,使用数组的方式是使用 Sizeof ,如果是结构体中的成员进行指针偏移的时候,就需要使用 Offsetof

至此,对于 golang 中的指针就聊到这里,关于 unsafe 包中的指针操作还有很多细节和知识,后续有机会可以接着聊,希望本次文章对你有帮助

感谢阅读,欢迎交流,点个赞,关注一波 再走吧

欢迎点赞,关注,收藏

朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力

技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

我是阿兵云原生,欢迎点赞关注收藏,下次见~

文中提到的技术点,感兴趣的可以查看这些文章:

  • 你真的知道 GO 中 nil 代表什么吗?
  • 微服务线上问题排查困难?不知道问题出在哪一环?那是你还不会分布式链路追踪
  • 【切片】基础不扎实引发的问题

可以进入地址进行体验和学习:https://xxetb.xet.tech/s/3lucCI

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1060731.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用正则表达式批量修改函数

贪心匹配,替换中的$1代表括号中的第一组。 使用[\s\S\r]代表所有字符,同时加个问号代表不贪心匹配:

【RP-RV1126】烧录固件使用记录

文章目录 烧录完整固件进入MASKROM模式固件烧录升级中:升级完成: 烧录部分进入Loader模式选择文件切换loader模式 烧录完整固件 完整固件就是update.img包含了所有的部件,烧录后可以直接运行。 全局编译:./build.sh all生成固件…

Java数据结构————优先级队列(堆)

一 、 优先级队列 有些情况下,操作的数据可能带有优先级, 一般出队列时,可能需要优先级高的元素先出队列。 数据结构应该提供两个最基本的操作, 一个是返回最高优先级对象, 一个是添加新的对象。 这种数据结构就是优…

(一)正点原子STM32MP135移植——准备

一、简述 使用板卡:正点原子的ATK-DLMP135 V1.2 从i.mx6ull学习完过来,想继续学习一下移植uboot和内核的,但是原子官方没有MP135的移植教程,STM32MP157的移植教程用的又是老版本的代码,ST官方更新后的代码不兼容老版本…

微信小程序button按钮去除边框去除背景色

button边框 去除button边框 在button上添加plain“true”在css中添加button.avatar-wrapper {background: none}用于去除button背景色在css中添加button.avatar-wrapper[plain]{ border:0 }用于去除button边框

数组结构与算法

文章目录 数据结构与算法稀疏数组sparse队列单向链表双向链表单向环形列表:CircleSingleLinkedList栈递归排序算法快速排序思路 树赫夫曼树 (HuffmanTree)二叉排序树(Binary sort tree)构建二叉树遍历二叉树 平衡二叉树…

Doris数据库BE——冷热数据方案

新的冷热数据方案是在整合了存算分离模型的基础上建立的,其核心思路是:DORIS本地存储作为热数据的载体,而外部集群(HDFS、S3等)作为冷数据的载体。数据在导入的过程中,先作为热数据存在,存储于B…

[架构之路-228]:计算机硬件与体系结构 - 硬盘存储结构原理:如何表征0和1,即如何存储0和1,如何读数据,如何写数据(修改数据)

目录 前言: 一、磁盘的盘面组成 1.1 磁盘是什么 ​编辑1.2 磁盘存储介质 1.3 磁盘数据的组织 1.3.1 分层组织:盘面号 1.3.2 扇区和磁道 1.3.3 数据 1.3.4 磁盘数据0和1的存储方式 1.3.5 磁盘数据0和1的修正方法 1.3.6 磁盘数据0和1的读 二、…

一键AI高清换脸——基于InsightFace、CodeFormer实现高清换脸与验证换脸后效果能否通过人脸比对、人脸识别算法

前言 AI换脸是指利用基于深度学习和计算机视觉来替换或合成图像或视频中的人脸。可以将一个人的脸替换为另一个人的脸,或者将一个人的表情合成到另一个人的照片或视频中。算法常常被用在娱乐目上,例如在社交媒体上创建有趣的照片或视频,也有用于电影制作、特效制作、人脸编…

华为云云耀云服务器L实例评测|Ubuntu云锁防火墙安装搭建使用

华为云云耀云服务器L实例评测|Ubuntu安装云锁防火墙对抗服务器入侵和网络攻击 1.前言概述 华为云耀云服务器L实例是新一代开箱即用、面向中小企业和开发者打造的全新轻量应用云服务器。多种产品规格,满足您对成本、性能及技术创新的诉求。云耀云服务器L…

【VUE·疑难问题】定义 table 中每行的高度(使用 element-UI)

一、如何定义 table 中每一行的 height &#xff1f; 1.table例子 <!-- 二、table --><div style"overflow: hidden;display: block;height: 68vh;width: 100%;"><el-table stripe show-header style"width: 100%" :data"tableData&q…

nodejs+vue养老人员活体鉴权服务系统elementui

系统 统计数据&#xff1a;统计报表、人员台账、机构数据、上报数据、核验报表等&#xff0c;养老人员活体鉴权服务是目前国家养老人员管理的重要环节&#xff0c;主要为以养老机构中养老人员信息为基础&#xff0c;每月进行活体鉴权识别并统计数据为养老补助等管理。前端功能&…

开箱报告,Simulink Toolbox库模块使用指南(七)——S-Fuction Builter模块

S-Fuction Builter S-Fuction Builter模块&#xff0c;Mathworks官方Help对该部分内容的说明如下所示。 DFT算法的原理讲解和模块开发在前几篇文章中已经完成了&#xff0c;本文介绍如何使用S-Fuction Builter模块一步到位地自动开发DFT算法模块&#xff0c;包括建立C MEX S-Fu…

水浒传数据集汇总

很喜欢《水浒传》&#xff0c;希望能将它融入我的考研复习中&#xff0c;打算用水浒传数据来贯穿数据结构的各种知识&#xff0c;先汇总下找到的数据集 天池上看到的一个水浒传文本数据集&#xff1a;https://tianchi.aliyun.com/dataset/36027 Hareric/masterworkNLP: 基于社…

CUDA C编程权威指南:1.1-CUDA基础知识点梳理

主要整理了N多年前&#xff08;2013年&#xff09;学习CUDA的时候开始总结的知识点&#xff0c;好长时间不写CUDA代码了&#xff0c;现在LLM推理需要重新学习CUDA编程&#xff0c;看来出来混迟早要还的。 1.CUDA 解析&#xff1a;2007年&#xff0c;NVIDIA推出CUDA&#xff08…

软件或游戏提示msvcp120.dll丢失的5种常用解决方法,msvcp120.dll文件全面解析

在当今数字化的时代&#xff0c;我们的生活已经离不开各种软件和游戏。然而&#xff0c;有时候我们可能会遇到一些技术问题&#xff0c;比如“软件或游戏提示msvcp120.dll丢失”。这个问题对于许多人来说可能很棘手&#xff0c;但是只要掌握了正确的解决方法&#xff0c;就能轻…

软件工程第四周

模型建立的基本理念 模型是对现实世界复杂系统的简化和抽象&#xff0c;目的是为了更好地理解、分析和预测系统的行为。它能够真实反映研究对象的整体结构 or 某一侧面&#xff08;功能、反应&#xff09;的本质特征和变化规律。可以建立不同的子模型用于反应系统不同的侧面。同…

《机器人SLAM导航核心技术与实战》第1季:第6章_机器人底盘

视频讲解 【第1季】6.第6章_机器人底盘-视频讲解 【第1季】6.1.第6章_机器人底盘_底盘运动学模型-视频讲解 【第1季】6.2.第6章_机器人底盘_底盘性能指标-视频讲解 【第1季】6.3.第6章_机器人底盘_典型机器人底盘搭建-视频讲解 第1季&#xff1a;第6章_机器人底盘 先 导 课…

SpringBoot二手车管理系统

本系统采用基于JAVA语言实现、架构模式选择B/S架构&#xff0c;Tomcat7.0及以上作为运行服务器支持&#xff0c;基于JAVA、springboot等主要技术和框架设计&#xff0c;idea作为开发环境&#xff0c;数据库采用MYSQL5.7以上. 采用技术: SpringBootMySQL

存在负权边的单源最短路径的原理和C++实现

负权图 此图用朴素迪氏或堆优化迪氏都会出错&#xff0c;floyd可以处理。 负环图 但floyd无法处理负权环&#xff0c;最短距离是无穷小。在环上不断循环。 经过k条边的最短距离&#xff08;可能有负权变&#xff09; 贝尔曼福特算法(bellman_ford)就是解决此问题的。 原理 …