深入理解Golang中指针的用途与技巧
作者:金刀大菜牙 发布时间:2024-05-21 10:23:13
在 Go 语言中,指针是一种重要的概念。了解和正确使用指针对于理解语言的底层机制、编写高效的代码以及处理复杂数据结构都非常关键。本文将深入探讨 Golang 中指针的概念、用法。
1. 指针的基本概念和语法
指针是一个存储变量内存地址的变量。它提供了直接访问内存中数据的能力,可以用于改变变量的值。在Go语言中,使用 * 表示指针类型,通过 & 操作符获取变量的地址,通过 * 操作符解引用指针获取指针指向的值。
在 Go 语言中,每个变量在运行时都具有一个地址,该地址表示变量在内存中的位置。要对变量进行"取地址"操作,可以在变量前加上 & 字符。此外,Go 语言中的值类型(如 int、float、bool、string、array 和 struct)都有相应的指针类型,分别为 *int、*float64、*bool、*string 等。
1.1 指针的声明和初始化
在 Go 语言中,可以使用指针来引用任何类型的变量。指针的声明和初始化可以通过如下语法完成:
var p *int // 声明一个指向 int 类型的指针 p
var str *string // 声明一个指向 string 类型的指针 str
初始化指针可以通过 new 函数来分配内存并返回指针的地址:
p := new(int) // 分配一个 int 类型的内存,并将指针 p 指向该内存
示例代码:
package main
?
import "fmt"
?
func main() {
var p *int
var str *string
?
fmt.Printf("p: %v, str: %v\n", p, str) // 输出 p: <nil>, str: <nil>
?
x := 10
p = &x // 将指针p指向变量x的地址
?
fmt.Printf("p: %v\n", p) // 输出 p: 0xc0000100e0
fmt.Printf("*p: %d\n", *p) // 输出 *p: 10
?
str = new(string) // 分配一个string类型的内存,并将指针str指向该内存
?
fmt.Printf("str: %v\n", str) // 输出 str: 0xc000010120
fmt.Printf("*str: %s\n", *str) // 输出 *str: ""
?
*str = "Hello, Go!" // 通过指针修改字符串的值
?
fmt.Printf("*str: %s\n", *str) // 输出 *str: Hello, Go!
}
1.2 获取指针的地址和解引用
通过 & 操作符可以获取变量的地址,例如:
x := 10
p := &x // 将指针 p 指向变量 x 的地址
?
a := 10
b := &a // 将指针 b 指向变量 a 的地址
我们来看一下 b := &a 的图示:
使用 * 操作符可以解引用指针,获取指针指向的值:
fmt.Println(*p) // 输出指针 p 指向的值,即变量 x 的值
示例代码:
func main() {
//指针取值
a := 10
b := &a // 取变量a的地址,将指针保存到b中
fmt.Printf("type of b:%T\n", b)
c := *b // 指针取值(根据指针去内存取值)
fmt.Printf("type of c:%T\n", c)
fmt.Printf("value of c:%v\n", c)
}
输出如下:
type of b:*int
type of c:int
value of c:10
取地址操作符 & 和取值操作符 * 是一对互补操作符,& 取出地址,* 根据地址取出地址指向的值。
1.3 指针作为函数参数
在 Go 语言中,函数的参数传递默认是值传递。如果想要在函数内部修改外部变量的值,可以通过传递指针来实现。
示例代码:
package main
?
import "fmt"
?
func changeValue(ptr *int) {
*ptr = 20 // 修改指针指向的值
}
?
func main() {
x := 10
changeValue(&x) // 传递x的地址给changeValue函数
fmt.Println(x) // 输出修改后的x的值,即20
}
2. 指针的应用场景
指针在 Go 语言中有着广泛的应用场景,下面将从几个方面介绍指针的常见应用。
2.1 传递大对象
在函数参数传递时,如果直接传递大对象的副本,会产生额外的内存开销。通过传递指针,可以避免复制整个对象,提高程序的性能。
示例代码:
package main
?
import "fmt"
?
type BigObject struct {
// 大对象的定义...
}
?
func processObject(obj *BigObject) {
// 对大对象进行处理...
}
?
func main() {
obj := BigObject{}
processObject(&obj) // 传递大对象的指针
}
2.2 修改函数外部变量
通过指针,函数可以修改函数外部的变量。这在需要修改外部变量的值时非常有用,特别是在处理复杂数据结构或需要对全局状态进行修改的情况下。
示例代码:
package main
?
import "fmt"
?
func modifyValue(ptr *int) {
*ptr = 30 // 修改指针指向的值
}
?
func main() {
x := 10
modifyValue(&x) // 传递x的地址给modifyValue函数
fmt.Println(x) // 输出修改后的x的值,即30
}
2.3 动态分配内存
指针的另一个重要应用是动态分配内存。通过 new 函数可以在堆上动态分配内存,避免了在栈上分配固定大小的内存空间的限制。这对于需要返回动态分配的数据或创建复杂数据结构非常有用。
示例代码:
package main
?
import "fmt"
?
type ComplexStruct struct {
// 复杂数据结构的定义...
}
?
func createComplexStruct() *ComplexStruct {
cs := new(ComplexStruct) // 动态分配内存并返回指针
// 初始化复杂数据结构...
return cs
}
?
func main() {
obj := createComplexStruct()
// 对动态分配的数据结构进行操作...
}
2.4 函数返回指针
在函数中返回指针可以将函数内部创建的变量的地址传递给调用者。这样做可以避免复制整个变量,并允许调用者直接访问和修改函数内部的数据。
示例代码:
package main
?
import "fmt"
?
func createValue() *int {
x := 10 // 在函数内部创建变量
return &x // 返回变量的地址
}
?
func main() {
p := createValue()
fmt.Println(*p) // 输出通过指针访问的函数内部变量的值,即10
}
3. new 和 make
我们先来看一个例子:
func main() {
var a *int
*a = 100
fmt.Println(*a)
?
var b map[string]int
b["沙河娜扎"] = 100
fmt.Println(b)
}
执行上面的代码会引发 panic,为什么呢?在 Go 语言中,对于引用类型的变量,在使用之前需要先进行声明,并为其分配内存空间,否则无法存储值。而对于值类型的声明,无需手动分配内存空间,因为它们在声明时已经默认分配了内存空间。为了分配内存空间,我们可以使用 Go 语言中内建的两个函数:new 和 make。这两个函数具有不同的用途,new 主要用于分配值类型的内存空间,而 make 主要用于分配引用类型(如 slice、map 和 channel)的内存空间。
3.1 new
new 是一个内置的函数,它的函数签名如下:
func new(Type) *Type
Type 表示类型,new 函数只接受一个参数,这个参数是一个类型,*Type 表示类型指针,new 函数返回一个指向该类型内存地址的指针。
示例代码:
func main() {
a := new(int)
b := new(bool)
fmt.Printf("%T\n", a) // *int
fmt.Printf("%T\n", b) // *bool
fmt.Println(*a) // 0
fmt.Println(*b) // false
}
3.2 make
make 也是用于内存分配的,区别于 new,它只用于 slice、map 以及 channel 的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。make 函数的函数签名如下:
func make(t Type, size ...IntegerType) Type
make 函数是无可替代的,我们在使用 slice、map 以及 channel 的时候,都需要使用 make 进行初始化,然后才可以对它们进行操作。
示例代码:
func main() {
var b map[string]int
b = make(map[string]int, 10)
b["沙河娜扎"] = 100
fmt.Println(b)
}
4. 总结
指针是 Go 语言中一种重要的概念,它提供了直接访问内存和修改变量值的能力。正确使用指针可以提高程序的性能、处理复杂数据结构以及实现并发编程中的数据共享和同步。
在编写代码时,我们应该充分理解指针的特性和使用注意事项,避免指针引起的错误和不确定性。合理使用指针将帮助我们编写出高效、可靠且易于维护的 Go 语言程序。
来源:https://juejin.cn/post/7236213437801087031


猜你喜欢
- 最近入了一块树莓派,想让其实现摄像头的调用,因此写下此博客备忘一、树莓派网络的配置首先,对树莓派进行网络配置,否则就无法进行软件的安装我们知
- 这些年来,我发现许多开发者对于何时使用数据操纵语言(DML)触发器与何时使用约束感到迷惑。许多时候,如果没有正确应用这两个对象,就会造成问题
- 本文实例为大家分享了vue仿写下拉菜单功能,带有过渡效果(移动端),供大家参考,具体内容如下效果图clickOutside.js 点击目标之
- 银行跨行转账业务是一个典型分布式事务场景,假设 A 需要跨行转账给 B,那么就涉及两个银行的数据,无法通过一个数据库的本地事务保证转账的 A
- 使用pip安装 pip install virtualenv因为已经安装过了,所以显示这样在这里我想在这里推荐大
- python以下是个人学习 python 研究判断ip连通性方法的集合。 缺点可能有办法解决,如有错误,欢迎矫正。方法一import osr
- 一、在linux下 删除这些目录是很简单的,命令如下 find . -type d -name ".svn"|xargs
- javascript版 俄罗斯方块(Russian box)小游戏,喜欢的朋友可以玩玩。对源代码感兴趣的朋友也可以研究一下。玩法介绍:可以输
- 前言后续还会更新更多优雅的规范。命名风格1. 【强制】代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。&n
- QSS介绍前言QSS即Qt样式表,是用来自定义控件外观的一种机制,QSS大量参考了Css的内容,但QSS的功能要比Css弱得多,体现在选择器
- TensorFlow修改变量值后,需要重新赋值,assign用起来有点小技巧,就是需要需要弄个操作子,运行一下。下面这么用是不行的impor
- 早上我偶然看见一篇介绍两个Python脚本的博文,其中一个效率更高。这篇博文已经被删除,所以我没办法给出文章链接,但脚本基本可以归结如下:f
- 为什么要使用php缓存技术?理由很简单:提高效率。在程序开发中,获取信息的方式主要是查询数据库,除此以外,也可能是通过Web Service
- 前言大家都知道其实学习Django非常简单,几乎不用花什么精力就可以入门了。配置一个url,分给一个函数处理它,返回response,几乎都
- 1、获取指定时间函数:date_format() 转换# 获取前一天时间的最大值SELECT date_format(CURRE
- 前言总结一下最近看的关于opencv图像几何变换的一些笔记. 这是原图: 1.平移import cv2import numpy as npi
- 摘要:近几天在做一个东西,其中需要对图像中的文字进行识别,看了前辈们的文章,找到两个较简单的方法:使用python的pytesseract库
- 霍夫变换是一种检测任何形状的流行技术,可以检测形状,即使它被破坏或扭曲一点点.一条线可以表示成y = mx + c或参数形式,像ρ=xcos
- 最近接触一个项目,要在多个虚拟机中运行任务,参考别人之前项目的代码,采用了多进程来处理,于是上网查了查python中的多进程一、先说说Que
- 关于Markdown在刚才的导语里提到,Markdown 是一种用来写作的轻量级「标记语言」,它用简洁的语法代替排版,而不像一般我们用的字处