Go 语言快速入门指南:Go 指针

简介: 我们都知道,Java、C#、Python 都 因为指针的复杂而避开了指针的用法,改成了引用。Go 语言作为 21 世纪的 C 语言,自然保留了 C 语言的许多特性,指针就是其一。但相比于 C 的指针,Go 对指针做了很多限制。这一篇,就来学习 Go 指针的各种相关知识。


我们都知道,Java、C#、Python 都 因为指针的复杂而避开了指针的用法,改成了引用


Go 语言作为 21 世纪的 C 语言,自然保留了 C 语言的许多特性,指针就是其一。但相比于 C 的指针,Go 对指针做了很多限制。


这一篇,就来学习 Go 指针的各种相关知识。

地址与指针

变量是存储值的地方。利用声明的变量名来区分各种变量,例如 x。  而指针的值是一个变量的地址。一个指针是指向值所保存的位置,不是所有的值都有地址,但是所有的变量都有。

package main
import "fmt"
func main() {
    x := 2021
    fmt.Println(x)
    fmt.Println(&x)
}


将会得到如下结果(不同的电脑地址可能不一样):

2021
0xc0000120a8

image.png


那么这些 "地址 "究竟是什么呢?好吧,如果你想在拥挤的城市中找到一个特定的 的房子,你就用它的地址......


image.png


就像一个城市,你的计算机为你的程序留出的内存是一个拥挤的地方。它充满了变量值:布尔值、整数、字符串,以及更多。就像房子的地址一样,如果你有一个变量的地址,你就可以 用它来找到该变量所包含的值。


image.png


代表变量地址的值被称为指针,因为它们指向可以找到变量的位置。它们指向可以找到该变量的位置。


image.png

指针的零值是Nil。Go 编译器为指针变量分配一个 Nil 值,以防你没有要分配的确切地址。这是在变量声明时完成的。分配为 Nil 的指针称为 Nil 指针。


在大多数操作系统上,不允许程序访问地址 0 处的内存,因为该内存是由操作系统保留的。但是,内存地址 0 具有特殊的意义;它表示指针不打算指向可访问的内存位置。但是按照惯例,如果指针包含 nil(零)值,则假定它不指向任何内容。 可以使用以下方式检查 nil 指针

if(ptr != nil)
if(ptr == nil)

利用指针更改函数参数的值

使用指针,可以在无须知道变量名字的情况下,间接读取或更新变量的值。    当我们调用一个带参数的函数时,该参数被复制到函数中。

func zeroValue(x int) {
    x = 0
}
func main() {
    x := 5
    zeroValue(x)
    fmt.Println(x)     // x is still 5
}


在这个程序中,zeroValue 函数不会修改变量 x 的值。但如果我们想修改呢?  一种方式是使用特殊的数据类型:指针。

func zeroValue(x * int) {
    *x = 0
}
func main() {
    x := 5
    zeroValue(&x)
    fmt.Println(x)    // x is 5
}


   指针指的是内存中一个值存储的位置,而不是数值本身。指针指向其他东西,通过使用指针(*int),zeroValue 函数就能修改原始变量。

* 运算符 和 & 操作符

在 Go 中,指针是用 * (星号)字符来表示的,后面跟的指针所指向的变量的类型。  * 也被用来解引用,解引用是取地址的逆过程。

var x int // 声明一个变量
x := 2  // 赋值变量
p := &x  // p 是整型指针,指向 x
fmt.Println(*p)  // "2"
*p = 22
fmt.Println(x)  // 结果 “22”


如果一个变量声明为 var x int,表达式 &x (x 的地址) 获取一个指向整型变量的指针,它的类型是整型指针 (*int)。  如果值叫做 p,那么就说 p 指向 x,或者说 p 包含 x 的地址。  因为 *p 代表一个变量,所以它也可以出现在赋值操作符的左边,用于更新变量的值。


我们使用 & 操作符来寻找一个变量的地址, &x 返回一个 *int ,因为 x 是一个 int,这就是为什么我们修改 *p 的值时,原来的变量 x 也会更改的原因。

使用 new() 函数创建指针

另一种方式得到指针就是通过内置函数 new ,通过内置函数 new 为任何类型的值开辟一块内存并将此内存的起始地址为此值得地址返回。

package main
import "fmt"
func one(p *int) {
    *p = 2021
}
func main() {
    p := new(int)
    one(p)
    fmt.Println(*p) // 结果为:2021
}


new 接收一个类型作为参数,分配足够的内存来容纳一个该类型的值,并返回一个指针。


在 C 语言中,使用 new 和 & 有很大区别,C 语言中使用 new 创建的指针可以需要手动回收。但在 Go 中,Go 语言会自动进行垃圾回收的,如果没有任何东西指向这个值,内存会被自动清理掉。指针很少被用于 Go 的内置类型,但是指针与结构体搭配时,是非常有用的。

Go 指针的一些限制

Go 指针在使用上有一些限制,通过这些限制,减少了很多危险的操作。

Go 指针不支持算术运算

在 Go 中,指针是不能参与算术运算的,对于指针 p, p++p-2 都是非法的。


如果 p 为一个指向一个数字类型值得指针,*p++ 将被编译器认为是合法的并且等价于(*p)++。例如:

package main
import "fmt"
func main() {
    x := int64(2021)
    p := &x
    *p++    // *p为2021,然后 ++ ,变成2022,指针p指向2022,同时x更改为2022
    fmt.Println(*p, x)
    fmt.Println("p == &x is ", p == &x)
    *&x++ // x is 2023
    *&*&x++ // 2024
    **&p++  // 2025
    *&*p++  // 2026
    fmt.Println(*p, x)
}


运行结果:

2022 2022
p == &x is  true
2026 2026

一个指针类型的值不能被随意转换为另一个指针类型

在 Go 中,只有如下某个条件被满足的情况下,一个类型为 T1 的指针值才能被显式转换为另一个指针类型 T2 :


  1. 类型 T1 和 T2 的底层类型必须一致(忽略结构体字段的标签)。特别地,如果类型 T1 和 T2 中只要有一个是非定义类型,并且它们的底层类型一致(考虑结构体类型的标签),则此转换可以是隐式的。
  2. 类型 T1 和 T2 都为非定义类型并且它们的基类型的底层类型一致(忽略结构体类型字段的标签)。
type MyInt int64
type Ta *int64
type Tb *MyInt


对于上述的指针类型,下面的结论成立:


  • 类型 int64 的值可以被隐式转换到类型 Ta,反之亦然(因为它们的底层类型均为 int64)。
  • 类型 MyInt 的值可以被隐式转换到类型 Tb,反之亦然(因为它们的底层类型均为 MyInt)。
  • 类型 *MyInt 的值可以被显式转换为类型 *int64 ,反之亦然(因为它们都是非定义的并且它们的基类型的底层类型均为 int64)。
  • 类型 Ta 的值不能直接被转换为类型 Tb,即使是显式转换也是不行的。 但是,通过上述三条事 实,通过三层显式转换 Tb((*MyInt)((*int64)(ta))) ,一个类型为 Ta 的值 ta 可以被间接地转换为类型 Tb。


这些指针类型的任何值都无法被转换到类型 *uint64。

一个指针值不能和其它任一指针类型的值进行比较

Go 指针值是支持(使用比较运算符==和!=)比较的。 但是,两个指针只有在下列任一条件被满足的时候才可以比较:


  • 这两个指针的类型相同
  • 其中一个指针可以被隐式转换为另一个指针的类型。换句话说,这两个指针类型的底层类型必须一致并且其中一个指针类型为非定义的(考虑结构体字段的标签)
  • 其中一个并且只有一个指针类型不确定的 nil 标识符表示
package main
func main() {
    type MyInt int64
    type Ta *int64
    type Tb *MyInt
    // 4 个不同类型的指针
    var pa0 Ta
    var pa1 *int64
    var pb0 Tb
    var pb1 *MyInt
    // 下面 6 行编译没问题,它们的比较结果都为true 
    _ = pa0 == pa1
    _ = pb0 == pb1
    _ = pa0 == nil
    _ = pa1 == nil
    _ = pb0 == nil
    _ = pb1 == nil
    // 下面三行编译不通过
    _ = pa0 == pb0
    _ = pa1 == pb1
    _ = pa0 == Tb(nil)
}


image.png

一个指针值不能被赋值给其他任意类型的指针值

一个指针值可以被赋值给另一个指针值的条件和这两个指针值可以比较的条件是一致的。

总结

最后,我们总结一下这篇文章:


  • Go 语言保留了 C 语言的指针,指针用来指向变量的位置
  • 可以利用指针更改函数参数的值
  • 使用 * (星号)来定义一个指针,后面跟指针指向变量的类型,使用 & (取地址操作)来访问一个变量的位置
  • 可以使用 new 函数来创建一个指针
  • Go 指针不支持算术元素,不能随意更改类型,不能随意比较
相关文章
|
3天前
|
编译器 Go
揭秘 Go 语言中空结构体的强大用法
Go 语言中的空结构体 `struct{}` 不包含任何字段,不占用内存空间。它在实际编程中有多种典型用法:1) 结合 map 实现集合(set)类型;2) 与 channel 搭配用于信号通知;3) 申请超大容量的 Slice 和 Array 以节省内存;4) 作为接口实现时明确表示不关注值。此外,需要注意的是,空结构体作为字段时可能会因内存对齐原因占用额外空间。建议将空结构体放在外层结构体的第一个字段以优化内存使用。
|
3天前
|
存储 缓存 监控
企业监控软件中 Go 语言哈希表算法的应用研究与分析
在数字化时代,企业监控软件对企业的稳定运营至关重要。哈希表(散列表)作为高效的数据结构,广泛应用于企业监控中,如设备状态管理、数据分类和缓存机制。Go 语言中的 map 实现了哈希表,能快速处理海量监控数据,确保实时准确反映设备状态,提升系统性能,助力企业实现智能化管理。
21 3
|
2天前
|
运维 监控 算法
监控局域网其他电脑:Go 语言迪杰斯特拉算法的高效应用
在信息化时代,监控局域网成为网络管理与安全防护的关键需求。本文探讨了迪杰斯特拉(Dijkstra)算法在监控局域网中的应用,通过计算最短路径优化数据传输和故障检测。文中提供了使用Go语言实现的代码例程,展示了如何高效地进行网络监控,确保局域网的稳定运行和数据安全。迪杰斯特拉算法能减少传输延迟和带宽消耗,及时发现并处理网络故障,适用于复杂网络环境下的管理和维护。
|
3天前
|
存储 缓存 安全
Go 语言中的 Sync.Map 详解:并发安全的 Map 实现
`sync.Map` 是 Go 语言中用于并发安全操作的 Map 实现,适用于读多写少的场景。它通过两个底层 Map(`read` 和 `dirty`)实现读写分离,提供高效的读性能。主要方法包括 `Store`、`Load`、`Delete` 等。在大量写入时性能可能下降,需谨慎选择使用场景。
|
4天前
|
SQL 安全 Java
阿里双十一背后的Go语言实践:百万QPS网关的设计与实现
解析阿里核心网关如何利用Go协程池、RingBuffer、零拷贝技术支撑亿级流量。 重点分享: ① 如何用gRPC拦截器实现熔断限流; ② Sync.Map在高并发读写中的取舍。
|
7天前
|
存储 Go
Go 语言入门指南:切片
Golang中的切片(Slice)是基于数组的动态序列,支持变长操作。它由指针、长度和容量三部分组成,底层引用一个连续的数组片段。切片提供灵活的增减元素功能,语法形式为`[]T`,其中T为元素类型。相比固定长度的数组,切片更常用,允许动态调整大小,并且多个切片可以共享同一底层数组。通过内置的`make`函数可创建指定长度和容量的切片。需要注意的是,切片不能直接比较,只能与`nil`比较,且空切片的长度为0。
Go 语言入门指南:切片
|
11天前
|
算法 安全 Go
公司局域网管理系统里的 Go 语言 Bloom Filter 算法,太值得深挖了
本文探讨了如何利用 Go 语言中的 Bloom Filter 算法提升公司局域网管理系统的性能。Bloom Filter 是一种高效的空间节省型数据结构,适用于快速判断元素是否存在于集合中。文中通过具体代码示例展示了如何在 Go 中实现 Bloom Filter,并应用于局域网的 IP 访问控制,显著提高系统响应速度和安全性。随着网络规模扩大和技术进步,持续优化算法和结合其他安全技术将是企业维持网络竞争力的关键。
26 2
公司局域网管理系统里的 Go 语言 Bloom Filter 算法,太值得深挖了
|
6天前
|
开发框架 前端开发 Go
eino — 基于go语言的大模型应用开发框架(二)
本文介绍了如何使用Eino框架实现一个基本的LLM(大语言模型)应用。Eino中的`ChatModel`接口提供了与不同大模型服务(如OpenAI、Ollama等)交互的统一方式,支持生成完整响应、流式响应和绑定工具等功能。`Generate`方法用于生成完整的模型响应,`Stream`方法以流式方式返回结果,`BindTools`方法为模型绑定工具。此外,还介绍了通过`Option`模式配置模型参数及模板功能,支持基于前端和用户自定义的角色及Prompt。目前主要聚焦于`ChatModel`的`Generate`方法,后续将继续深入学习。
93 6
|
7天前
|
存储 开发框架 Devops
eino — 基于go语言的大模型应用开发框架(一)
Eino 是一个受开源社区优秀LLM应用开发框架(如LangChain和LlamaIndex)启发的Go语言框架,强调简洁性、可扩展性和可靠性。它提供了易于复用的组件、强大的编排框架、简洁明了的API、最佳实践集合及实用的DevOps工具,支持快速构建和部署LLM应用。Eino不仅兼容多种模型库(如OpenAI、Ollama、Ark),还提供详细的官方文档和活跃的社区支持,便于开发者上手使用。
70 8
|
17天前
|
监控 Linux PHP
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
68 20

热门文章

最新文章