为了账号安全,请及时绑定邮箱和手机立即绑定

在结构字段中使用指针的区别

在结构字段中使用指针的区别

Go
慕后森 2022-05-10 16:05:29
我们可以通过这种方式在 golang 中创建结构。下面的例子:这两者有什么区别?// Usual waytype Employee struct {    firstName string    `json:"name"`    salary    int       `json:"salary"`    fullTime  bool      `json:"fullTime"`    projects  []Project `json:"projects"`}// Un-usal way with pointerstype Employee struct {    firstName *string    `json:"name"`    salary    *int       `json:"salary"`    fullTime  *bool      `json:"fullTime"`    projects  *[]Project `json:"projects"`}有没有像内存这样的权衡?更新:假设以下函数:// this function consumes MORE memoryfunc printEmployeeWithoutPointer(employee Employee) {    // print here}// this function consumes LESS memoryfunc printEmployeeWithPointer(employee *Employee) {    // print here}
查看完整描述

3 回答

?
一只萌萌小番薯

TA贡献1795条经验 获得超7个赞

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


type Employee struct {

    FirstName *string `json:"name"`

    Salary    *int    `json:"salary"`

    FullTime  *bool   `json:"fullTime"`

}

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


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


type Foo struct {

    Bar string  `json:"bar"`

    Foo *string `json:"foo,omitempty"`

}

当我解组一条没有bar值的消息时,该Bar字段将只是一个空字符串。这使得很难确定该字段是否已发送。尤其是在处理整数时:如何区分未发送的字段与发送值为 0 的字段?

使用指针字段,并指定omitempty允许您这样做。如果 JSON 数据中未指定该字段,则结构中的字段将为nil,如果没有:它将指向值为 0 的整数。


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


陷阱

指针允许您更改它们指向的值

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


func (e Employee) SetName(name string) {

    if e.Firstname == nil {

        e.Firstname = &name

        return

    }

    *e.Firstname = name

}

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


在 上实现相同的功能EmployeeV,但是:


func (e EmployeeV) SetName(name string) {

    e.Firstname = name

}

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


type Employee struct {

    Firstname string

    // other fields

}


func (e *Employee) SetName(name string) {

    e.Firstname = name

}

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


数据竞赛

与往常一样:如果您使用指针,您实际上是在允许代码操作直接指向的内存。鉴于 golang 是一种众所周知的促进并发性的语言,并且访问相同的内存位意味着您有创建数据竞争的风险:


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 竞争检测器很可能会将此代码标记为潜在的数据竞争。


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


查看完整回答
反对 回复 2022-05-10
?
天涯尽头无女友

TA贡献1831条经验 获得超9个赞

我们使用指针来共享数据,但这并不总是意味着它具有更高的内存效率或更高的性能。Go 在复制数据方面非常出色且快速。


当谈到结构时,使用指针的一个常见原因是指针可以具有nil值,而基元则不能。如果你需要一个带有可选字段的结构,你会使用指针


如果您要反序列化 JSON,那么您可以使用omitempty. 这里 fullTime 是可选的


type Employee struct {

    firstName string `json:"name"`

    salary int `json:"salary"`

    fullTime *bool `json:"fullTime,omitempty"`

}

使用 JSON 时的性能


如果您将 JSON 反序列化为指针以希望节省内存,则不会。从 JSON 的角度来看,每个项目都是唯一的,因此不存在数据共享。您将使用更多内存,因为现在每个值都必须存储一个值和一个指向该值的指针。而且它会更慢,因为您需要一直取消引用指针


查看完整回答
反对 回复 2022-05-10
?
慕娘9325324

TA贡献1783条经验 获得超4个赞

仅供参考,补充阅读https://github.com/golang/go/wiki/CodeReviewComments#pass-values

传递值 不要将指针作为函数参数传递,只是为了节省几个字节。如果一个函数始终将其参数 x 称为 *x,则该参数不应是指针。这种情况的常见实例包括传递指向字符串的指针 (*string) 或指向接口值的指针 (*io.Reader)。在这两种情况下,值本身都是固定大小的,可以直接传递。此建议不适用于大型结构,甚至可能增长的小型结构。


查看完整回答
反对 回复 2022-05-10
  • 3 回答
  • 0 关注
  • 136 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信