【Go语言-Day 9】指针基础:深入理解内存地址与值传递

Langchain系列文章目录

01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
08-【万字长文】MCP深度解析:打通AI与世界的“USB-C”,模型上下文协议原理、实践与未来

Python系列文章目录

PyTorch系列文章目录

机器学习系列文章目录

深度学习系列文章目录

Java系列文章目录

JavaScript系列文章目录

Python系列文章目录

Go语言系列文章目录

01-【Go语言-Day 1】扬帆起航:从零到一,精通 Go 语言环境搭建与首个程序
02-【Go语言-Day 2】代码的基石:深入解析Go变量(var, :=)与常量(const, iota)
03-【Go语言-Day 3】从零掌握 Go 基本数据类型:string, runestrconv 的实战技巧
04-【Go语言-Day 4】掌握标准 I/O:fmt 包 Print, Scan, Printf 核心用法详解
05-【Go语言-Day 5】掌握Go的运算脉络:算术、逻辑到位的全方位指南
06-【Go语言-Day 6】掌控代码流:if-else 条件判断的四种核心用法
07-【Go语言-Day 7】循环控制全解析:从 for 基础到 for-range 遍历与高级控制
08-【Go语言-Day 8】告别冗长if-else:深入解析 switch-case 的优雅之道
09-【Go语言-Day 9】指针基础:深入理解内存地址与值传递


文章目录

  • Langchain系列文章目录
  • Python系列文章目录
  • PyTorch系列文章目录
  • 机器学习系列文章目录
  • 深度学习系列文章目录
  • Java系列文章目录
  • JavaScript系列文章目录
  • Python系列文章目录
  • Go语言系列文章目录
  • 前言
  • 一、初识内存与地址
    • 1.1 计算机内存的奥秘
    • 1.2 如何查看变量的地址
  • 二、指针的核心:声明与操作
    • 2.1 指针变量的声明
    • 2.2 指针的“双雄”:`&` 与 `*`
      • 2.2.1 取地址运算符 `&`
      • 2.2.2 取值运算符 `*` (解引用)
    • 2.3 空指针 `nil`
  • 三、为何要拥抱指针:值传递 vs 引用传递
    • 3.1 Go语言的默认机制:值传递
    • 3.2 指针的威力:实现引用传递的效果
    • 3.3 指针的应用场景总结
  • 四、总结


前言

大家好!经过前面几篇文章对 ifforswitch 等流程控制语句的学习,我们已经能够构建出逻辑丰富的程序了。从今天开始,我们将踏入 Go 语言中一个极为核心且强大的领域——指针(Pointer)。许多初学者闻“指针”色变,认为它复杂难懂。但实际上,Go 语言的指针相比 C/C++ 已经做了很多简化和安全限制,理解它将为你打开一扇通往更高阶编程世界的大门。本篇作为指针系列的上篇,将拨开云雾,带你从最基础的内存地址概念入手,彻底搞懂指针是什么、如何使用以及为何要使用它。


一、初识内存与地址

在深入指针之前,我们必须先建立一个清晰的“心理模型”——程序中的数据是如何存储的。

1.1 计算机内存的奥秘

想象一下,计算机的内存就像一个巨大无比的酒店,拥有成千上万个房间,每个房间都有一个独一无二的门牌号。

  • 数据 (Data):就像是住进酒店的客人。你在程序中定义的每一个变量(无论是数字、字符串还是其他类型),都像一位客人,需要一个地方住。
  • 内存单元 (Memory Cell):就是酒店里的房间,用来存放数据。
  • 内存地址 (Memory Address):就是每个房间的门牌号。通过这个地址,CPU 可以快速、准确地找到存储特定数据的那个内存单元。

简单来说,当你在 Go 中声明一个变量时,计算机会在内存中为它分配一个“房间”,并将变量的值存进去,这个“房间”的门牌号就是它的内存地址。

1.2 如何查看变量的地址

Go 语言提供了一个非常直观的操作符 &(取地址符),它可以获取任意变量的内存地址。

我们来看一个简单的例子:

package mainimport "fmt"func main() {// 声明一个整型变量 avar a int = 10// 使用 &a 获取变量 a 的内存地址// 使用 fmt.Printf 和 %p 占位符来打印地址fmt.Printf("变量 a 的值是: %d\n", a)fmt.Printf("变量 a 的内存地址是: %p\n", &a)
}

代码解析:

  • var a int = 10:我们在内存中开辟了一块空间,命名为 a,并存入了整数 10
  • &a:这里的 & 就像一个探测器,它不关心 a 里面存的是什么,只关心 a 这个“房间”的门牌号是多少。
  • %p:这是 fmt.Printf 中专门用来格式化输出内存地址的占位符(p for pointer)。

运行上述代码,你可能会看到类似这样的输出(地址每次运行都可能不同):

变量 a 的值是: 10
变量 a 的内存地址是: 0xc000018090

这个 0xc000018090 就是变量 a 在本次程序运行时,操作系统分配给它的独一无二的内存地址。

二、指针的核心:声明与操作

理解了内存地址,指针的概念就水到渠成了。

核心定义:指针(Pointer)就是一个特殊类型的变量,它不存储普通的数据值,而是专门用来存储另一个变量的内存地址。

如果说普通变量是直接存放“货物”的仓库,那么指针变量就是一张记录着“某个货物存放在哪个仓库”的地图。

2.1 指针变量的声明

Go 语言中指针变量的声明格式为:

var ptr *T
  • ptr:是你给指针变量起的名字。
  • *:星号在这里表明我们正在声明的是一个指针变量。
  • T:代表该指针要指向的变量的类型。例如,一个指向 int 类型变量的指针,其类型就是 *int;一个指向 string 类型变量的指针,其类型就是 *string

示例:

package mainimport "fmt"func main() {var a int = 100// 声明一个可以指向 int 类型的指针变量 pvar p *int// 将变量 a 的地址赋值给指针 pp = &afmt.Printf("变量 a 的地址是: %p\n", &a)fmt.Printf("指针变量 p 中存储的值是: %p\n", p)fmt.Printf("指针变量 p 自身的地址是: %p\n", &p)
}

代码解析:

  • var p *int:声明了一个名为 p 的指针变量,它的类型是 *int,意味着它只能存储 int 类型变量的地址。
  • p = &a:我们将 a 的地址 (&a) 赋值给了 p。现在,p 就“指向”了 a
  • 从输出可以看到,a 的地址和 p 中存储的值是完全一样的。同时,p 本身也是一个变量,它也有自己独立的内存地址。

2.2 指针的“双雄”:&*

掌握指针,关键在于熟练运用一对核心操作符:&*

2.2.1 取地址运算符 &

这个我们已经很熟悉了,它的作用是获取一个变量的内存地址。

&i
变量 i = 10
内存地址 0x...

2.2.2 取值运算符 * (解引用)

* 用在指针变量前面时,它的含义就变成了“取值”或“解引用”(Dereference)。它的作用是:通过指针中存储的地址,找到该地址对应的内存空间,并获取其中存储的真正的值。

*p
指针 p 存有地址 0x...
位于地址 0x... 的变量的值

综合示例:

让我们通过一个完整的例子来看看如何通过指针读取并修改原始变量的值。

package mainimport "fmt"func main() {var num int = 42var ptr *int // 声明一个 int 指针ptr = &num // ptr 指向 numfmt.Printf("原始变量 num 的值: %d\n", num)fmt.Printf("通过指针 ptr 获取的值: %d\n", *ptr) // 使用 *ptr 解引用// 现在,通过指针修改值*ptr = 100fmt.Println("--- 通过指针修改值后 ---")fmt.Printf("指针 ptr 指向的地址没变: %p\n", ptr)fmt.Printf("现在,原始变量 num 的值变成了: %d\n", num)fmt.Printf("通过指针 ptr 再次获取的值: %d\n", *ptr)
}

代码解析与关键点:

  1. ptr = &num:指针 ptr 拿到了 num 的“门牌号”。
  2. *ptr:当我们使用 *ptr 时,Go 语言会:
    • 查看 ptr 变量里存的地址(比如是 0xc000018090)。
    • 找到内存中 0xc000018090 这个位置。
    • 读取或操作这个位置上的值。
  3. *ptr = 100:这是最关键的一步!这行代码的意思是:“找到 ptr 指向的那个内存地址,把那个地址上的值给我改成 100”。由于 ptr 指向的是 num,所以实际上被修改的就是 num 变量本身。

输出:

原始变量 num 的值: 42
通过指针 ptr 获取的值: 42
--- 通过指针修改值后 ---
指针 ptr 指向的地址没变: 0xc0000b2008
现在,原始变量 num 的值变成了: 100
通过指针 ptr 再次获取的值: 100

这个例子完美地展示了指针的威力:它允许我们在程序的某个地方,间接地修改另一个地方的变量值。

2.3 空指针 nil

当我们声明一个指针变量但没有给它赋任何地址时,它的默认值是 nil

nil 是 Go 语言中指针、接口、map、切片、channel 和函数类型的零值。对于指针来说,nil 指针表示它不指向任何有效的内存地址。

package mainimport "fmt"func main() {var p *intfmt.Printf("指针 p 的值: %v\n", p) // 输出 <nil>// 重要的安全检查if p != nil {fmt.Println("p 指向的值是:", *p)} else {fmt.Println("p 是一个空指针,不能解引用!")}// 下面的代码会引发 panic// fmt.Println(*p) // invalid memory address or nil pointer dereference
}

重要警告:对一个 nil 指针进行解引用(取值 *p)是一个严重的运行时错误,会导致程序崩溃(panic)。因此,在使用指针前,进行 nil 检查是一个非常重要的好习惯。

三、为何要拥抱指针:值传递 vs 引用传递

现在你可能会问,我直接用变量 num 不就好了,为什么要费劲去用指针 ptr 呢?答案在于理解 Go 函数的传参机制。

3.1 Go语言的默认机制:值传递

在 Go 语言中,所有的函数参数传递都是值传递(Pass by Value)。这意味着当你将一个变量作为参数传递给函数时,函数接收到的是这个变量的一个副本(Copy)。函数内部对这个副本的任何修改,都不会影响到函数外部的原始变量。

示例:

package mainimport "fmt"func modifyValue(val int) {fmt.Printf("进入函数,val 的地址: %p\n", &val)val = 200 // 修改的是副本的值
}func main() {num := 100fmt.Printf("调用前,num 的值: %d, 地址: %p\n", num, &num)modifyValue(num) // 将 num 的值 "100" 传递给函数fmt.Printf("调用后,num 的值: %d, 地址: %p\n", num, &num)
}

输出:

调用前,num 的值: 100, 地址: 0xc000018098
进入函数,val 的地址: 0xc0000180c0
调用后,num 的值: 100, 地址: 0xc000018098

分析:

  • main 函数中的 nummodifyValue 函数中的 val 拥有完全不同的内存地址。
  • 调用 modifyValue(num) 时,Go 只是把 num 的值 100 复制了一份给了 val
  • 函数内部 val = 200 修改的仅仅是 val 这个副本,和 main 函数中的 num 毫无关系。
modifyValue 函数
main 函数
值拷贝
修改
val = 100
地址: 0x...0c0
val = 200
地址: 0x...0c0
num = 100
地址: 0x...098

3.2 指针的威力:实现引用传递的效果

如果我们想让一个函数能够修改外部变量的值,就需要把这个变量的地址(也就是指针)传递给函数。

示例:

package mainimport "fmt"func modifyByPointer(ptr *int) { // 参数是一个 *int 类型的指针fmt.Printf("进入函数,ptr 存储的地址: %p\n", ptr)*ptr = 200 // 通过指针解引用,修改原始地址上的值
}func main() {num := 100fmt.Printf("调用前,num 的值: %d, 地址: %p\n", num, &num)modifyByPointer(&num) // 将 num 的地址传递给函数fmt.Printf("调用后,num 的值: %d, 地址: %p\n", num, &num)
}

输出:

调用前,num 的值: 100, 地址: 0xc000018098
进入函数,ptr 存储的地址: 0xc000018098
调用后,num 的值: 200, 地址: 0xc000018098

分析:

  • 这次我们传递的是 &num,即 num 的内存地址。
  • 函数 modifyByPointer 的参数 ptr 接收了这个地址。现在 ptrnum 指向了同一块内存
  • 函数内部执行 *ptr = 200,就直接修改了那块内存中的值,因此 main 函数中的 num 也随之改变。

虽然这本质上仍然是值传递(传递的是地址这个值),但它达到了引用传递(Pass by Reference) 的效果。

modifyByPointer 函数
main 函数
传递地址 &num
解引用 *ptr = 200
ptr 存的值是 0x...098
num = 100
地址: 0x...098

3.3 指针的应用场景总结

通过上面的对比,我们可以清晰地看到指针的第一个核心用途:

  1. 在函数间共享数据:当你想让一个函数能够修改调用方的数据时,传递指针是必由之路。
  2. 提高性能:对于非常大的数据结构(例如一个包含很多字段的复杂 struct),如果每次函数调用都完整地复制一份,开销会非常大。而传递一个指针(通常只有 8 个字节)则非常轻量高效。这一点我们将在后续的结构体学习中深入体会。

四、总结

恭喜你,完成了 Go 指针基础的学习!让我们回顾一下今天的核心知识点:

  1. 内存与地址:程序中的每个变量都存储在内存的特定位置,这个位置由其内存地址唯一标识。
  2. 指针定义:指针是一种特殊的变量,它存储的是另一个变量的内存地址。
  3. 核心操作符
    • & (取地址符):获取变量的内存地址。
    • * (解引用/取值符):当用在指针前时,获取指针所指向地址上的值。
  4. 空指针 nil:指针变量的零值是 nil,表示不指向任何地方。对 nil 指针解引用会导致程序崩溃,使用前应做检查。
  5. 指针的核心价值:Go 默认是值传递,函数无法修改外部变量。通过传递指针,可以让函数间接地操作和修改原始数据,实现引用传递的效果,这对于数据共享和性能优化至关重要。

指针是理解 Go 语言许多高级特性的基石,如下一篇将要讲到的 newmake 的区别,以及后续的切片、map、结构体方法的实现都与它息息相关。请务必动手实践本文中的所有代码,加深理解。在下一篇文章 【Go语言-Day 10】指针 - Go 语言的利器 (下) 中,我们将继续探索指针在函数、数组、结构体中的高级应用,以及 newmake 的辨析,敬请期待!


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

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

相关文章

如何使用 vue vxe-table 来实现一个产品对比表表格

如何使用 vue vxe-table 来实现一个产品对比表表格 查看官网&#xff1a;https://vxetable.cn 效果 代码 <template><div class"demo-page-wrapper"><vxe-grid v-bind"gridOptions"><template #img11><vxe-image src"h…

【CF】Day85——CF 1033 (Div. 2) B (物理?) + CF 860 (Div. 2) C (数学思维 + lcm + gcd)

忙于期末&#xff0c;久久未写&#xff0c;今日一写&#xff0c;全都忘了 C. Candy Store 题目&#xff1a; 思路&#xff1a; 数学思维 我们假设一个标签 cost 可以覆盖一个连续的区间&#xff0c;那么这个 cost 就满足 cost bl * dl bl1 * dl1 ... br-1 * dr-1 br * d…

16.2 Docker多阶段构建实战:LanguageMentor镜像瘦身40%,支持500+并发1.2秒响应!

LanguageMentor Agent 容器化部署与发布:Docker 镜像创建与测试 关键词:Docker 容器化部署, 多阶段构建, 镜像分层优化, 环境一致性, 私有化模型集成 1. Dockerfile 最佳实践架构设计 通过多阶段构建策略实现开发与生产环境分离: #mermaid-svg-CKUsKERUN6nqM0bI {font-fam…

高可用与低成本兼得:全面解析 TDengine 时序数据库双活与双副本

在现代数据管理中&#xff0c;企业对于可靠性、可用性和成本的平衡有着多样化的需求。为此&#xff0c;TDengine 在 3.3.0.0 版本中推出了两种不同的企业级解决方案&#xff1a;双活方案和基于仲裁者的双副本方案&#xff0c;以满足不同应用场景下的特殊需求。本文将详细探讨这…

Github项目:Python编写的录屏工具 TTvideo(已打包)

打包好能立即用的github项目&#xff1a;TTvideo 用的github上的项目&#xff1a;https://github.com/jumppppp/ttvideo 朴实无华&#xff0c;功能强大的录屏工具 原作者如有认为侵权&#xff0c;评论区联系立删 1.效果图 2.下载链接 录屏工具

LE AUDIO---Chapter 2. The Bluetooth® LE Audio architecture

目录 2.1 The use cases 2.1.1 Hearing aid requirements - the use cases 2.1.1.1 Basic telephony 2.1.1.2 Low latency audio from a TV 2.1.1.3 Adding more users 2.1.1.4 Adding more listeners to support larger areas 2.1.1.5 Coordinating left and right hearin…

算法第54天| 并查集

107. 寻找存在的路径 题目 思路与解法 #include <iostream> #include <vector> using namespace std;int n; // 节点数量 vector<int> father vector<int> (101, 0); // 按照节点大小定义数组大小// 并查集初始化 void init() {for (int i 1; i &l…

守护API可用性:全面对抗DDoS与CC洪水攻击策略

API的可用性直接关系到用户体验和业务收入。分布式拒绝服务&#xff08;DDoS&#xff09;和针对应用层的CC&#xff08;Challenge Collapsar&#xff09;攻击&#xff0c;旨在耗尽服务器资源&#xff08;带宽、连接数、CPU&#xff09;&#xff0c;使合法用户无法访问。这类攻击…

第 4 章:第一个神经网络实战——使用 PyTorch

第 4 章&#xff1a;第一个神经网络实战——使用 PyTorch 经过前三章的学习&#xff0c;我们已经对神经网络的理论基础有了扎实的理解。我们知道数据如何前向传播&#xff0c;如何用损失函数评估预测&#xff0c;以及如何通过梯度下降和反向传播来更新网络参数。 理论是根基&a…

MST56XXB/MST5650B/MST5033B 是一款耐高压的LDO芯片,针对中控设备,给MCU供电,60V的耐压,150mA

MST56XXB系列是一款高输入电压(60V)低静态电流、高PSRR线性稳压器(LDO)&#xff0c;能够提供150mA负载电流。LDO针对线电压瞬变和负载电流瞬变具有非常快速的响应特性&#xff0c;并确保LDO启动期间和短路恢复过程中不会出现过冲电压。该设备具有集成的短路和热关断保护。该设备…

Java基础(五):流程控制全解析——分支(if/switch)和循环(for/while)的深度指南

Java基础系列文章 Java基础(一)&#xff1a;发展史、技术体系与JDK环境配置详解 Java基础(二)&#xff1a;八种基本数据类型详解 Java基础(三)&#xff1a;逻辑运算符详解 Java基础(四)&#xff1a;位运算符详解 Java基础(五)&#xff1a;if/switch与for/while - 深入理解…

面向对象概述

1 面向过程程序设计 面向过程是最为实际的一种思考方式&#xff0c;面向对象的方法也是含有面向过程的思想&#xff0c;面向过程是一种基础的方法。它考虑的是实际的实现&#xff0c;一般的面向过程是从上往下步步求精。面向过程最重要的是模块化的思想方法。对比面向对象&…

linux dts overlay

设备树 Overlay&#xff08;Device Tree Overlays, DTO&#xff09;&#xff0c;它在嵌入式Linux系统&#xff08;尤其是基于ARM的设备&#xff0c;比如树莓派、NanoPi等&#xff09;中非常常见。它主要用于动态修改设备树&#xff0c;以适配硬件的变化或扩展外设支持。 1. 设备…

ArkUI-X的声明式语法转换过程

以下是ArkUI-X声明式语法转换过程的详细解析&#xff0c;结合其核心设计原理与实现机制&#xff1a; ‌一、基础语法转换规则 组件声明转换 传统命令式组件创建&#xff08;如Android XMLJava&#xff09;转换为ArkUI-X的Component结构&#xff1a; // 命令式&#xff08;A…

Docker 入门教程(一):从概念到第一个容器

文章目录 &#x1f433; Docker 入门教程&#xff08;一&#xff09;&#xff1a;从概念到第一个容器1. Docker 是什么&#xff1f;2. Docker 的核心概念3. 安装 Docker4. 运行你的第一个 Docker 容器 &#x1f433; Docker 入门教程&#xff08;一&#xff09;&#xff1a;从概…

如何在 Vue 应用中嵌入 ONLYOFFICE 编辑器

以下是仅包含 纯前端集成 ONLYOFFICE 文档编辑器到 Vue.js 项目 的完整代码与说明&#xff0c;无需重新创建项目&#xff0c;可直接集成到现有 Vue 项目中&#xff1a; Vue.js 集成 ONLYOFFICE 文档编辑器&#xff08;纯前端实现&#xff09; 后端需要部署到服务器&#xff0c…

Cursor 1.0 炸裂功能:在后台运行多个Agent,释放双手

Cursor 1.0 版本更新了用于代码审查的 BugBot、对内存、一键式 MCP 设置、Jupyter 支持以及 Background Agent 的正式发布。 今天这篇文章主要介绍 Background Agent 的使用教程。 文章目录 1. Background Agent 的基本概念2. 后台 Agent 的使用方法3. 让后台 Agent 创造一个简…

MLX LM - 在Apple芯片上运行大语言模型的Python工具包

文章目录 一、关于MLX LM1、项目概览2、相关链接资源3、功能特性 二、安装配置三、使用指南1、快速开始2、Python API3、量化模型&#xff0c;上传HF4、流式生成采样 5、命令行6、长提示词与生成 四、支持模型大模型 一、关于MLX LM 1、项目概览 MLX LM是一个Python工具包&am…

【git学习】学习目标及课程安排

Git 是一款非常强大的版本控制工具&#xff0c;掌握它对编程和团队协作都有巨大帮助。 &#x1f3af;学习目标&#xff08;适合个人与团队使用&#xff09; 理解 Git 和版本控制的基本概念 熟练使用 Git 进行代码提交、分支管理、合并与冲突解决 掌握远程仓库协作流程&#x…

HDFS(Hadoop分布式文件系统)总结

文章目录 一、HDFS概述1. 定义与定位2. 核心特点 二、HDFS架构核心组件1. NameNode&#xff08;名称节点&#xff09;2. DataNode&#xff08;数据节点&#xff09;3. Client&#xff08;客户端&#xff09;4. Secondary NameNode&#xff08;辅助名称节点&#xff09; 三、数据…