douquanzhan0315 2018-03-13 08:38
浏览 31
已采纳

Go struct方法允许类型混合吗?

I have a simple struct with a single method:

type Person struct {
  name string
}

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

the output of the following:

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

is going to be empty because the method receiver accepts a value(or more accurately creates a copy of the value you pass) so it will not modify your underlying value.

If I change the method to this:

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

the output will be "Dave".

Now what I don't understand is am I not supposed to call the method on a pointer? So when initialing my object I should do this:

dave := &Person{}

instead of this:

dave := Person{}

Also using go's reflect package I was trying to find out the value of:

&Person

and found it to be *Person, which is fine. When I print the value though I don't get a memory location compared to when I print the value of a pointer to an int:

a := 4
fmt.Println(&a)

I keep reading the docs, asking questions on so but the more I learn the more I wonder if I'm missing something simple since a lot of people don't think all this is confusing.

  • 写回答

1条回答 默认 最新

  • duanlujiaji10335 2018-03-13 09:05
    关注

    You're confusing calling a method on a pointer receiver with keeping a pointer to a value. That is, there is no inherent connection between calling SetName defined on a pointer receiver with the requirement to store a pointer to a memory holding a value of type Person vs storing Person directly.

    When you have the declaration

    var pers Person
    

    and then call

    pers.SetName("foo")
    

    the compiler takes the address of the memory block occupied by the variable pers—just like if you were doing that by hand through applying the & operator to pers, and passes that address to the SetName function, so the call ends up being done like

    (&pers).SetName("foo")
    

    or, to say it differently, the SetName's receiver will be &pers—that is, the address of pers.

    Now there's nothing preventing you from taking the address of pers and storing it elsewhere:

    var ptr *Person = &pers
    

    (you usually do not write such Java-style stuttering code but I'm doing this for extra clarity), and now you're able to call SetName right on the ptr value:

    ptr.SetName("bar")
    

    Now the compiler will directly use the value stored in the variable ptr to pass it as the method's receiver.

    The final bit of this "puzzle" is that when you use the special syntax provided by Go—applying the address-taking operator & directly to a literal,—the compiler does something like this:

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

    …and makes the __pers variable inaccessible by name (since it naturally has no name).

    As you can see, this is no different from getting the pointer to a Person value's memory by hand.


    You might want to consult this FAQ entry to gain more understanding of how the method sets on a type T and *T relate, and why the latter always includes the former but not vice-versa. Also read this.


    Update: to explain the difference in how fmt.Println formats and outputs the values of type *int and *Person.

    • The former is printed as the base-16 string representation of an address of a memory location containing that int value.
    • The latter is rendered using a string representation resembling Go's syntax for taking a pointer to a literal: &{}.

    This behaviour is as documented: fmt.Print and fmt.Println use the so-called default formatting rules for the arguments they are passed, and these rules depend on the types of the arguments.

    Let's first consult the documentation on fmt.Println (run go doc fmt.Println):

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

    Println formats using the default formats for its operands and writes to standard output. Spaces are always added between operands and a newline is appended. It returns the number of bytes written and any write error encountered.

    (Emphasis mine.)

    Now let's turn to the documentation of the fmt package itself (run go doc fmt):

    <…>

    Printing

    The verbs:

    General:

    • %v the value in a default format

      when printing structs, the plus flag (%+v) adds field names.

    <…>

    The default format for %v is:

    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
    

    For compound objects, the elements are printed using these rules, recursively, laid out like this:

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

    A *Person is a pointer to a struct type, so it's rendered as

    &{name}
    

    where name is the contents of the name field; say, if it was assigned the string "Dave", the output would be &{dave}.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 openwrt双栈NAT
  • ¥15 部分网页页面无法显示!
  • ¥15 怎样解决power bi 中设置管理聚合,详细信息表和详细信息列显示灰色,而不能选择相应的内容呢?
  • ¥15 QTOF MSE数据分析
  • ¥15 平板录音机录音问题解决
  • ¥15 请问维特智能的安卓APP在手机上存储传感器数据后,如何找到它的存储路径?
  • ¥15 (SQL语句|查询结果翻了4倍)
  • ¥15 Odoo17操作下面代码的模块时出现没有'读取'来访问
  • ¥50 .net core 并发调用接口问题
  • ¥15 网上各种方法试过了,pip还是无法使用