Go struct方法允许类型混合?

Gil*_*iwu 0 go

我有一个简单的结构,只有一个方法:

type Person struct {
  name string
}

func (p Person) SetName(name string) {
  p.name = name
}
Run Code Online (Sandbox Code Playgroud)

以下输出:

dave := Person{}
dave.SetName("Dave")
fmt.Println(dave.name)
Run Code Online (Sandbox Code Playgroud)

将是空的,因为方法接收器接受一个值(或更准确地创建您传递的值的副本),因此它不会修改您的基础值.

如果我将方法更改为:

func (p *Person) SetName(name string) {
  p.name = name
}
Run Code Online (Sandbox Code Playgroud)

输出将是"戴夫".

现在我不明白的是我不应该在指针上调用方法吗?所以在初始化我的对象时我应该这样做:

dave := &Person{}
Run Code Online (Sandbox Code Playgroud)

而不是这个:

dave := Person{}
Run Code Online (Sandbox Code Playgroud)

另外使用go的反射包我试图找出以下值:

&Person
Run Code Online (Sandbox Code Playgroud)

并发现它是*人,这很好.当我打印值虽然我没有得到内存位置相比,当我打印指向int的指针的值时:

a := 4
fmt.Println(&a)
Run Code Online (Sandbox Code Playgroud)

我一直在阅读文档,提出问题,但是我学的越多,我就越想知道我是否遗漏了一些简单的东西,因为很多人并不认为这一切都让人困惑.

kos*_*tix 6

你在控制一个指针接收器上的方法并保持指向一个值的指针时会感到困惑.也就是说,在指针接收器上定义的调用与存储指向存储器的指令之间没有固有的连接SetName,该存储器保持类型的值直接Person存储Person.

当你有声明

var pers Person
Run Code Online (Sandbox Code Playgroud)

然后打电话

pers.SetName("foo")
Run Code Online (Sandbox Code Playgroud)

编译器获取变量占用的内存块的地址 - pers就像你通过应用&运算符手动执行该操作一样pers,并将该地址传递给SetName 函数,因此调用最终会像

(&pers).SetName("foo")
Run Code Online (Sandbox Code Playgroud)

或者,换句话说,SetName接收器将是 &pers- 也就是地址pers.

现在没有什么可以阻止你获取地址pers 并将其存储在其他地方:

var ptr *Person = &pers
Run Code Online (Sandbox Code Playgroud)

(你通常不会写这种Java风格的口吃代码,但为了更加清晰,我正在这样做),现在你可以SetName正确地调用 这个ptr值了:

ptr.SetName("bar")
Run Code Online (Sandbox Code Playgroud)

现在,编译器将直接使用存储在变量中的值ptr 将其作为方法的接收器传递.

这个"难题"的最后一点是,当你使用Go-将地址获取操作符& 直接应用于文字时提供的特殊语法时,编译器会执行以下操作:

var __pers Person = Person{
    // The contents of your literal
}
var dave = &__pers
Run Code Online (Sandbox Code Playgroud)

...并且__pers根据名称使变量不可访问(因为它自然没有名称).

正如您所看到的,这与Person手动获取指向值的内存没有什么不同.


您可能需要参考此FAQ条目以更深入地了解方法如何设置类型T*T关联,以及为什么后者始终包括前者但反之亦然.还读这个.


更新:解释fmt.Println格式和输出类型*int和值的差异*Person.

  • 前者打印为包含该int值的内存位置地址的base-16字符串表示.
  • 后者使用类似Go的语法的字符串表示来呈现,以获取指向文字的指针:&{}.

此行为如下所述:fmt.Print并且fmt.Println 对它们传递的参数使用所谓的默认格式规则,这些规则取决于参数的类型.

我们先来查看fmt.Println (运行go doc fmt.Println)的文档:

func Println(a ...interface{}) (n int, err error)

Println格式使用其操作数的默认格式 并写入标准输出.始终在操作数之间添加空格,并附加换行符.它返回写入的字节数和遇到的任何写入错误.

(强调我的.)

现在让我们转向fmt包本身的文档(运行go doc fmt):

<...>

印花

动词:

一般:

  • %v 默认格式的值

    在打印结构时,加号标志(%+v)添加字段名称.

<...>

默认格式为%v:

bool:                    %t
int, int8 etc.:          %d
uint, uint8 etc.:        %d, %#x if printed with %#v
float32, complex64, etc: %g
string:                  %s
chan:                    %p
pointer:                 %p
Run Code Online (Sandbox Code Playgroud)

对于复合对象,使用这些规则打印元素,递归地,如下所示:

struct:             {field0 field1 ...}
array, slice:       [elem0 elem1 ...]
maps:               map[key1:value1 key2:value2]
pointer to above:   &{}, &[], &map[]
Run Code Online (Sandbox Code Playgroud)

A *Person是指向结构类型的指针,因此它呈现为

&{name}
Run Code Online (Sandbox Code Playgroud)

namename字段的内容在哪里; 比如说,如果它被分配了字符串"Dave",那么输出就是&{dave}.