pointers - 在结构字段中使用指针的区别
问题描述
我们可以通过这种方式在 golang 中创建结构。下面的例子:这两者有什么区别?
// 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"`
}
有没有像内存这样的权衡?
更新:
假设以下函数:
// this function consumes MORE memory
func printEmployeeWithoutPointer(employee Employee) {
// print here
}
// this function consumes LESS memory
func printEmployeeWithPointer(employee *Employee) {
// print here
}
解决方案
是的,有很多事情需要考虑。首先:让我们从指针示例中明显的语法错误开始:
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 等单个字段确实不是您应该担心的事情。如果你正在传递一个相当大的结构,并且你知道它是安全的,那么传递一个指向所述结构的指针可能是个好主意。再说一次,通过指针访问值而不是直接访问它们不是免费的:间接增加了一点开销。
推荐阅读
- bash - 在文件更改 docker-compose 上运行入口点和 CMD
- swift - 如何使用中心点制作垂直收藏视图
- oracle - PLSQL - 从树数据结构中删除根
- python - 猴子修补 pandas 和 matplotlib 以删除 df.plot() 的刺
- c# - 如何使用 EF 将 UTF-8 字符插入到文本数据类型的 SQL Server 中?
- kubernetes - storageos ls:无法访问“卷”:传输端点未连接
- javascript - SVG 模板,放置它们以供重用的最佳位置,内部还是外部?
- sql-server - 在 Oracle 和 SQL Server 中相同的查询不同的结果
- android - 升级后第一次数据库调用时出现 SQLiteException
- arrays - 如何在 Swift 中获取指向数组的指针(而不是副本)?