将结构指针传递给采用接口指针的函数

Passing Struct Pointers to Functions That Take Interface Pointers

提问人:Grant 提问时间:9/4/2022 更新时间:9/5/2022 访问量:335

问:

我正在尝试理解将接口指针作为参数的函数的问题,但仍然不能完全理解这个问题,所以如果这是一个重复的问题,请道歉。虽然我理解人们普遍认为通常不需要指向接口的指针,但我觉得我实际上处于实现它们会很有用的情况。不过,如果不是这种情况,请随时告诉我

我计划从多个安全交易所获取 OHLC 数据。我目前正在为加密货币交易所编写机器人,但也计划为外汇和证券交易所/经纪商编写机器人。目标是让每个交换的 OHLC 响应写入结构体。OHLC的响应在交换之间通常不相同,因此这可能会产生一个问题,我希望通过接口来解决OHLCVals

每个机器人都有自己的结构(这是一个示例),用于实现接口的方法。此结构包含有关 OHLC 订阅的一些“元数据”,这些“元数据”通常是特定于 Exchange 的。该列表实际上将每个蜡烛的 OHLC 数据保存在单独的节点中,每个节点的字段也是一个接口。由于每个交易所都有自己的结构,我相信我可以解决结构中的字段作为接口的大多数问题。因为我需要在新数据进入时更新地图中的列表,所以我认为我需要使用指针地图才能进行这些更新。OHLCResponseHolderKrakenOHLCResponseHolderOhlcResponseHolderDataOHLCResponseHolderValsOHLCValsOhlcResponseHolder

我相信在这种情况下,使用接口指针可能是最好的解决方案,因为我唯一能想到的其他事情就是使用泛型。如果我没记错的话,泛型不能与地图一起使用。即使它们能够与地图一起使用,它也可能变得非常乏味,因为我计划支持许多交换(希望是 10-20 个),这将导致具有许多可接受的数据类型和许多非边缘情况的通用函数来测试

由于我的函数用例将指向接口的指针排除在外,因此我在将结构指针传递给采用接口指针的函数时遇到了一些麻烦。以下是我目前编码的接口和结构:

type Candle interface {
    GetCandle() Candle
    GetStartTime() UnixTime
    GetEndTime() UnixTime
    GetHigh() decimal.Decimal
    SetHigh(num decimal.Decimal)
    GetLow() decimal.Decimal
    SetLow(num decimal.Decimal)
    GetClose() decimal.Decimal
    SetClose(num decimal.Decimal)
    GetVWAP() decimal.Decimal
    SetVWAP(num decimal.Decimal)
    GetVolume() decimal.Decimal
    SetVolume(num decimal.Decimal)
    GetCount() int
    SetCount(num int)
}

type Node struct {
    Data Candle
    Next *Node
}

type List interface {
    GetList() List
    AddToEnd(n *Node)
    Print(locker *sync.RWMutex)
    IsEmpty() bool
    GetLast() *Node
}

type list struct {
    Head   *Node
    Last   *Node
    Length uint
}

func NewList(head *Node, last *Node, length uint) *list {
    return &list{Head: head, Last: last, Length: length}
}

func (l *list) GetList() List {
    return l
}

func (l *list) AddToEnd(n *Node) {
    if l.Head == nil {
        l.Head = n
        l.Last = n
        l.Length++
        return
    }
    tmp := l.Last
    tmp.Next = n
    l.Last = n
    l.Length++
}

func (l list) Print(locker *sync.RWMutex) {
    locker.RLock()
    tmp := l.Head
    for tmp != nil {
        fmt.Println(string("\033[34m"), tmp.Data, string("\033[0m"))
        tmp = tmp.Next
    }
    locker.RUnlock()
    fmt.Println()
}

func (l list) IsEmpty() bool {
    return l.Length == 0
}

func (l *list) GetLast() *Node {
    return l.Last
}

type OhlcResponseHolder interface {
    GetChannelID() int
    GetList() *List
    GetInterval() int64
}

type KrakenOHLCResponseHolder struct {
    ChannelID   int
    Interval    int64
    ChannelName string
    Pair        string
    List        shared_types.List
}

func (o KrakenOHLCResponseHolder) GetChannelID() int {
    return o.ChannelID
}

func (o *KrakenOHLCResponseHolder) GetList() *shared_types.List {
    return &o.List
}

func (o KrakenOHLCResponseHolder) GetInterval() int64 {
    return o.Interval
}

type OHLCValHolder interface {
    Set(key int, data *OhlcResponseHolder)
    RLock()
    RUnlock()
    Lock()
    Unlock()
    GetVals() map[int]*OhlcResponseHolder
    GetMutex() *sync.RWMutex
}

type OHLCVals struct {
    Vals  map[int]*shared_types.OhlcResponseHolder
    Mutex sync.RWMutex
}

func (ohlcVals *OHLCVals) Set(key int, data *shared_types.OhlcResponseHolder) {
    if ohlcVals.Vals == nil {
        ohlcVals.Vals = make(map[int]*shared_types.OhlcResponseHolder)
    }
    ohlcVals.Vals[key] = data
}

func (ohlcVals *OHLCVals) RLock() {
    ohlcVals.Mutex.RLock()
}

func (ohlcVals *OHLCVals) RUnlock() {
    ohlcVals.Mutex.RUnlock()
}

func (ohlcVals *OHLCVals) Lock() {
    ohlcVals.Mutex.Lock()
}

func (ohlcVals *OHLCVals) Unlock() {
    ohlcVals.Mutex.Unlock()
}

func (ohlcVals *OHLCVals) GetVals() map[int]*shared_types.OhlcResponseHolder {
    return ohlcVals.Vals
}

func (ohlcVals *OHLCVals) GetMutex() *sync.RWMutex {
    return &ohlcVals.Mutex
}

这是使用代码的上下文:

func HandleOHLCResponse(v *types.OHLCResponse, ohlcMap *shared_types.OHLCValHolder) {
    (*ohlcMap).RLock()
    if subID, found := (*ohlcMap).GetVals()[v.ChannelID]; found { // if channelID already exists in the map, then...
        // stuff
        fmt.Println(2+2)    
    } else { // if the channel id cannot be found in the map
        interval, _ := strconv.ParseInt(v.ChannelName[len(v.ChannelName)-1:], 10, 64)
        v.OHLCArray.StartTime.Time = v.OHLCArray.EndTime.Add(-time.Minute * time.Duration(interval))
        node := shared_types.Node{Data: &v.OHLCArray, Next: nil}
        tmp := types.KrakenOHLCResponseHolder{ChannelID: v.ChannelID, ChannelName: v.ChannelName, Pair: v.Pair, Interval: interval, List: shared_types.NewList(&node, &node, 1)}
        (*ohlcMap).RUnlock()

        (*ohlcMap).Lock()
        (*ohlcMap).Set(tmp.ChannelID, &tmp)
        (*ohlcMap).Unlock()
    }
}

这是我作为参数传递给时遇到的错误:cannot use &tmp(类型为*“bruit/bruit/clients/kraken/types”的值。KrakenOHLCResponseHolder)作为*shared_types。参数中的 OhlcResponseHolder 值 (*ohlcMap)。设置: *“bruit/bruit/clients/kraken/types”。KrakenOHLCResponseHolder 不实现 *shared_types。OhlcResponseHolder(类型 *shared_types。OhlcResponseHolder 是指向接口的指针,而不是接口)&tmp(*ohlcMap).Set(tmp.ChannelID, &tmp)

将不胜感激

Go 指针 结构 接口 按引用传递

评论

1赞 mkopriva 9/4/2022
但是,解决即时错误,您可以简单地先转换为接口类型并将其结果存储在新变量中,然后将该新变量的地址作为参数传递给方法。如果你想避免额外的变量,你可以直接从具体类型的结构文字进行转换,即tempSettmp := shared_types.OhlcResponseHolder(types.KrakenOHLCResponseHolder{ ... })
1赞 mkopriva 9/4/2022
指向接口的指针有一个且只有一个用例。调用方变量的更新。我知道它们没有其他用途。
1赞 mkopriva 9/4/2022
通过快速扫描代码,代码及其元素都不需要成为指针。只有实现这些接口的动态类型(如果它们需要能够通过接口方法修改自身)才需要是指针。请记住,在后台,接口值已经具有指向基础数据的指针。如果您仍然认为您确实需要指向接口的指针,您能否尝试通过提供两个并排简化的代码示例来证明这一点,一个带有指向接口的指针,另一个带有非指针接口,第二个中断?ohlcMap
2赞 Volker 9/4/2022
不要使用指向接口的指针,非常简单。
1赞 Grant 9/28/2022
@mkopriva对迟到的回复表示歉意。生活有时会发生,哈哈。我确实有机会浏览代码并将所有内容从指针接口更改为仅接口,并且我确实具有与以前相同的功能。在这种情况下,您不需要指针接口是 100% 正确的。感谢您花时间解释一切!事实证明这是一个愚蠢的问题,但我们来这里是为了学习

答:

0赞 Pak Uula 9/5/2022 #1

类型是指向接口类型的指针。*types.OhlcResponseHolder

不能将值作为第二个参数传递给 类型:&tmpSet*types.OhlcResponseHolder

  • 的基础类型&tmpKrakenOHLCResponseHolder
  • 的基础类型是*types.OhlcResponseHoldertypes.OhlcResponseHolder
  • 这些类型不完全相同(命名类型仅与它们本身相同)
  • 因此不能分配给&tmp*types.OhlcResponseHolder
  • 因此不能转换为(参见第一条规则:x 可分配给 T&tmp*types.OhlcResponseHolder)

如果您使用中间变量,您仍然可以使用:tmp

    var h_tmp types.OhlcResponseHolder = tmp
    (*ohlcMap).Set(tmp.ChannelID, &h_tmp)

这将起作用,因为 的底层类型是 并且它与函数的第二个参数的类型相同。&h_tmptypes.OhlcResponseHolderSet

下面是一个简化的示例:https://go.dev/play/p/zCAe56jg_re

这个 recipie 的问题是它没有实现 - 该方法是为指针接收器定义的。KrakenOHLCResponseHolderOhlcResponseHolderGetList

评论

0赞 Grant 9/5/2022
谢谢。因此,如果我理解正确,问题是指针方法不能在指针接口上使用?我想我很难理解为什么这是一个问题,因为如果您只是取消引用接口,似乎仍然可以使用指针接收器。无论出于何种原因,我都很难将我的大脑包裹在这个概念上
0赞 Pak Uula 9/5/2022
@Grant看起来你像在 C 中一样对待指针 - 以保持操作中的副作用。在围棋中还有另一种范式。这些值按值传递,它们在内部保留指针以在副本之间共享状态。习惯后,生活变得轻松多了。无需担心泄漏状态和不包含重复项。请参阅我的回答中的示例 - 它使用 int 指针来保持状态,并且值按值传递。