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 高价求中通快递查询接口
  • ¥15 解决一个加好友限制问题 或者有好的方案
  • ¥15 关于#java#的问题,请各位专家解答!
  • ¥15 急matlab编程仿真二阶震荡系统
  • ¥20 TEC-9的数据通路实验
  • ¥15 ue5 .3之前好好的现在只要是激活关卡就会崩溃
  • ¥50 MATLAB实现圆柱体容器内球形颗粒堆积
  • ¥15 python如何将动态的多个子列表,拼接后进行集合的交集
  • ¥20 vitis-ai量化基于pytorch框架下的yolov5模型
  • ¥15 如何实现H5在QQ平台上的二次分享卡片效果?