在结构字段中使用指针的区别 [英] Difference using pointer in struct fields

查看:13
本文介绍了在结构字段中使用指针的区别的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们可以通过这种方式在 golang 中创建结构体.下面的例子:这两者有什么区别?

We can create structs in golang this way. Examples below: What are differences between these two?

// Usual way
type Employee struct {
    firstName string    `json:"name"`
    salary    int       `json:"salary"`
    fullTime  bool      `json:"fullTime"`
    projects  []Project `json:"projects"`
}

// Un-usal way with pointers
type Employee struct {
    firstName *string    `json:"name"`
    salary    *int       `json:"salary"`
    fullTime  *bool      `json:"fullTime"`
    projects  *[]Project `json:"projects"`
}

有没有像内存这样的权衡?

Are there any trade-offs like memory?

更新:

假设以下函数:

// this function consumes MORE memory
func printEmployeeWithoutPointer(employee Employee) {
    // print here
}

// this function consumes LESS memory
func printEmployeeWithPointer(employee *Employee) {
    // print here
}

推荐答案

是的,有很多事情需要考虑.首先:让我们从指针示例中明显的语法错误开始:

Right, there's a number of things to consider. First up: let's start with the obvious syntax error in your pointer example:

type Employee struct {
    FirstName *string `json:"name"`
    Salary    *int    `json:"salary"`
    FullTime  *bool   `json:"fullTime"`
}

所以我已经将星号移到了类型上,并且我已经将字段大写了.encoding/json 包使用反射来设置字段的值,因此需要将它们导出.

So I've moved the asterisk to the type, and I've captialized the fields. The encoding/json package uses reflection to set the values of the fields, so they need to be exported.

看到您使用的是 json 标签,让我们从简单的事情开始:

Seeing as you're using json tags, let's start with the simple things:

type Foo struct {
    Bar string  `json:"bar"`
    Foo *string `json:"foo,omitempty"`
}

当我解组没有 bar 值的消息时,Bar 字段将只是一个空字符串.这使得很难确定该字段是否已发送.特别是在处理整数时:如何区分未发送的字段与已发送值为 0 的字段之间的区别?
使用指针字段并指定 omitempty 允许您这样做.如果该字段未在 JSON 数据中指定,则结构中的字段将为 nil,否则:它将指向值为 0 的整数.

When I'm unmarshalling a message that has no bar value, the Bar field will just be an empty string. That makes it kind of hard to work out whether or not the field was sent or not. Especially when dealing with integers: how do I tell the difference between a field that wasn't sent vs a field that was sent with a value of 0?
Using a pointer field, and specify omitempty allows you to do that. If the field wasn't specified in the JSON data, then the field in your struct will be nil, if not: it'll point to an integer of value 0.

当然,必须检查指针是否为 nil 可能很乏味,这会使代码更容易出错,因此您只需要在有实际原因想要区分不为空的字段时才需要这样做设置,和一个零值.

Of course, having to check for pointers being nil can be tedious, it makes code more error-prone, and so you only need to do so if there's an actual reason why you'd want to differentiate between a field not being set, and a zero value.

让我们继续讨论指针本身带来的风险.假设您的 Employee 结构具有指针字段,并且名为 EmployeeV 的类型相同但具有值字段,请考虑以下函数:

Let's move on to the risks pointers inherently bring with them. Assuming your Employee struct with pointer fields, and a type called EmployeeV that is the same but with value fields, consider these functions:

func (e Employee) SetName(name string) {
    if e.Firstname == nil {
        e.Firstname = &name
        return
    }
    *e.Firstname = name
}

现在这个功能只会在一半的时间里工作.您正在值接收器上调用 SetName.如果 Firstname 为 nil,那么您将在原始变量的副本上设置指针,并且您的变量不会反映您在函数中所做的更改.但是,如果设置了 Firstname ,则副本将指向与原始变量相同的字符串,并且该指针指向的值将 更新.这很糟糕.

Now this function is only going to work half of the time. You're calling SetName on a value receiver. If Firstname is nil, then you're going to set the pointer on a copy of your original variable, and your variable will not reflect the change you made in the function. If Firstname was set, however, the copy will point to the same string as your original variable, and the value that pointer points to will get updated. That's bad.

EmployeeV 上实现相同的函数,但是:

Implement the same function on EmployeeV, however:

func (e EmployeeV) SetName(name string) {
    e.Firstname = name
}

而且它根本不会永远起作用.您将始终更新副本,并且更改不会影响您调用 SetName 函数的变量.出于这个原因,在 go 中,做这样的事情的惯用方式是:

And it simply won't ever work. You'll always update a copy, and the changes won't affect the variable on which you call the SetName function. For that reason, the idiomatic way, in go, to do something like this would be:

type Employee struct {
    Firstname string
    // other fields
}

func (e *Employee) SetName(name string) {
    e.Firstname = name
}

所以我们改变了方法以使用指针接收器.

So we're changing the method to use a pointer receiver.

与往常一样:如果您使用指针,则本质上是允许代码直接操作某些指向的内存.鉴于 golang 是一种以促进并发而闻名的语言,并且访问相同的内存意味着您面临着创建数据竞争的风险:

As always: if you're using pointers, you're essentially allowing code to manipulate the memory something points to directly. Given how golang is a language that is known to facilitate concurrency, and accessing the same bit of memory means you're at risk of creating data-races:

func main() {
    n := "name"
    e := Employee{
        Firstname: &n,
    }
    go func() {
         *e.Firstname = "foo"
    }()
    race(e)
}

func race(e Employee) {
    go race(e)
    go func() {
        *e.Firstname = "in routine"
    }()
    *e.Firstname = fmt.Sprintf("%d", time.Now().UnixNano())
}

这个 Firstname 字段在许多不同的例程中被访问.它的最终价值是多少?你还知道吗?golang 竞争检测器很可能会将此代码标记为潜在的数据竞争.

This Firstname field is accessed in a lot of different routines. What will be its eventual value? Do you even know? The golang race detector will most likely flag this code as a potential data race.

就内存使用而言:像 int 或 bool 这样的单个字段确实不是您应该担心的事情.如果您正在传递一个相当大的结构,并且您知道它是安全的,那么传递一个指向该结构的指针可能是个好主意.再说一次,通过指针访问值而不是直接访问它们不是免费的:间接增加了少量开销.

In terms of memory use: individual fields like ints or bools really aren't the thing you ought to be worried about. If you're passing around a sizeable struct, and you know it's safe, then it's probably a good idea to pass around a pointer to said struct. Then again, accessing values through a pointer rather than accessing them directly isn't free: indirection adds a small overhead.

这篇关于在结构字段中使用指针的区别的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆