英文:
go when to use & or not?
问题
我很困惑是否在使用Go语言声明变量并使用结构体进行初始化时需要使用&
。
假设我们有一个结构体包装器:
type HttpResult struct {
Status int32 `json:"status"`
Msg string `json:"msg"`
Data interface{} `json:"data,omitempty"`
}
还有一个定义用户模型的结构体:
type OmUser struct {
Id primitive.ObjectID `json:"id" bson:"_id,omitempty"`
Name string `json:"name"`
Password string `json:"password"`
Email string `json:"email"`
}
以下两种声明方式似乎会得到相同的结果:
myOmUser := OmUser{ // 注意这里没有 & 符号
Name: "Tony",
Password: "mypass",
Email: "tony@foo.com",
}
httpResult := &HttpResult{
Status: 0,
Msg: "ok",
Data: myOmUser,
}
js, _ := json.Marshal(httpResult)
fmt.Println(js)
或者
myOmUser := &OmUser{ // 注意这里有 & 符号
Name: "Tony",
Password: "mypass",
Email: "tony@foo.com",
}
httpResult := &HttpResult{
Status: 0,
Msg: "ok",
Data: myOmUser,
}
js, _ := json.Marshal(httpResult)
fmt.Println(js)
那么,何时使用&
以及为什么要使用呢?
英文:
I am confused whether to use a &
with go when declaring a variable and init with a struct
say we have a struct wrapper
type HttpResult struct {
Status int32 `json:"status"`
Msg string `json:"msg"`
Data interface{} `json:"data,omitempty"`
}
and a struct defining the user model
type OmUser struct {
Id primitive.ObjectID `json:"id" bson:"_id,omitempty"`
Name string `json:"name"`
Password string `json:"password"`
Email string `json:"email"`
}
And the following declaring seems give the same result:
myOmUser := OmUser{ //note no & sign here
Name: "Tony",
Password: "mypass",
Email: "tony@foo.com"
}
httpResult := &HttpResult{
Status: 0,
Msg: "ok",
Data: myOmUser,
}
js, _ := json.Marshal(httpResult)
fmt.Println(js)
Or
myOmUser := &OmUser{ //note the & sign
Name: "Tony",
Password: "mypass",
Email: "tony@foo.com"
}
httpResult := &HttpResult{
Status: 0,
Msg: "ok",
Data: myOmUser,
}
js, _ := json.Marshal(httpResult)
fmt.Println(js)
so, when to use &
and why?
答案1
得分: 1
在你的特定示例中,这并没有什么区别。
但是当我们看一个使用json.Unmarshal()
的例子时,它就更有意义了:
jsonBlob := []byte(`{"id": "1", "name": "bob", "password": "pass", "email", "hi@me.com"}`)
var newOmUser OmUser
err := json.Unmarshal(jsonBlob, &newOmUser)
if err != nil {
panic(err)
}
在这里,我们事先声明了变量,然后我们使用&
将一个指向该变量的指针传递给Unmarshal
函数。
这意味着Unmarshal
函数可以访问并更新该变量,即使它在函数外部声明。
如果没有&
,Unmarshal
函数将获得newOmUser
变量的副本,并且会使我们声明的原始newOmUser
变量为空。
当涉及到指针时,我的一般经验法则是:
除非必须使用,否则不要使用指针。
如果你需要使用任何解组函数,你将需要它们。还有很多其他使用它们的函数。
这是一个帮助我更好理解指针的快速练习:
func uppercase(s string) {
s = strings.ToUpper(s)
fmt.Println(s)
}
// 与uppercase()函数相同,但使用指针工作。
func uppercasePointer(s *string) {
*s = strings.ToUpper(*s)
fmt.Println(*s)
}
name := "bob"
uppercase(name) // 输出'BOB'
fmt.Println(name) // 输出'bob' - 我们的变量没有改变
name2 := "bobpointer"
uppercasePointer(&name2) // 输出'BOBPOINTER'
fmt.Println(name2) // 输出'BOBPOINTER' - 我们的变量已经改变
当我们调用uppercase(name)
函数时,Go会复制name
变量并将其发送到uppercase
函数。
函数对接收到的副本所做的任何更改都会保留在函数内部。我们在函数外部声明的原始变量不会改变。
当我们调用uppercasePointer(&name2)
函数时,我们发送了一个指向我们之前声明的name2
变量的指针。
函数可以使用该指针来访问并更新我们之前声明的name2
变量。
起初,你可能看不出指针的意义,但随着你继续使用Go,你会发现它们帮助我们解决一些复杂的问题。
英文:
In your particular example it doesn't make a difference.
But when we look at an example of using json.Unmarshal()
it makes a bit more sense:
jsonBlob := []byte(`{"id": "1", "name": "bob", "password": "pass", "email", "hi@me.com"}`)
var newOmUser OmUser
err := json.Unmarshal(jsonBlob, &newOmUser)
if err != nil {
panic(err)
}
Here we declare the variable before hand, and then we use the &
to pass a pointer to that variable into the Unmarshal
function.
That means that the Unmarshal
function can reach out and update that variable, even though it's declared outside of the function.
Without the &
, the Unmarshal
function would get a copy of the newOmUser
variable, and it would leave the original newOmUser
variable that we declared empty.
When it comes to pointers, my general rule of thumb is:
> Don't use them unless you have to.
If you need to use any unmarshalling functions, you'll need them. There are lots of other functions that make use of them.
Here's a quick exercise that helps me understand a little more about pointers:
func uppercase(s string) {
s = strings.ToUpper(s)
fmt.Println(s)
}
// Same as the uppercase() function, but works with a pointer.
func uppercasePointer(s *string) {
*s = strings.ToUpper(*s)
fmt.Println(*s)
}
name := "bob"
uppercase(name) // prints 'BOB'
fmt.Println(name) // prints 'bob' - our variable was not changed
name2 := "bobpointer"
uppercasePointer(&name2) // prints 'BOBPOINTER'
fmt.Println(name2) // prints 'BOBPOINTER' - our variable was changed
When we call the uppercase(name)
function, go makes a copy of the name
variable and sends it to the uppercase
function.
Whatever the function does to that copy that it received stays in the function. The original variable that we declared outside the function is not changed.
When we call the uppercasePointer(&name2)
function, we are sending a pointer to the name2
variable we declared.
The function can use that pointer to reach out and update the name2
variable that we declared earlier.
At first, you might not see the point of pointers, but as you continue to use go, you will see that they help us solve some complex problems.
答案2
得分: 0
在Go语言中,空接口类型(empty interface)可以保存任意类型的值。所以在你的HttpResult.Data
中,它是一个空接口类型,你可以给它赋予任意类型的值。
使用&
定义变量的区别在于获取该类型的指针。这是Go语言中两种具有不同功能的类型。但是你可以将它们都赋值给空接口类型的变量,因为空接口类型可以接受任意类型的值。
package main
import (
"fmt"
"reflect"
)
type OmUser struct {
}
func main() {
myOmUser := OmUser{}
myOmUser2 := &OmUser{}
fmt.Println(reflect.TypeOf(myOmUser)) //main.OmUser
fmt.Println(reflect.TypeOf(myOmUser2)) //*main.OmUser
}
关于&
的更多细节,请阅读Go文档中的地址操作符。
> 对于类型为T的操作数x,地址操作&x会生成一个类型为*T的指针,指向x。操作数必须是可寻址的,也就是说,要么是一个变量、指针间接引用或切片索引操作;或者是可寻址的结构体操作数的字段选择器;或者是可寻址的数组的索引操作。作为对可寻址要求的例外,x也可以是一个(可能带括号的)复合字面量。如果对x的求值会导致运行时恐慌,那么对&x的求值也会如此。
英文:
Empty interface type in Go can hold values of any type. Tour here.
So in your HttpResult.Data
is an empty interface type. So you can assigne any type to it.
The difference between defining a variable with &
is getting a pointer of that type. Tour here.
Those are obviously two types with two different functionalities in Go. But you can assign both to empty interface type variable because its accepting values of any type.
package main
import (
"fmt"
"reflect"
)
type OmUser struct {
}
func main() {
myOmUser := OmUser{}
myOmUser2 := &OmUser{}
fmt.Println(reflect.TypeOf(myOmUser)) //main.OmUser
fmt.Println(reflect.TypeOf(myOmUser2)) //*main.OmUser
}
For more details about &
, read Go doc address operators
> For an
> operand x of type T, the address operation &x generates a pointer of
> type *T to x. The operand must be addressable, that is, either a
> variable, pointer indirection, or slice indexing operation; or a field
> selector of an addressable struct operand; or an array indexing
> operation of an addressable array. As an exception to the
> addressability requirement, x may also be a (possibly parenthesized)
> composite literal. If the evaluation of x would cause a run-time
> panic, then the evaluation of &x does too.
答案3
得分: 0
fooA := &Foo{}
fooA
的类型是 *Foo
。
fooB := Foo{}
fooB
的类型是 Foo
。
在实践中,这意味着如果你有一个接受 *Foo
类型的函数,你可以使用以下任一方式...
func someFunc(f *Foo) {
// ...
}
fooA := &Foo{}
someFunc(fooA)
fooB := Foo{}
someFunc(&fooB)
所以实际上,你可以根据需要创建任何一种类型。
英文:
fooA := &Foo{}
fooA
has type *Foo
.
fooB := Foo{}
fooB
has type Foo
.
https://tour.golang.org/moretypes/1
In practice, this means if you had a func that accepted type *Foo
you could do either of the following...
func someFunc(f *Foo) {
// ...
}
fooA := &Foo{}
someFunc(fooA)
fooB := Foo{}
someFunc(&fooB)
So realistically, create whichever you need to be honest.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论