在Go中,是否该使用切片的指针?

原文地址:www.willem.dev/articles/sh...

如果你对Go语言的切片还不是很熟悉,那么,在将切片作为参数传递的时候,最好是用指针,而非传值。例如:

go 复制代码
s := []string{"👁", "👃", "👁"}
doSomething(&s)
andAgain(&s)

但是,这也并没有什么优点。

实际上,通过传递切片,你已经传递了一个指针:切片包含指向底层数组的指针。如下:

通过使用指向切片(slice)的指针,你本质上是在使用双指针(一个指向切片的指针,它反过来指向一个数组)。

这不仅仅是一个概念问题。这种额外的间接调用将使你的代码更加复杂。

复杂性1:指针可能会是nil 所有与切片一起使用的内置 Go 函数(如 append()、copy()、len() 和 cap())都要求切片是传值输入。而不是指向切片的指针。 在调用这些函数之前,您需要取消引用指针。

你只能对非nil的指针进行取消引用指针,因为一个nil指针不会指向任何值。

如果,你在代码中引用一个nil指针,那么会引起panic。如下:

go 复制代码
panic: runtime error: invalid memory address or nil pointer dereference

因此,在引用指针时,你必须要检查指针是否是nil。如下:

go 复制代码
package main

import "fmt"

func main() {
	s := []string{"👁", "👃", "👁"}
	printLen(&s)
	printLen(nil)
}

func printLen(sPtr *[]string) {
	// before we dereference the pointer we check if it is not nil.
	if sPtr != nil {
		// dereference the pointer when getting the length.
		fmt.Printf("the length of the slice is: %d\n", len(*sPtr))
	} else {
		fmt.Println("pointer is nil")
	}
}

这种做法会导致大量的额外代码,同时,如果你忘记了检查指针是否为nil,就会有panic的风险。

复杂性2:slice可能为nil 和指针类似,slice本身可能是nil。

通常不需要检查slice是否为nil(所有内置的切片函数都能处理它),但有时这是必要的。 如果你要检查slice是否为nil,首先需要检查指针是否为 nil,然后检查 slice 是否为 nil。 如下:

go 复制代码
if sPtr != nil && *sPtr == nil {
	// do something when slice is nil.
}

当阅读这样的代码时,会付出大量额外的精力。因此,不使用指针的方案会更容易接受。如下:

go 复制代码
if s == nil {
	// do something when slice is nil.
}

复杂度3:其他Go研发者 除非有特殊原因,否则其他 Go 程序员不会在他们的代码中使用切片指针。为了与其他程序员顺利合作,尽可能地遵守通用惯例是一个好主意。

合适使用切片指针

正如你所看到的,一般来说,你应该使用切片而不是指向切片的指针。 但是,在某些情况下,指向切片的指针是有用的吗?

解码和反序列化数据

使用切片指针的最常见情况应该是解码或反序列化数据时。 标准库中的 gob 、 xml 和 json 包都是以类似方式工作的函数和方法:

go 复制代码
func (d *Decoder) Decode(v any) error

func Unmarshal(data []byte, v any) error

对于这两个函数,v是需要指向你想解码的变量或将数据反序列化到其中的指针。这意味着如果你想解码或将数据反序列化到切片中,你需要传递一个指向切片的指针。

例如,如果你想把一个json数组反序列化到一个切片中:

go 复制代码
package main

import (
	"encoding/json"
	"fmt"
	"log"
)

func main() {
	data := []byte("[\"👁\", \"👃\", \"👁\"]")
	var target []string
	// unmarshal needs a pointer to the target slice.
	err := json.Unmarshal(data, &target)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(target)
}

自定义类型的指针接收者

可以定义一个自定义类型,该类型以切片作为其基础类型。例如:

go 复制代码
type greetings []string

定义了一个叫做 greetings的类型,该类型以字符串切片为基础类型。 如果我们想实现一个操作切片的方法,一般有两个选择:

  • 使用值接收器。
  • 使用指针接收器。

假设我们想在每次调用 AddOne 方法时都需要append一个问候语。

如果我们使用值接收器,那么调用该方法后会返回一个新的 greetings的值,如下:

go 复制代码
func (g greetings) AddOne() greetings {
	return append(g, "hello!")
}

并且将由调用者来处理调用 AddOne 的结果。

go 复制代码
var g greetings
g = g.AddOne()
fmt.Println(g) // prints: [hello!]

如果我们使用指针接收器,就可以在方法内部直接使用接收器,并且也不需要再返回一个新的 greetings的值。如下:

go 复制代码
func (g *greetings) AddOne() {
	*g = append(*g, "hello!")
}

现在,调用者看起来像这样:

go 复制代码
g := &greetings{}
g.AddOne()
fmt.Println(g) // prints: &[hello!]

结论

相关推荐
沈霁晨17 分钟前
Ruby语言的Web开发
开发语言·后端·golang
DanceDonkey20 分钟前
@RabbitListener处理重试机制完成后的异常捕获
开发语言·后端·ruby
平凡的运维之路1 小时前
vsftpd虚拟用户部署
后端
叫我:松哥1 小时前
基于Python django的音乐用户偏好分析及可视化系统设计与实现
人工智能·后端·python·mysql·数据分析·django
Leaf吧4 小时前
springboot 配置多数据源以及动态切换数据源
java·数据库·spring boot·后端
代码驿站5204 小时前
JavaScript语言的软件工程
开发语言·后端·golang
uccs5 小时前
使用 rust 创建多线程 http-server
后端·rust
Archy_Wang_15 小时前
ASP.NET Core 中的 JWT 鉴权实现
后端·ui·asp.net
Archy_Wang_15 小时前
ASP.NET Core中 JWT 实现无感刷新Token
后端·asp.net
用户49824901880135 小时前
VipSearchBuilder 技术文档
go