【问题标题】:Go struct method allows type mixing?Go struct 方法允许类型混合?
【发布时间】:2018-08-21 10:31:21
【问题描述】:

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

type Person struct {
  name string
}

func (p Person) SetName(name string) {
  p.name = name
}

以下输出:

dave := Person{}
dave.SetName("Dave")
fmt.Println(dave.name)

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

如果我把方法改成这样:

func (p *Person) SetName(name string) {
  p.name = name
}

输出将是“戴夫”。

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

dave := &Person{}

而不是这个:

dave := Person{}

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

&Person

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

a := 4
fmt.Println(&a)

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

【问题讨论】:

  • 查看相关/可能的重复项:onetwothree
  • 感谢这些。使我的问题有点重复,但至少关于地址的部分是独一无二的。
  • 这也是其他问题的重复...
  • 你能找到一个吗?我似乎找不到任何东西。更令人困惑的是,我可以通过获取指向指针的指针来获取结构对象的地址

标签: go


【解决方案1】:

您将在指针接收器上调用方法与保持指向值的指针混淆了。也就是说,在指针接收器上定义的调用SetName 与存储指向具有Person 类型值的内存的指针与直接存储Person 之间没有固有 连接。

当你有声明时

var pers Person

然后调用

pers.SetName("foo")

编译器获取被占用的内存块的地址 变量pers——就像你通过应用手动完成一样 将& 运算符传递给pers,并将该地址传递给SetName 函数,所以调用结束就像

(&pers).SetName("foo")

或者,换一种说法,SetName 的接收者将是 &pers——即pers的地址。

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

var ptr *Person = &pers

(您通常不会编写这种 Java 风格的口吃代码,但是 我这样做是为了更清楚),现在你可以打电话了 SetName 就在 ptr 值上:

ptr.SetName("bar")

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

这个“谜题”的最后一点是,当你使用 Go 提供的特殊语法——应用地址获取运算符& 直接转换成文字——编译器会做这样的事情:

var __pers Person = Person{
    // The contents of your literal
}
var dave = &__pers

...并使__pers 变量无法通过名称访问(因为它很自然 没有名字)。

如你所见,这与获取指针没有什么不同 手动到 Person 值的内存。


您可能想咨询this FAQ entry以获得更多了解 该方法如何设置类型 T*T 相关,以及为什么后者 总是包括前者,但反之不包括。 另请阅读this


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

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

此行为已记录在案:fmt.Printfmt.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

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

struct:             {field0 field1 ...}
array, slice:       [elem0 elem1 ...]
maps:               map[key1:value1 key2:value2]
pointer to above:   &{}, &[], &map[]

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

&{name}

其中name 是名称字段的内容;说,如果它被分配 字符串“Dave”,输出将是&{dave}

【讨论】:

  • 您是说指针接收器类型变量指向不同的地址,而当我们将其用作参数时,它指向不同的地址
  • 解释问题的最后一部分关于*int被打印为地址而不是*Person,这样就完成了。
  • 很好的解释。我有点猜到 go 会为你做转换。请解释有关打印内存地址的最后一点,以便我可以接受这个作为正确答案
  • @Himanshu,我无法解析这个;需要详细说明吗?可能是一些更简单的句子。
猜你喜欢
  • 1970-01-01
  • 2019-01-25
  • 1970-01-01
  • 1970-01-01
  • 2013-08-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-09
相关资源
最近更新 更多