Function 为什么不去';将指向函数的指针作为值传递时是否报告编译错误?

Function 为什么不去';将指向函数的指针作为值传递时是否报告编译错误?,function,pointers,go,parameters,Function,Pointers,Go,Parameters,我想如果我试图传递一个指向函数的指针,那么这个函数声明也应该接收一个指针?不确定,我试过这个: package main import ( "fmt" ) type I interface { Get() int Set(int) } type S struct { Age int } func (s S) Get() int { return s.Age } func (s *S) Set(age int) { s.Age = age } f

我想如果我试图传递一个指向函数的指针,那么这个函数声明也应该接收一个指针?不确定,我试过这个:

package main

import (
    "fmt"
)
type I interface {
    Get() int
    Set(int)
}

type S struct {
    Age int
}
func (s S) Get() int {
    return s.Age
}
func (s *S) Set(age int) {
    s.Age = age
}
func f(i I) {
    i.Set(10)
    fmt.Println(i.Get())
}
func main() {
    s := S{}
    f(&s) //4
    fmt.Println(s.Get())
}
它打印

10
10
我们看到f的函数是

func f(i I)
我不确定这是否是一个“按值传递”声明,如果是按值传递,那么“I”不应该在函数“f”右侧之外更改,它是“f”内部的副本

那么,我错在哪一点了?

f&s
正在按值传递
s
的指针地址,就像任何其他go函数调用一样。函数接受接口参数这一事实不会改变这一事实

现在,关于接口的工作方式:接口值包含两项:值和基础类型。本例中的值是指向结构的指针。该类型验证
s
是否满足接口-因为它实现了Get/Set函数签名

由于方法的指针接收器可以更改接收器的数据字段,因此
&s
可以通过
设置
方法进行更改。通过扩展,调用调用Set的
f(&s)
,也会改变结构
s
的状态

另外,这种行为对于大多数go标准库来说是至关重要的。许多软件包,例如
http
依赖于
io.Reader
io.Writer
接口。接受实现这些接口的值的函数和方法依赖于底层的具体类型改变状态、读取网络端口、刷新缓存等来工作,同时不会给调用者带来这些内部副作用。

请参见,但是,对于直接C代码的一个相当不完善的类比,请想象:

var x interface{ ... } // fill in the `...` part with functions
-或者在这种情况下,声明
i
以使
i
具有您定义的接口类型,就像声明一个具有两个成员的C
struct
,一个用于保存类型,另一个用于保存该类型的值:

struct I {
    struct type_info *type;
    union {
        void *p;
        int i;
        double d;
        // add more types if/as needed here
    } u;
};
struct I i;
当您将
&s
传递到
i
时,编译器将填充
i.type
插槽,并填充
i.u.p
以指向对象
s
。1

当您调用
i.Set(10)
时,Go编译器将其转换为等效的:

(*__lookup_func(i, "Set"))(i.u.p)
其中
\uuuu lookup\u func
查找实际的
func(s*s)集合(age int)
,大量的魔法发现它应该将指向
s
(从
i.u.p
)的指针传递给该setter函数。2

某些接口类型的变量有这两个槽——类型部分和保存当前值的类并集部分——这一事实才是真正的秘密。您可以使用类型断言:

v, ok := i.(int)
或类型开关:

switch v := i.(type) {
case int: // code where `v` is `var v int`
case float64: // code where `v` is `var v float64` ...
// add more cases as desired
}
检查类型槽,同时将值槽复制到新变量
v
。3

注意,
接口
变量比较等于
nil
当且仅当两个插槽(
i.type
i.u
)均为nil时。一直让人困惑的是,如果您从某个非接口类型初始化
接口
值,其
类型
插槽不再为零,测试:

if i == nil { // handle the case ...
即使值槽(
i.u.p
在我们的类比中)是
nil
,也不起作用


1我将其显示为几个C类型的并集,但不包括
struct
类型。事实上,
接口
值的第二个插槽的大小并不是编译器所承诺的,尽管在当前的编译器中,它与任何其他指针一样只有8个字节。但是,如果您拥有的任何值类型对于实际的底层实现来说都太大,编译器会插入一个分配:该值进入一些额外的内存,并且union的指针字段被设置为指向该值

编译器会在编译时检查填充到某个接口中的实际值的类型是否适合该接口。接口类型具有其必须支持的函数列表。如果基础类型具有这些函数,则赋值是正常的(并且编译器知道构建脚注2中提到的适当的类似vtable的数据)。如果基础类型缺少某些函数,则会出现编译时错误。因此,您肯定可以保证以后对接口变量的函数查找将始终成功

2查找比这里暗示的字符串查找快,因为<代码> SET/COD>具有编译代码分配给编译器的整数代码值,而内部的<代码>结构类型Type信息> /Cord>有各种快速查找表,有点类似于C++ VTABLE,帮助它。 在大多数情况下,“过多的魔力”被大大减少,只需“将正确的参数放在正确的参数寄存器或堆栈位置”:复制被调用方从不读取的额外字节是无害的。但是,如果整数和浮点需要不同的参数寄存器,这就有点棘手了,我不确定当前的Go编译器在这里到底做了什么

3在
v,ok:=i.(int)
表单中,如果类型槽不容纳
int
v
设置为零,
ok
设置为
false
。不管实际的类型是什么:所有类型都有一个默认的零值,
v
将成为您给定类型的零值