当前位置: 首页 > news >正文

Golang 语言中的指针操作

指针是 Go 语言中一个重要且容易让人困惑的概念,它直接关联到内存操作,理解指针有助于写出更高效、更灵活的代码。本文将从指针的基本定义出发,逐步解析其用法、特性及常见误区,帮助你彻底掌握 Go 语言中的指针。

一、什么是指针?

在计算机中,所有数据都存储在内存中,每个内存单元都有一个唯一的地址(类似房间号),通过这个地址可以准确找到数据所在的位置。指针就是用来存储这个内存地址的变量。​

打个比方:如果把内存比作一栋公寓楼,每个房间(内存单元)都有唯一的门牌号(内存地址),而指针就像一张纸条,上面写着某个房间的门牌号。通过这张纸条,我们可以快速找到对应的房间并查看或修改里面的内容。​

在 Go 语言中,指针变量与普通变量的区别在于:普通变量存储的是具体的数据值,而指针变量存储的是另一个变量的内存地址。

二、指针的基本操作

Go 语言中与指针相关的操作主要通过两个运算符完成:&(取地址符)和*(指针取值符),这两个都是单目运算符(仅需一个操作数)。

1. 取地址符 &

& 用于获取变量的内存地址,格式为 &变量名。当我们声明一个普通变量后,可以通过 & 运算符得到它的内存地址,这个地址可以赋值给指针变量。​

package mainimport "fmt"func main() {a := 101  // 声明普通变量a,存储值101fmt.Println("a的值:", a)       // 输出:a的值:101fmt.Println("a的地址:", &a)    // 输出一个16进制的大整数作为地址
}# 输出的结果为:
a的值为: 101
a的内存地址为: 0xc00008c0a8
进程 已完成,退出代码为 0

2. 指针变量的声明与初始化

* 用于获取指针变量所指向的内存地址中存储的值,格式为 *指针变量名。通过这个操作,我们可以读取或修改指针指向的变量的值。

package mainimport "fmt"func main() {a := 101fmt.Printf("a的值为: %d\na的内存地址为: %v\n", a, &a)var p *intp = &afmt.Println("p的地址为: ", p)
}# 输出的结果为:
a的值为: 101
a的内存地址为: 0xc00000a0f8
p的地址为:  0xc00000a0f8进程 已完成,退出代码为 0

3. 指针取值符 *​

* 用于获取指针变量所指向的内存地址中存储的值,格式为 *指针变量名。通过这个操作,我们可以读取或修改指针指向的变量的值。

package mainimport "fmt"func main() {a := 101b := &afmt.Println("a的值为:", *b)
}# 运行结果为:
a的值为: 101进程 已完成,退出代码为 0

这里需要注意:* 在指针声明时表示 “这是一个指针类型”,而在后续使用时表示 “获取指针指向的值”,具体含义需结合上下文判断。

4.小结指针

在Go语言中,赋值往往是建立副本,而副本的内存地址与本身不一致。

实例说明:

package mainimport "fmt"func main() {a := 101b := &afmt.Printf("用b来打印a的地址为: %X,b的类型此时为: %[1]T,b此时的值为%[2]v\n", b, *b)var c *intc = &afmt.Println(c == &a)var d = afmt.Println(d)fmt.Println(&d == &a)
}
/* 
运行结果为:
用b来打印a的地址为: C00000A0F8,b的类型此时为: *int,b此时的值为101
true
101
false
*/
  1. 第一个比较 c == &a 返回 true

    • c 是一个 *int 类型的指针变量
    • 我们执行了 c = &a,让 c 指向了变量 a 的内存地址
    • 因此 c 中存储的地址与 &aa 的地址)完全相同,所以比较结果为 true
  2. 第二个比较 &d == &a 返回 false

    • var d = a 是将 a 的值(101)复制给了 dd 是一个新的 int 类型变量
    • 虽然 d 的值和 a 相同,但它们是两个独立的变量,存储在内存中的不同位置
    • &d 是 d 的内存地址,&a 是 a 的内存地址,这两个地址不同,所以比较结果为 false

简单总结:

  • 指针变量 c 和 &a 指向同一个内存地址(存放 a 的位置),所以相等
  • 变量 d 是 a 的副本,拥有自己独立的内存地址,所以 &d 和 &a 不相等

指针小结

package mainimport "fmt"func main() {a := 101b := &ac := *bd := &cfmt.Println(1, a == c)fmt.Println(2, d == b)fmt.Printf("a的值为:%[1]v\na的内存地址为:%v\nc的值为:%v\nc的内存地址为:%v\n", a, b, c, d)/* 由此运行结果我们可以发现,内存分配一个空间给a存储101,b通过&a去找到内存中的地址(指针),也可以说是门牌号(本质上还是一个大整数),拿到这个门牌号之后,可以通过*b去将这个值给取出来。但是,如果我们再将c指向101,这个时候,c会重新从内存中找一个新地址来存放101.简单来说,指针让我们可以直接操作内存地址,而通过指针取值并赋值给新变量时,会创建新的内存空间来存储这个值。*/e := a // 简单赋值,建立副本fmt.Println("e的地址为: ", e, &e)fmt.Println(1, e == a)fmt.Println(2, &e == &a)// 在Go语言中,赋值往往是建立副本,而副本的内存地址与本身不一致。x := 101fmt.Printf("a的值为: %d\na的内存地址为: %v\n", x, &a)var p *intp = &afmt.Println("p的地址为: ", p)}# 运行的结果为:1 true
2 false
a的值为:101
a的内存地址为:0xc00000a0f8
c的值为:101
c的内存地址为:0xc00000a110
e的地址为:  101 0xc00000a140
1 true
2 false
a的值为: 101
a的内存地址为: 0xc00000a0f8
p的地址为:  0xc00000a0f8进程 已完成,退出代码为 0

三、指针的实际应用场景

指针并非凭空存在,它在很多场景下能解决普通变量无法解决的问题,以下是几个典型应用场景:​

1. 函数传参:实现引用传递​

Go 语言中函数参数默认是值传递(将变量的副本传入函数),如果希望在函数内部修改外部变量的值,就需要通过指针实现 “引用传递”。​

交换两个变量的值

// 普通函数(值传递):无法真正交换a和b的值
func swap(a, b int) {a, b = b, a
}// 指针函数(引用传递):可以交换外部变量的值
func swapWithPointer(a, b *int) {*a, *b = *b, *a
}func main() {x, y := 10, 20swap(x, y)fmt.Println("swap后:", x, y)  // 输出:swap后:10 20(未交换)swapWithPointer(&x, &y)fmt.Println("swapWithPointer后:", x, y)  // 输出:swapWithPointer后:20 10(已交换)
}

四、指针的注意事项与常见误区

虽然指针功能强大,但使用不当也会引发问题,以下是需要特别注意的几点:​

1. 空指针(nil)​

在Go语言中,空指针属于非常严重的错误!

未初始化的指针变量默认值为 nil(空指针),表示不指向任何内存地址。对空指针使用 * 取值会导致程序崩溃(运行时错误)。

func main() {var p *int  // 未初始化的指针,值为nil// fmt.Println(*p)  // 运行时错误:panic: runtime error: invalid memory address or nil pointer dereference
}

避免空指针错误的方法:使用指针前先检查是否为 nil:

if p != nil {fmt.Println(*p)  // 确保指针非空再操作
}

2. 指针的类型限制​

指针变量有严格的类型限制,一个指向 int 类型的指针不能指向 float64 类型的变量,即使它们都是数值类型。

func main() {a := 100var p *int = &ab := 3.14// p = &b  // 编译错误:cannot use &b (type *float64) as type *int in assignment
}

3. Go 语言没有指针算术​

与 C 语言不同,Go 语言不支持指针算术运算(如 p++、p+1 等),这是为了避免指针操作带来的安全问题,简化内存管理。

func main() {a := 10p := &a// p++  // 编译错误:invalid operation: p++ (non-numeric type *int)
}

五、总结​

指针是 Go 语言中连接变量与内存的桥梁,它让我们能够直接操作内存地址,实现数据的高效传递和共享。通过本文的学习,我们掌握了:​

  • 指针的本质:存储内存地址的变量​
  • 核心操作:& 取地址和 * 取值​
  • 应用场景:函数引用传递、大型数据高效处理、数据共享​
  • 注意事项:避免空指针、类型匹配、不返回局部变量指针等​

合理使用指针能让代码更高效、更灵活,但也需谨慎操作,避免因指针滥用导致的内存安全问题。建议在实践中多编写指针相关代码,逐步加深对这一概念的理解。

http://www.dtcms.com/a/337212.html

相关文章:

  • Android中使用RxJava实现网络请求与缓存策略
  • 实习两个月总结
  • 通义万相Wan2.1- 阿里推出的开源视频生成大模型
  • 从哲学(业务)视角看待数据挖掘:从认知到实践的螺旋上升
  • Elasticsearch查询中的track_total_hits参数
  • 【网络安全实验报告】实验五:网络嗅探及安全性分析
  • 在阿里云 CentOS Stream 9 64位 UEFI 版上离线安装 Docker Compose
  • CentOS 7更换国内镜像源
  • CentOS 7安装OpenVASGVM指南
  • 国产!全志T113-i 双核Cortex-A7@1.2GHz 工业开发板—ARM + DSP、RISC-V核间通信开发案例
  • [数据结构] ArrayList 与 顺序表
  • OVS:ovn为什么默认选择Geneve作为二层隧道网络协议?
  • 【Day 30】Linux-Mysql数据库
  • 大数据计算引擎(三)——Elasticsearch入门
  • uart串口 day57
  • 产品经理如何提升职场学习能力?破除成长瓶颈
  • Vue+Flask 电影协同推荐可视化平台 前后端分离 大数据分析
  • Windows从零到一安装KingbaseES数据库及使用ksql工具连接全指南
  • 05.用户和组管理命令
  • 【机器学习】FPR(False Positive Rate,误报率)是什么?
  • Zephyr下ESP32S3开发环境搭建(Linux篇)
  • 深度研究系统、方法与应用的综述
  • Transformer架构的编码器和解码器介绍
  • 管理本地用户和组:红帽企业 Linux 系统安全的基础
  • TDengine `count_window` 指定列计数功能用户手册
  • 数据泵实施VPS海外:跨国数据同步的完整解决方案
  • elasticsearch-集成prometheus监控(k8s)
  • 【iOS】锁的原理
  • Flutter 多功能列表项:图标、文字与Switch组合
  • Highcharts for Flutter 正式发布