Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/go/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Pointers Go中独特功能的集合_Pointers_Go_Set - Fatal编程技术网

Pointers Go中独特功能的集合

Pointers Go中独特功能的集合,pointers,go,set,Pointers,Go,Set,我正在尝试在go中实现一组函数。上下文是一个事件服务器;我希望防止(或至少警告)为事件多次添加相同的处理程序 我已经读到地图作为集合使用的习惯用法,因为它易于检查成员资格: if _, ok := set[item]; ok { // don't add item } else { // do add item } 不过,我在函数中使用这个范例时遇到了一些问题。这是我的第一次尝试: // this is not the actual signature type EventRe

我正在尝试在go中实现一组函数。上下文是一个事件服务器;我希望防止(或至少警告)为事件多次添加相同的处理程序

我已经读到地图作为集合使用的习惯用法,因为它易于检查成员资格:

if _, ok := set[item]; ok {
    // don't add item
} else {
    // do add item
}
不过,我在函数中使用这个范例时遇到了一些问题。这是我的第一次尝试:

// this is not the actual signature
type EventResponse func(args interface{})

type EventResponseSet map[*EventResponse]struct{}

func (ers EventResponseSet) Add(r EventResponse) {
    if _, ok := ers[&r]; ok {
        // warn here
        return
    }
    ers[&r] = struct{}{}
}

func (ers EventResponseSet) Remove(r EventResponse) {
    // if key is not there, doesn't matter
    delete(ers, &r)
}
显然,这不起作用的原因是:函数不是Go中的引用类型,尽管有些人会告诉你它们是,尽管我们不需要它,因为语言规范说除了映射、切片和指针之外的所有东西都是按值传递的

尝试2:

func (ers EventResponseSet) Add(r *EventResponse) {
// ...
}
这有两个问题:

  • 任何EventResponse都必须像
    fn:=func(args接口{}){}
    那样声明,因为您不能以通常的方式声明函数

  • 你根本不能通过一个关闭

  • 使用包装器不是一个选项,因为传递给包装器的任何函数都将从包装器中获得一个新地址——没有函数可以通过地址唯一地识别,所有这些仔细的规划都是徒劳的

我不接受将函数定义为变量作为解决方案是愚蠢的吗?还有其他(好的)解决办法吗


说清楚一点,我承认有些情况我不能抓住(闭包),这没关系。我设想的用例是定义一组处理程序,并且相对安全,如果有意义的话,我不会意外地将一个处理程序添加到同一事件中两次。

哪些函数是相等的?语言规范中没有为函数类型定义可比性<代码>反射.值或多或少为您提供所需的行为

type EventResponseSet map[reflect.Value]struct{}
set := make(EventResponseSet)
if _, ok := set[reflect.ValueOf(item)]; ok {
    // don't add item
} else {
    // do add item
    set[reflect.ValueOf(item)] = struct{}{}
}
此断言将仅视为由赋值产生的相等项

//for example
item1 := fmt.Println
item2 := fmt.Println
item3 := item1
//would have all same reflect.Value

但我认为任何文件都不能保证这种行为

您可以使用Uvelichitel提供的
reflect.Value
,也可以使用获取的
string
函数地址或获取的
uintpttr
地址(答案中有更多内容),但我建议不要使用

由于语言规范不允许,也不允许,您无法保证在您的程序中某个时间工作的某些东西将始终工作,包括特定的运行,以及包括不同的(未来的)Go编译器。我不会用它

由于规范对此要求严格,这意味着编译器可以生成代码,例如在运行时更改函数的地址(例如,卸载未使用的函数,然后在需要时再次加载)。我目前不知道这种行为,但这并不意味着未来的Go编译器不会利用这种行为

如果存储函数地址(以任何格式),则该值不再算作保留函数值。如果其他人不再“拥有”函数值,生成的代码(和Go运行时)将“自由”修改/重新定位函数(从而更改其地址)——而不会违反规范和Go的类型安全。因此,您不能对编译器感到愤怒和责怪,而只能责怪您自己

如果要检查是否重用,可以使用接口值

假设您需要具有签名的函数:

func(p ParamType) RetType
创建一个接口:

type EventResponse interface {
    Do(p ParamType) RetType
}
例如,您可以有一个未报告的
struct
类型,并且指向它的指针可以实现您的
EventResponse
接口。生成一个导出函数以返回单个值,这样就不会创建新值

例如:

是否真的需要将实现隐藏在包中,并且只创建和“发布”单个实例?不幸的是,是的,因为您可以创建其他值,如
&myevtrep{}
,这些值可能是仍然具有相同
Do()
方法的不同指针,但接口包装器值可能不相等:

界面值具有可比性。如果两个接口值具有相同的动态类型和动态值,或者都具有值
nil
,则两个接口值相等

[……和……]

指针值是可比较的。如果两个指针值指向同一个变量或都有值nil,则两个指针值相等。指向不同变量的指针可能相等,也可能不相等

类型
*myevtrep
实现
EventResponse
,因此您可以注册它的值(唯一的值,可通过
Get()
访问)。您可以有一个类型为
map[EventResponse]bool
的映射,您可以在其中存储注册的处理程序、作为键的接口值和作为值的
true
。使用映射中不存在的键对映射进行索引将生成映射值类型的零值。因此,如果映射的值类型为
bool
,则使用不存在的键对其进行索引将导致
false
——告知它不在映射中。使用已注册的
EventResponse
(现有密钥)进行索引将生成存储值–
true
–告诉它在地图中,它已注册

您只需检查是否已注册:

type EventResponseSet map[*EventResponse]bool

func (ers EventResponseSet) Add(r EventResponse) {
    if ers[r] {
        // warn here
        return
    }
    ers[r] = true
}

关闭:为了避免重复使用,这似乎有点太麻烦了。我同意,但我不会这么做。但是如果您想…

如果键类型是
*EventResponse
,则无法捕获添加同一函数两次的情况,因为您的键是指向引用该函数的任何变量的指针。您可能需要查看
reflect
包,它可能提供了一种实现所需功能的方法。你需要使用函数以外的东西作为键。我知道函数是不可比较的。然而,这就是我在这里要做的。我的问题是函数不可寻址。你能为钥匙推荐一个好的选择吗?可以从赋中获得的东西
type EventResponseSet map[*EventResponse]bool

func (ers EventResponseSet) Add(r EventResponse) {
    if ers[r] {
        // warn here
        return
    }
    ers[r] = true
}