gpt4 book ai didi

function - 为什么 Go 在将函数指针作为值传递时不报告编译错误?

转载 作者:行者123 更新时间:2023-12-01 22:42:52 24 4
gpt4 key购买 nike

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

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's answer ,但是,对于直接 C 代码的一个相当不完美的类比,想象一下:

var x interface{ ... } // fill in the `...` part with functions

— 或者在这种情况下,声明 i I制作 i有你定义的接口(interface)类型——就像声明一个 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;

编译器填写 i.type当你通过时插槽 &si , 并填写 i.u.p指向对象 s .1

当您调用 i.Set(10) ,Go 编译器将其转换为等价于:
(*__lookup_func(i, "Set"))(i.u.p)

在哪里 __lookup_func找到实际的 func (s *S) Set(age int)并且过多的魔法发现它应该将指针传递给 s (从 i.u.p )到那个 setter function.2

某些接口(interface)类型的变量具有这两个槽——“类型”部分和保存当前值的类联合部分——是这里真正的 secret 。您可以使用类型断言:
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当且仅当 两个插槽( i.typei.u )为零。总是让人绊倒的是,如果你初始化一个 interface来自一些非接口(interface)类型的值,它的 type slot 不再为零,并且测试:
if i == nil { // handle the case ...

不起作用,即使值槽( i.u.p 在我们的类比中)是 nil .

1我将其显示为几种 C 类型的联合,但不包括 struct类型。实际上, interface 的第二个插槽的大小value 不是编译器做出任何 promise 的东西,尽管在当前的编译器中,它就像任何其他指针一样只有 8 个字节。但是,如果您拥有的任何值类型对于实际的底层实现来说太大了,编译器会插入一个分配:该值进入一些额外的内存,并且联合的指针字段被设置为指向该值。

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

2查找比此处隐含的字符串查找更快 Set具有编译器在编译时分配给该特定接口(interface)类型的整数代码值,以及内部 struct type_info stuff 有各种快速查找表,有点类似于 C++ vtables,也可以帮助它。

在大多数情况下,“过多的魔法”被大大减少为只是“将正确的参数放在正确的参数寄存器或堆栈位置”:复制被调用者从未读取的额外字节是无害的。但是,如果整数与浮点需要不同的参数寄存器,那就有点棘手了,而且我不确定当前的 Go 编译器实际上在这里做了什么。

3在 v, ok := i.(int)表单,如果类型槽不包含 int , v设置为零, ok设置为 false .无论实际类型如何,这都成立:所有类型都有默认零值, v变成你给定类型的零值。

关于function - 为什么 Go 在将函数指针作为值传递时不报告编译错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58687462/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com