gpt4 book ai didi

go - 在 Go 中是否可以迭代自定义类型?

转载 作者:IT老高 更新时间:2023-10-28 13:00:34 24 4
gpt4 key购买 nike

我有一个自定义类型,它内部有一段数据。

是否有可能通过实现范围运算符需要的一些函数或接口(interface)来迭代(使用范围)我的自定义类型?

最佳答案

最简洁的答案是不。

长答案仍然是否定的,但有可能以某种有效的方式破解它。但需要明确的是,这肯定是一次黑客攻击。

有几种方法可以做到,但它们之间的共同主题是您希望以某种方式将数据转换为 Go 能够覆盖的类型。

方法一: slice

由于您提到您在内部有一个 slice ,因此这对于您的用例来说可能是最简单的。这个想法很简单:你的类型应该有一个 Iterate()方法(或类似),其返回值是适当类型的 slice 。调用时,将创建一个新 slice ,其中包含数据结构的所有元素,无论您希望它们以何种顺序进行迭代。因此,例如:

func (m *MyType) Iterate() []MyElementType { ... }

mm := NewMyType()
for i, v := range mm.Iterate() {
...
}

这里有几个问题。首先,分配 - 除非您想公开对内部数据的引用(通常,您可能不会这样做),否则您必须创建一个新 slice 并复制所有元素。从大 O 的角度来看,这并没有那么糟糕(无论如何,您都在做线性量的工作来迭代所有内容),但出于实际目的,这可能很重要。

此外,这不处理对变异数据的迭代。大多数时候这可能不是问题,但如果您真的想支持并发更新和某些类型的迭代语义,您可能会关心。

方法二: channel

channel 也是 Go 中可以覆盖的东西。我们的想法是让您的 Iterate()方法生成一个 goroutine,它将迭代数据结构中的元素,并将它们写入 channel 。然后,当迭代完成时,可以关闭 channel ,这将导致循环完成。例如:
func (m *MyType) Iterate() <-chan MyElementType {
c := make(chan MyElementType)
go func() {
for _, v := range m.elements {
c <- v
}
close(c)
}()
return c
}

mm := NewMyType()
for v := range mm.Iterate() {
...
}

与 slice 方法相比,此方法有两个优点:首先,您不必分配线性数量的内存(尽管出于性能原因,您可能希望 channel 有一点缓冲区),其次,您如果你喜欢这种事情,可以让你的迭代器很好地处理并发更新。

这种方法的最大缺点是,如果您不小心,可能会泄漏 goroutine。解决这个问题的唯一方法是让你的 channel 有一个足够深的缓冲区来容纳你数据结构中的所有元素,这样 goroutine 可以填充它,然后即使没有从 channel 读取元素也可以返回(然后 channel 可以稍后被垃圾收集)。这里的问题是,a) 你现在回到线性分配,b) 你必须预先知道你要写多少元素,这会阻止整个并发更新的事情.

这个故事的寓意是 channel 很适合迭代,但你可能不想实际使用它们。

方法 3:内部迭代器

归功于 hobbsgetting to this before me ,但我会在这里介绍它的完整性(因为我想多说一点)。

这里的想法是创建一个迭代器对象(或者让你的对象一次只支持一个迭代器,并直接对其进行迭代),就像你在更直接支持它的语言中一样。那么,你要做的是拨打 Next()方法,a) 将迭代器推进到下一个元素,b) 返回一个 bool 值,指示是否还有任何东西。那么你需要一个单独的 Get()方法来实际获取当前元素的值。这个的用法实际上并不使用 range关键字,但它看起来很自然:
mm := MyNewType()
for mm.Next() {
v := mm.Get()
...
}

与前两种技术相比,这种技术有一些优点。首先,它不涉及预先分配内存。其次,它非常自然地支持错误。虽然它不是真正的迭代器,但这正是 bufio.Scanner 做。基本上这个想法是有一个 Error()您在迭代完成后调用的方法,以查看迭代是因为完成还是因为在中途遇到错误而终止。对于纯粹的内存数据结构,这可能无关紧要,但对于涉及 IO 的数据结构(例如,遍历文件系统树,迭代数据库查询结果等),这真的很好。因此,要完成上面的代码片段:
mm := MyNewType()
for mm.Next() {
v := mm.Get()
...
}
if err := mm.Error(); err != nil {
...
}

结论

Go 不支持覆盖任意数据结构——或自定义迭代器——但你可以破解它。如果您必须在生产代码中执行此操作,则第三种方法是 100% 可行的方法,因为它既是最干净的又是最少的 hack(毕竟,标准库包含此模式)。

关于go - 在 Go 中是否可以迭代自定义类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35810674/

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