英文:
golang pointers on pointers as function parameters
问题
我有以下的函数:
func addCatsToMap(m map[string][]CatHouse, meowId int, treats Set, dog *Dog) {
//如果(复杂的事情)将猫添加到m中
}
其中,Set
是treats
的类型,它是一个具有以下定义的接口:
type Set interface {
Add(value string)
Contains(value string) (bool)
Length() (int)
RemoveDuplicates()
}
问题:
m
、treats
和dog
是按引用传递的,而meowId
的值是被复制的吗?
我假设:
m
是按引用传递的,因为它是一个映射dog
是一个结构体。所以,我应该传递指针以避免复制数据。
英文:
I have the following function:
func addCatsToMap(m map[string][]CatHouse, meowId int, treats Set, dog *Dog) {
//if (complicated thing) add Cat to m
}
where Set
, the type of treats
, is an interface with the following definition:
type Set interface {
Add(value string)
Contains(value string) (bool)
Length() (int)
RemoveDuplicates()
}
Question:
Is it true that m
, treats
, and dog
are passed-by-reference, and meowId
has it's value copied?
I assume that:
m
is pass-by-reference because it's a mapdog
is a struct. So, I should pass the pointer to avoid copying the data
答案1
得分: 45
接口类型只是一组方法。请注意,接口定义的成员不指定接收器类型是否为指针。这是因为值类型的方法集是其关联指针类型的方法集的子集。也就是说,如果你有以下代码:
type Whatever struct {
Name string
}
并且你定义了以下两个方法:
func (w *Whatever) Foo() {
...
}
func (w Whatever) Bar() {
...
}
那么类型Whatever
只有方法Bar()
,而类型*Whatever
有方法Foo()
和Bar()
。这意味着如果你有以下接口:
type Grits interface {
Foo()
Bar()
}
那么*Whatever
实现了Grits
,但Whatever
没有实现,因为Whatever
缺少方法Foo()
。当你将函数的输入定义为接口类型时,你无法知道它是指针类型还是值类型。
以下示例演示了一个以两种方式接受接口类型的函数:
package main
import "fmt"
type Fruit struct {
Name string
}
func (f Fruit) Rename(name string) {
f.Name = name
}
type Candy struct {
Name string
}
func (c *Candy) Rename(name string) {
c.Name = name
}
type Renamable interface {
Rename(string)
}
func Rename(v Renamable, name string) {
v.Rename(name)
// 此时,我们不知道v是指针类型还是值类型。
}
func main() {
c := Candy{Name: "Snickers"}
f := Fruit{Name: "Apple"}
fmt.Println(f)
fmt.Println(c)
Rename(f, "Zemo Fruit")
Rename(&c, "Zemo Bar")
fmt.Println(f)
fmt.Println(c)
}
你可以调用Rename(&f, "Jorelli Fruit")
,但不能调用Rename(c, "Jorelli Bar")
,因为Fruit
和*Fruit
都实现了Renamable
,而*Candy
实现了Renamable
,但Candy
没有实现。
http://play.golang.org/p/Fb-L8Bvuwj
英文:
An interface type is simply a set of methods. Notice that the members of an interface definition do not specify whether or not the receiver type is a pointer. That is because the method set of a value type is a subset of the method set of its associated pointer type. That's a mouthful. What I mean is, if you have the following:
type Whatever struct {
Name string
}
and you define the following two methods:
func (w *Whatever) Foo() {
...
}
func (w Whatever) Bar() {
...
}
Then the type Whatever
has only the method Bar()
, while the type *Whatever
has the methods Foo()
and Bar()
. That means if you have the following interface:
type Grits interface {
Foo()
Bar()
}
Then *Whatever
implements Grits
but Whatever
does not, because Whatever
lacks the method Foo()
. When you define the input to a function as an interface type, you have no idea whether it's a pointer or a value type.
The following example illustrates a function that takes an interface type in both ways:
package main
import "fmt"
type Fruit struct {
Name string
}
func (f Fruit) Rename(name string) {
f.Name = name
}
type Candy struct {
Name string
}
func (c *Candy) Rename(name string) {
c.Name = name
}
type Renamable interface {
Rename(string)
}
func Rename(v Renamable, name string) {
v.Rename(name)
// at this point, we don't know if v is a pointer type or not.
}
func main() {
c := Candy{Name: "Snickers"}
f := Fruit{Name: "Apple"}
fmt.Println(f)
fmt.Println(c)
Rename(f, "Zemo Fruit")
Rename(&c, "Zemo Bar")
fmt.Println(f)
fmt.Println(c)
}
you could call Raname(&f, "Jorelli Fruit")
but not Rename(c, "Jorelli Bar")
, because both Fruit
and *Fruit
implement Renamable
, while *Candy
implements Renable
and Candy
does not.
答案2
得分: 6
通过引用传递是一种语言特性,在Go语言中没有"通过引用传递"。通过引用传递意味着赋值运算符在单独使用时可以改变原始值。然而,有一些引用类型,比如映射和指针,它们指向某个位置。对它们使用赋值运算符不会修改原始值,除非使用其他运算符,比如映射索引和*
运算符。
你说的m
映射是引用类型,因此类似于指针。除了替换映射之外,对映射的任何更改都会修改原始值。
m["whatever"] = 2 // 修改原始映射
m = anothermap // 不会修改原始映射
如果真的是"通过引用传递",第二个例子会修改原始映射。
像你在dog
中所做的那样传递一个指针允许你修改原始值。如果调用任何指针方法或使用*
运算符,原始值将会改变。在你的例子中,可能不需要使用指针。如果Dog
很小,直接传递一个副本可能更容易。程序员需要确定何时使用指针是一个好时机。
Set
不是通过引用传递的。**接口不是引用。**虽然在6g编译器内部接口使用指针,但接口本身并不像指针那样工作。使用6g编译器传递接口,无论对象的大小如何,都和传递指针一样廉价。然而,无法像指针和映射那样修改接口的原始值。
虽然无法修改传递的原始接口,但接口可以包含指针类型。在这种情况下,它将像dog
指针一样工作,调用某些方法可以修改原始值。对于你的特定Set
接口,我猜测它包含一个指针类型,基于方法名。因此,当你调用set.Add(whatever)
时,它将改变原始数据的内部。
英文:
Pass by reference is a language thing, nothing in Go is "pass by reference". Pass by reference means the assignment operator can change the original value when use alone. However, there are reference types such as maps and pointers which point somewhere. Using the assignment operator on them will not modify the original unless you use other operators such as the map index and the *
operator.
You are correct that your map m
is a reference type and therefore like a pointer. Any changes to the map except replacing the map will modify the original.
m["whatever"] = 2 // Modifies the original map
m = anothermap // Does not modify the original map
If there was true "pass by reference", the second example would modify the original map.
Passing a pointer, as you do with dog
allows you to modify the original. If you call any pointer methods or use the *
operator, the original will change. In your example, a pointer may not have been needed. If Dog
is small, it may be easier to just pass a copy. It is up to the programmer to determine when it is a good time to use a pointer.
Set
is not passed by reference. Interfaces are not references. While it is true that internally in the 6g compiler an interface uses pointers, the interface itself does not act like one. Passing an interface, no matter of the size of the object it contains, is as cheap as passing a pointer using the 6g compiler. However, there is no way to modify the original value of an interface as you can with pointers and maps.
Although you can not modify the original interface passed, the interface can contain a pointer type. In that case it would act just like the dog pointer where the calling of certain methods can modify the original. For your particular Set
interface, I would guess it contains a pointer type based on the method names. So when you call set.Add(whatever)
, it will change the internal data of the original.
答案3
得分: 3
在函数调用中,函数值和参数按照通常的顺序进行评估。在它们被评估之后,调用的函数的参数通过值传递给函数,并且被调用的函数开始执行。当函数返回时,函数的返回参数通过值传递回调用函数。
与C语言系列中的所有语言一样,Go语言中的所有内容都是通过值传递的。也就是说,函数总是得到被传递的东西的副本,就好像有一个赋值语句将值赋给参数一样。例如,将int值传递给函数会复制int的值,将指针值传递给函数会复制指针,但不会复制指针所指向的数据。(有关此对方法接收器的影响的讨论,请参见下一节。)
映射和切片值的行为类似于指针:它们是包含指向底层映射或切片数据的指针的描述符。复制映射或切片值不会复制它们指向的数据。复制接口值会复制存储在接口值中的内容。如果接口值持有一个结构体,复制接口值会复制结构体。如果接口值持有一个指针,复制接口值会复制指针,但不会复制指针所指向的数据。
英文:
> Calls, The Go Programming Language Specification
>
> In a function call, the function value and arguments are evaluated in
> the usual order. After they are evaluated, the parameters of the call
> are passed by value to the function and the called function begins
> execution. The return parameters of the function are passed by value
> back to the calling function when the function returns.
>
> When are function parameters passed by value? FAQ - The Go
> Programming Language.
>
> As in all languages in the C family,
> everything in Go is passed by value. That is, a function always gets a
> copy of the thing being passed, as if there were an assignment
> statement assigning the value to the parameter. For instance, passing
> an int value to a function makes a copy of the int, and passing a
> pointer value makes a copy of the pointer, but not the data it points
> to. (See the next section for a discussion of how this affects method
> receivers.)
>
> Map and slice values behave like pointers: they are descriptors that
> contain pointers to the underlying map or slice data. Copying a map or
> slice value doesn't copy the data it points to. Copying an interface
> value makes a copy of the thing stored in the interface value. If the
> interface value holds a struct, copying the interface value makes a
> copy of the struct. If the interface value holds a pointer, copying
> the interface value makes a copy of the pointer, but again not the
> data it points to.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论