【Go系列】Go的指针

承上启下

        我们在前面的文章中,首先介绍了GO的基础语法,然后介绍了Goroutine和channel这个最具有特色的东西,同时介绍了Sync和context,以及在上篇文章中详细距离说明了Go里面用于高并发的多种写法。基础的使用方法也告一段落了,我们要进入新的篇章,就是Go的指针,这边的指针类型不仅是一个地址,还有Unsafe.Point,还有intptr,让我们详细看看。

开始学习

普通指针

在Go语言中,指针是一种特殊类型的变量,它存储了另一个变量的内存地址。指针在Go中虽然不像C或C++那样普遍使用,但它们在某些情况下仍然非常有用,尤其是在需要修改函数内部变量的值、避免大对象复制、实现数据结构(如链表、树等)时。

以下是关于Go语言中指针的详细介绍:

指针的基本概念

  • 内存地址:每个变量在内存中都有一个地址,指针变量存储的就是这个地址。
  • 解引用:通过指针访问它所指向的变量的值称为解引用。

声明指针

在Go中,指针的声明方式是在变量类型前加上*

var pointer *int

这里,pointer是一个指向int类型变量的指针。

初始化指针

指针必须在使用前进行初始化。你可以使用&操作符来获取一个变量的地址,并将其赋值给指针:

value := 10
pointer := &value

在这个例子中,pointer存储了变量value的内存地址。

解引用指针

使用*操作符可以解引用指针,访问或修改它所指向的值:

*pointer = 20

这将把变量value的值修改为20。

函数中的指针

在函数中传递指针允许你修改函数外部的变量:

func modifyValue(ptr *int) {*ptr = 30
}func main() {value := 10modifyValue(&value)fmt.Println(value) // 输出 30
}

在这个例子中,modifyValue函数通过指针参数修改了外部变量value的值。

指针的nil值

一个未初始化的指针有一个nil值,表示它不指向任何地址:

var pointer *int
if pointer == nil {fmt.Println("Pointer is nil")
}

指针和结构体

指针常用于结构体,可以创建结构体的指针,并通过指针访问或修改结构体的字段:

type Person struct {Name stringAge  int
}func main() {person := &Person{Name: "Alice", Age: 30}person.Age = 31 // 通过指针修改结构体的字段
}

指针数组与数组指针

  • 指针数组:一个数组,其元素是指针。

    var ptrArray [3]*int
    
  • 数组指针:一个指向数组的指针。

    var array [3]int
    var ptrToArray *[3]int = &array
    

指针的指针

虽然不常见,但你可以在Go中创建指向指针的指针:

var value int = 100
var ptr *int = &value
var ptrToPtr **int = &ptr

这里,ptrToPtr是一个指向ptr指针的指针。

注意事项

  • Go不支持指针算术,即你不能对指针进行加减操作。
  • Go的垃圾回收机制会自动管理内存,因此通常不需要手动释放指针指向的内存。

Unsafe.Point

在Go语言中,unsafe.Pointer 是一个特殊类型的指针,它可以指向任意类型的值。unsafe 包提供了一些绕过Go类型系统的功能,允许程序进行一些原本不被允许的操作,比如在不同指针类型之间进行转换,或者计算一个对象的实际内存大小等。

以下是关于 unsafe.Pointer 的一些关键点:

类型转换

unsafe.Pointer 可以用于在任意指针类型之间进行转换。例如,如果你有一个 *int 类型的指针,你可以将其转换为 unsafe.Pointer,然后再转换回其他类型的指针。

package mainimport ("fmt""unsafe"
)func main() {i := 42ip := &i         // *intptr := unsafe.Pointer(ip) // 转换为 unsafe.Pointeruptr := uintptr(ptr)     // 转换为 uintptr// 反向转换ptr = unsafe.Pointer(uptr) // 转换回 unsafe.Pointerip2 := (*int)(ptr)        // 转换回 *int*ip2 = 84fmt.Println(i) // 输出 84
}

访问任意内存地址

通过 unsafe.Pointer,你可以访问任意内存地址,这在Go的常规操作中是不被允许的,因为它绕过了Go的类型系统和内存安全检查。

ptr := unsafe.Pointer(uintptr(0x12345678))

上述代码试图访问一个特定的内存地址,这在实际的程序中是非常危险的,因为它可能导致未定义行为,包括程序崩溃。

计算结构体大小

unsafe.Sizeof 函数可以返回一个值的大小,单位是字节。这个函数通常与 unsafe.Pointer 一起使用来计算结构体的大小。

type MyStruct struct {a intb string
}s := MyStruct{a: 1, b: "hello"}
size := unsafe.Sizeof(s)
fmt.Println(size) // 输出结构体 MyStruct 的大小

使用 uintptr 进行指针算术

虽然Go不支持 unsafe.Pointer 的算术操作,但你可以将 unsafe.Pointer 转换为 uintptr,然后在 uintptr 上执行算术操作,最后再转换回 unsafe.Pointer

ptr := unsafe.Pointer(&s)
uptr := uintptr(ptr)
newPtr := unsafe.Pointer(uptr + unsafe.Offsetof(s.b)) // 访问结构体中的 b 字段

注意事项

  • 使用 unsafe 包绕过Go的类型系统和内存安全机制,需要非常小心,因为错误的使用可能会导致程序崩溃或者安全漏洞。
  • unsafe.Pointer 的使用应该限制在必要的范围内,并且要确保操作的安全性。
  • unsafe 包的内容可能会在不同的Go版本之间发生变化,因此在使用时应保持谨慎。

由于 unsafe 包的功能非常强大,Go官方建议开发者只有在没有其他选择的情况下才使用它,并且要确保代码的稳定性和安全性。

UintPtr

在Go语言中,intptr 并不是一个内置的类型。你可能在提到 uintptr 时出现了误解,或者是在引用其他语言中的类型。在Go语言中,与指针操作相关的类型是 uintptr

uintptr 类型

uintptr 是 unsafe 包中的一个类型,它足够大,可以存储任何类型的指针的位模式(即内存地址)。uintptr 类型主要用于低级编程,比如与操作系统接口、内存操作等。

以下是一些关于 uintptr 的关键点:

  • uintptr 是一个无符号整数类型,其大小足以容纳任何指针的位模式。
  • 它可以用于将指针转换为整数,反之亦然。
  • uintptr 可以用于执行指针算术,但这样做需要非常小心,因为它可能会绕过Go的内存安全保证。

示例

以下是如何使用 uintptr 的示例:

package mainimport ("fmt""unsafe"
)func main() {i := 42ptr := &i         // *intuptr := uintptr(unsafe.Pointer(ptr)) // 转换为 uintptr// 使用 uintptr 进行指针算术newPtr := unsafe.Pointer(uptr + unsafe.Sizeof(i))// 反向转换回指针类型newIntPtr := (*int)(newPtr)*newIntPtr = 84fmt.Println(i) // 输出 84
}

在这个例子中,我们首先将一个 *int 类型的指针转换为 uintptr,然后执行了指针算术操作(虽然在这个特定的例子中这样做没有意义,因为它只是在一个整数大小的范围内移动),最后将结果转换回 *int 类型的指针。

注意事项

  • 使用 uintptr 需要非常小心,因为不正确的使用可能会导致内存安全问题,比如访问未分配的内存、越界访问等。
  • uintptr 类型的值不应该被存储或以任何方式保留,因为它们可能会在垃圾回收期间变得无效。
  • 通常情况下,Go程序员不需要直接使用 uintptr,除非他们正在编写需要直接与操作系统或硬件交互的底层代码。

三种指针类型对比

在Go语言中,指针、unsafe.Pointer 和 uintptr 是三种不同的概念,它们在内存操作和类型转换中扮演着不同的角色。下面是它们的区别:

指针(如 *int

  • 定义:指针是一种变量,它存储了另一个变量的内存地址。
  • 用途:用于引用和修改变量,或者在函数调用中传递变量的地址以修改其值。
  • 类型安全:指针是类型安全的,它们只能指向特定类型的变量。
  • 示例
    var a int = 42
    var ptr *int = &a
    *ptr = 100 // 修改a的值
    

unsafe.Pointer

  • 定义unsafe.Pointer 是 unsafe 包中的一个特殊类型,它可以指向任意类型的变量。
  • 用途:用于在不同指针类型之间进行转换,或者在需要时进行底层的内存操作。
  • 类型安全unsafe.Pointer 本身不是类型安全的,因为它可以指向任何类型的变量,但它需要与其他类型安全的指针一起使用。
  • 示例
    var a int = 42
    var ptr unsafe.Pointer = unsafe.Pointer(&a)
    

uintptr

  • 定义uintptr 是 unsafe 包中的一个无符号整数类型,其大小足以存储任何类型的指针的位模式。
  • 用途:用于执行指针算术操作,或者将指针转换为整数以便进行低级内存操作。
  • 类型安全uintptr 不是类型安全的,因为它可以存储任何指针的位模式,并且可以用于执行指针算术,这可能会绕过Go的内存安全保证。
  • 示例
    var a int = 42
    var ptr uintptr = uintptr(unsafe.Pointer(&a))
    

区别

  • 类型安全

    • 指针是类型安全的,只能用于指向特定类型的变量。
    • unsafe.Pointer 不是类型安全的,可以指向任何类型的变量,但它需要与其他类型安全的指针一起使用。
    • uintptr 也不是类型安全的,它可以存储任何指针的位模式,并且可以用于执行指针算术。
  • 用途

    • 指针主要用于变量引用和修改变量的值。
    • unsafe.Pointer 用于在不同指针类型之间进行转换,或者在需要时进行底层的内存操作。
    • uintptr 用于执行指针算术操作,或者将指针转换为整数以便进行低级内存操作。
  • 内存安全

    • 使用指针时,Go的垃圾回收器会确保指向的变量在需要时不会被回收。
    • 使用 unsafe.Pointer 和 uintptr 时,程序员需要确保操作不会导致内存安全问题,比如越界访问或访问未分配的内存。

总结来说,指针是Go中用于日常变量引用和修改变量的类型安全工具,而 unsafe.Pointer 和 uintptr 用于更底层的内存操作,它们提供了更大的灵活性和能力,但同时也带来了更高的风险,因为它们不是类型安全的,并且需要程序员更加小心地使用。

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

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

相关文章

Linux多线程编程-哲学家就餐问题详解与实现(C语言)

在哲学家就餐问题中,假设有五位哲学家围坐在圆桌前,每位哲学家需要进行思考和进餐两种活动。他们的思考不需要任何资源,但进餐需要使用两根筷子(左右两侧各一根)。筷子是共享资源,哲学家们在进行进餐时需要…

Qt qml详细介绍

一.基本类型 QML的基本类型包括了很多不同的类型,这些类型可以用于定义用户界面元素、属性和信号。以下是一些常用的QML基本类型及其详细介绍: 数值类型:包括整数类型(int、uint、short、ushort等)和浮点数类型&#…

c++ :运算符重载函数中的细节

赋值运算符重载与拷贝构造函数 (1)区分初始化时的赋值(一般就叫初始化),和非初始化时的赋值(一般就叫赋值) (2)实验验证初始化和赋值时各自对应 避免赋值运算符中的自赋值 (1)自赋值就是Person a; a a; (2)自赋值如…

鞭炮插画:成都亚恒丰创教育科技有限公司

鞭炮插画:年味里的绚烂记忆 在岁末年初的温柔时光里,总有一抹色彩,能瞬间唤醒沉睡的年味——那便是鞭炮插画中跃动的红与金,成都亚恒丰创教育科技有限公司 它们不仅仅是纸与墨的交织,更是情感与记忆的桥梁&#xff0c…

自适应手机版大学职业技术学院网站模版源码系统 带完整的安装代码包以及搭建部署教程

系统概述 随着智能手机的普及和移动互联网技术的飞速发展,用户越来越倾向于通过移动设备访问网站。对于大学职业技术学院而言,一个能够自适应各种屏幕尺寸、操作流畅、内容丰富的移动端网站,不仅能够提升用户体验,还能有效扩大学…

最短路之朴素版的dij板子

模板&#xff1a; 注意这个只是单向的双向的需要在更新一次 #include<bits/stdc.h>using namespace std;typedef long long ll; typedef pair<int, int>PII; const int N2e510; const int MOD 998244353; const int INF0X3F3F3F3F; const int dx[]{-1,1,0,0,-1,…

【Python Tips】将一个列表List元素添加进另一个列表List

一、引言 在处理Python列表数据类型时&#xff0c;有时需要合并两个列表&#xff0c;下面是几种列表合并的操作代码&#xff0c;尤其是对于长列表的高效合并方式&#xff0c;记录在此。 二、列表合并方式 1. 使用extend方法 extend方法将一个列表中的所有元素添加到另一个列表…

mysql快速精通(三)表关系

主打一个实用 一. 一对多&#xff08;多对一&#xff09;关系 例如班级和学生&#xff0c;这种类型我们一般建两个表,一方为主表&#xff0c;多方为从表 二. 多对多 例如课程与学生&#xff0c;这种类型我们一般需要建三张表&#xff0c;两张一方主表&#xff0c;与一张多方从表…

初识影刀:EXCEL根据部门筛选低值易耗品

第一次知道这个办公自动化的软件还是在招聘网站上&#xff0c;了解之后发现对于办公中重复性的工作还是挺有帮助的&#xff0c;特别是那些操作非EXCEL的重复性工作&#xff0c;当然用在EXCEL上更加方便&#xff0c;有些操作比写VBA便捷。 下面就是一个了解基本操作后&#xff…

[Linux]CentOS软件的安装

一、Linux 软件包管理器 yum 1.Linux安装软件的方式 在linux中安装软件常用的有三种方式&#xff1a; 源代码安装&#xff08;我们还需要进行编译运行后才可以&#xff0c;很麻烦&#xff09; rpm安装&#xff08;Linux的安装包&#xff0c;需要下载一些rpm包&#xff0c;但是…

基于机器学习的锂离子电池容量估计(MATLAB R2021B)

锂离子电池已经广泛应用于电动汽车或混合动力汽车的能源存储装置。由于电化学成分的衰退&#xff0c;锂离子电池随着使用时间的增加&#xff0c;电池性能不断退化&#xff0c;导致电池容量和功率发生衰退。电池容量衰退的因素主要有金属锂沉积&#xff0c;活性物质分解和电解液…

深度学习DeepLearning多元线性回归 学习笔记

文章目录 多维特征变量与术语公式多元线性回归正规方程法Mean normalizationZ-score normalization设置合适的学习率Feature engineering 多维特征 变量与术语 列属性xj属性数n x ⃗ \vec{x} x (i)行向量某个值 x ⃗ j i \vec{x}_j^i x ji​上行下列均值μ标准化标准差σsigm…

SpringMVC 中常用注解

在 SpringMVC 框架的开发中&#xff0c;注解的合理运用能够极大地提高开发效率和代码的可维护性。今天&#xff0c;让我们一起来总结一下 SpringMVC 中一些常用的注解及其用法。 一、Controller 注解 Controller 用于标识一个控制器类&#xff0c;该类中的方法用于处理用户的请…

ArduPilot开源代码之AP_AHRS_Backend

ArduPilot开源代码之AP_AHRS_Backend 1. 源由2. 类继承关系3. 框架设计2.1 构造函数和析构函数2.2 不可复制2.3 嵌套结构和枚举2.4 虚方法2.5 静态方法2.6 实用方法2.7 纯虚方法2.8 条件编译 3. 虚方法设计3.1 初始化3.1.1 构造函数3.1.2 析构函数3.1.3 AP_AHRS_Backend::init …

Chromium CI/CD 之Jenkins实用指南2024-如何创建新节点(三)

1. 前言 在前一篇《Jenkins实用指南2024-系统基本配置&#xff08;二&#xff09;》中&#xff0c;我们详细介绍了如何对Jenkins进行基本配置&#xff0c;包括系统设置、安全配置、插件管理以及创建第一个Job。通过这些配置&#xff0c;您的Jenkins环境已经具备了基本的功能和…

基于pyqt5实现xlsx选择器应用程序

环境搭建 基于python3.12pyqt5 pip3 install PyQt5 pip3 install pyinstallerpyinstaller --onefile --windowed test.py代码 新建main.py import sys from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit, QFileDial…

leetcode 665.非递减数列

1.题目要求: 给你一个长度为 n 的整数数组 nums &#xff0c;请你判断在 最多 改变 1 个元素的情况下&#xff0c;该数组能否变成一个非递减数列。我们是这样定义一个非递减数列的&#xff1a; 对于数组中任意的 i (0 < i < n-2)&#xff0c;总满足 nums[i] < nums[i…

Java 设计模式系列:外观模式

简介 外观模式&#xff08;Facade Pattern&#xff09;是一种设计模式&#xff0c;又名门面模式&#xff0c;是一种通过为多个复杂的子系统提供一个一致的接口&#xff0c;而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口&#xff0c;外部应用程序不用关心内部…

Android中RecyclerView使用详解(一)

目录 概述优点列表布局RecyclerView一、创建RecyclerView并且在布局中绑定二、实现RecyclerView单个item的布局三、给RecyclerView写一个对应的适配器Adapter1.创建自定义的ViewHolder2.继承Adapter&#xff0c;泛型使用我们自定义的ViewHolder3.重写Adapter的三个方法onCreate…

线程安全(二)synchronized 的底层实现原理、锁升级、对象的内存结构

目录 一、基础使用1.1 不加锁的代码实现1.2 加锁的代码实现二、实现原理2.1 synchronized 简介2.2 对象监控器(Monitor)2.3 加锁过程第一步:判断 Owner 指向第二步:进入 EntryList 阻塞第三步:主动进入 WaitSet 等待三、锁升级3.1 对象的内存结构3.2 Mark Word 对象头3.3 …