method
方法
如果我们定义一个类型A,并给它关联一个方法,然后就可以通过这个类型A的变量来调用这个方法了,这种调用方式其实是“语法糖”实际上和下面这种方式是一样的。这里变量a就是所谓的方法接收者,它会作为方法Name的第一个参数传入。这一点我们可以通过右边的代码验证一下,Go中函数类型只和参数与返回值相关,所以这两个类型值相等就能够证明方法本质上就是普通的函数。而接收者就是隐含的第一个参数。
下面来看看方法调用的情况,man函数栈帧局部如图,传参值拷贝,函数内a.name指向新的字符串内容,这里的局部变量a是值接收者,通过它调用方法时,修改的是拷贝过去的参数。而不是局部变量a,要想修改局部变量a,要用指针接收者。
把上面方法Name的接收者改为指针类型,然后用pa来调用方法Name。pa.Name()会被转换成(*A).Name(pa)这样的函数调用。传参值拷贝,这里拷贝的是局部变量a的地址,修改的是局部变量a。这就是指针接收者调用方法的过程,同样要把接收者作为第一个参数传入。同样是参数值拷贝,但是指针接收者拷贝的是地址,因此实现了对局部变量a的修改。
这里既有值接收者的方法,又有指针接收者的方法,我们已经了解了两种接收者调用方法的基本过程,但是main函数中通过值调用指针接收者的方法,通过指针调用值接收者的方法。也能正常运行是什么意思??其实若没有涉及到接口的话,这些也是语法糖,编译阶段它会转换成这种形式。不过既然这种语法糖在编译期间发挥作用的,像编译期间不能拿到地址的字面量是不能借助语法糖来转换的,因此并不能通过编译。
方法表达式 & 方法变量
总结
最后来看一下把一个方法赋给一个变量是怎么回事,我们已经知道Go中函数作为变量,参数和返回值时,都是以Function Value的形式存在的,也知道闭包只是有捕获列表的Function Value而已。如果把一个类型的方法赋给变量f1,f1就是一个方法表达式。这实际上等价于f1:=GetName,所以f1本质上也是一个function value,也就是一个funcval结构体的指针,fn指向A.Getname的函数指令入口。 因为前面我们已经验证了这两个方法的等价性,所以通过f1执行方法时,需要传入A类型的变量a作为第一个参数,而f2:=a.GetName以这样的方式赋值,它被称为方法变量,理论上讲,方法变量也是一个FunctionValue,而且它会捕获方法接收者, 形成闭包,但是这里f2仅作为局部变量,它与a的生命周期是一致的,所以编译器会做出优化,把它转换成类型A的方法调用并传入a作为参数。
我们可以看一个方法变量作为返回值的例子,对比一下。这里f2与上个例子相同,它会被编译器做出优化,而下面的f3赋值为GetFunc函数的返回值,返回的是一个方法变量,这等价于这样一段代码。通过这一段代码我们可以清晰的看到闭包是如何形成的,所以这里的f3就是一个闭包对象,捕获了GetFunc函数的局部变量a。
因此从本质上来讲,方法表达式和方法变量都是function value