在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!]

结论

相关推荐
2401_857439691 小时前
Spring Boot新闻推荐系统:用户体验优化
spring boot·后端·ux
进击的女IT2 小时前
SpringBoot上传图片实现本地存储以及实现直接上传阿里云OSS
java·spring boot·后端
一 乐3 小时前
学籍管理平台|在线学籍管理平台系统|基于Springboot+VUE的在线学籍管理平台系统设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·学习
艾伦~耶格尔6 小时前
Spring Boot 三层架构开发模式入门
java·spring boot·后端·架构·三层架构
man20176 小时前
基于spring boot的篮球论坛系统
java·spring boot·后端
攸攸太上7 小时前
Spring Gateway学习
java·后端·学习·spring·微服务·gateway
罗曼蒂克在消亡7 小时前
graphql--快速了解graphql特点
后端·graphql
潘多编程7 小时前
Spring Boot与GraphQL:现代化API设计
spring boot·后端·graphql
大神薯条老师8 小时前
Python从入门到高手4.3节-掌握跳转控制语句
后端·爬虫·python·深度学习·机器学习·数据分析
2401_857622668 小时前
Spring Boot新闻推荐系统:性能优化策略
java·spring boot·后端