function - 为什么 Go 在将函数指针作为值传递时不报告编译错误?
问题描述
我想如果我尝试将一个指针传递给一个函数,那么这个函数声明也应该接收一个指针?不确定,我试过这个:
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”内部的副本。
那么我在哪一点上错了?
解决方案
请参阅colminator 的答案,但是,对于直接 C 代码的一个相当不完美的类比,想象一下:
var x interface{ ... } // fill in the `...` part with functions
——或者在这种情况下,声明i I
makei
具有你定义的接口类型——就像声明一个struct
具有两个成员的 C,一个用于保存类型,一个用于保存该类型的值:
struct I {
struct type_info *type;
union {
void *p;
int i;
double d;
// add more types if/as needed here
} u;
};
struct I i;
编译器在您传递到i.type
时填充插槽,并填充以指向对象。1&s
i
i.u.p
s
当您调用i.Set(10)
时,Go 编译器会将其转换为等价于:
(*__lookup_func(i, "Set"))(i.u.p)
where__lookup_func
发现实际func (s *S) Set(age int)
和过多的魔法发现它应该将指向s
(from 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
请注意,当且仅当两个槽(和) 都为零时,interface
变量才比较等于。一直绊倒人们的是,如果你从某个非接口类型初始化一个值,它的槽不再是 nil,并且测试:nil
i.type
i.u
interface
type
if i == nil { // handle the case ...
不起作用,即使值槽(i.u.p
在我们这里的类比中)是 nil
.
1我将其显示为几种 C 类型的联合,但不包括struct
类型。事实上,一个interface
值的第二个槽的大小并不是编译器做出的任何承诺,尽管在当前的编译器中,它与任何其他指针一样只有 8 个字节。但是,如果您拥有的任何值类型对于实际的底层实现来说太大了,编译器会插入一个分配:该值进入一些额外的内存,并且联合的指针字段被设置为指向该值。
编译器在编译时检查您填充到某个接口的实际值的类型是否适合该接口。一个接口类型有一个它必须支持的函数列表。如果底层类型具有这些函数,则赋值是可以的(并且编译器知道要构建脚注 2 中提到的适当的类似 vtable 的数据)。如果底层类型缺少某些函数,则会出现编译时错误。因此,您绝对可以保证以后对接口变量的函数查找总是会成功。
2查找比此处隐含的字符串查找更快,因为Set
它具有编译器在编译时分配给该特定接口类型的整数代码值,并且内部struct type_info
有各种快速查找表,有点类似于 C++ vtables,以帮助它也是。
在大多数情况下,“过多的魔法”被大大减少为只是“将正确的参数放在正确的参数寄存器或堆栈位置”:复制被调用者从未读取的额外字节是无害的。但是,如果整数与浮点需要不同的参数寄存器,那就有点棘手了,而且我不确定当前的 Go 编译器实际上在这里做了什么。
3在v, ok := i.(int)
表格中,如果类型槽不持有int
,v
则设置为零,并ok
设置为false
。无论实际类型如何,这都成立:所有类型都具有默认的零值,并v
成为您提供的类型的零值。
推荐阅读
- python - 使用 Python,如何检查输入是否与定义的字符不同?
- spring-boot - spring boot 声明问题
- r - 具有较大值的散点图出现在顶部
- hadoop - 如何使配置单元查询中的 max 函数忽略 _HIVE_DEFAULT_PARTITION__
- javascript - 出于某种原因,双重 redux 调度
- delay - 如何使用延迟帧补丁(或任何其他)从相机纹理中选择哪一帧?
- node.js - nodejs 等效于 https curl -k 请求
- angular - 为什么一个组件在一个守卫之后不执行?
- sql - 如何将 Presto SQL (AWS Athena) 中的时间戳转换为日期,“2021-05-03T00: 00: 00.000Z” 格式?
- bash - 如何从基本文件名中获取最后一个字符?