Golang中将结构体切片作为空接口传递给函数的方法

Golang中将结构体切片作为空接口传递给函数的方法 我有许多数据结构,例如:

type dbClusters struct {
  some struct members
}

type dbHostStates struct {
  some struct members
}

还有另一个结构体定义如下:

type tableInfo struct {
  tablename string
  datastructure []interface{}
}

在我的代码中,我这样使用这些结构体:

func myFunc() {
  var clusters []dbClusters
  var states []dbHostStates

  [some code to populate the structs]

  tables := tableInfo {
    tableInfo { tablename: "clusterstable", datastructure: clusters },
    tableInfo { tablename: "statestable", datastructure: states }
  }
}

这里的目的是,稍后在我的代码中,我将有一个单一的函数,它将以 []tableInfo 作为参数被调用,以处理表 tableInfo.tablename 中的任何数据,并使用 tableInfo.datastructure 中的数据结构。

我认为将这些信息放在一个 tableInfo 结构体中是正确的方式,并且使用空接口来映射实际的数据结构是“聪明”的。

我选择这种方式的原因是,我可能需要处理多达15个表,我不希望将几乎相同的代码重复15次。

我在这里遗漏或误解了什么?

尝试构建时,我得到以下错误:

[21:43:58|jfgratton@bergen:source]: ./build.sh 
# vmman3/db
db/db-import.go:47:54: cannot use hyps (variable of type []dbHypervisors) as type []interface{} in struct literal
db/db-import.go:48:55: cannot use sps (variable of type []dbStoragePools) as type []interface{} in struct literal
db/db-import.go:49:50: cannot use vms (variable of type []dbVmStates) as type []interface{} in struct literal

我理解它说的是什么,但不明白它为什么这么说。


更多关于Golang中将结构体切片作为空接口传递给函数的方法的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

我找到了一个与你回复相似的答案。这有点违背我原本想尝试的目的。尽管我热爱Go,但到目前为止(作为Go新手),我想我仍然需要摒弃一些面向对象编程的概念,并学习Go的方式。 从你提供的FAQ中(我怎么会错过这个??),我理解了这背后的原因。

这意味着,对于我需要处理的每个表,我将不得不编写一些近乎重复的代码。我本想构建某种TableInjectorFactory()函数,但是……

感谢你的回复!

更多关于Golang中将结构体切片作为空接口传递给函数的方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我对泛型的理解是,它是一种规避GO语言中缺乏函数重载功能的方法,我理解得对吗?

我打算在我的软件的下一个迭代版本中研究这个问题。这个项目是我的“学习”项目(一个将QEMU虚拟机管理器从Python3移植到GO的版本)。

到目前为止,这令人兴奋;GO语言带来了如此多的可能性……

我已经收藏了你的链接,肯定会尽快查看。我想尽快完成当前这个软件迭代版本,然后再进行优化。

你好!

正如这里所解释的:常见问题解答 - Go 编程语言,Go 语言不允许将特定类型的切片直接转换/赋值给 []interface{}

在将其赋值给数据结构之前,你需要先将其转换为 []interface{}

func myFunc() {
  var clusters []dbClusters
  var states []dbHostStates

  [some code to populate the structs]

  dbC := make([]interface{}, len(clusters))
  for i, v := range clusters {
    dbC[i] = v
  }

  dbS := make([]interface{}, len(states))
  for i, v := range states {
    dbS[i] = v
  }

  tables := tableInfo {
    tableInfo { tablename: "clusterstable", datastructure: dbC },
    tableInfo { tablename: "statestable", datastructure: dbS }
  }
}

您也可以探索泛型是否适合您的需求:

图片

教程:泛型入门 - Go 编程语言

https://gobyexample.com/generics

使用泛型消除代码重复的最简单方法是使用一个泛型函数,该函数将您的切片转换为 []interface{}。代码现在看起来会像这样:

// 泛型函数,用于将任何类型的切片转换为 []interface{}
func dataStructure[T any](s []T) []interface{} {
	ds := make([]interface{}, len(s))
	for i, v := range s {
		ds[i] = v
	}

	return ds
}

func myFunc() {
  var clusters []dbClusters
  var states []dbHostStates

  [用于填充结构体的一些代码]

  dbC := dataStructure[dbClusters](clusters)
  dbS := dataStructure[dbHostStates](states)

  tables := tableInfo {
    tableInfo { tablename: "clusterstable", datastructure: dbC },
    tableInfo { tablename: "statestable", datastructure: dbS }
  }
}

在Go语言中,[]interface{}[]具体类型 是不同的类型,不能直接赋值。你需要使用空接口切片来包装具体类型的切片。以下是解决方案:

type tableInfo struct {
    tablename     string
    datastructure []interface{}
}

func myFunc() {
    var clusters []dbClusters
    var states []dbHostStates

    // 填充数据...

    tables := []tableInfo{
        {
            tablename:     "clusterstable",
            datastructure: convertToInterfaceSlice(clusters),
        },
        {
            tablename:     "statestable",
            datastructure: convertToInterfaceSlice(states),
        },
    }
}

// 通用转换函数
func convertToInterfaceSlice(slice interface{}) []interface{} {
    s := reflect.ValueOf(slice)
    if s.Kind() != reflect.Slice {
        panic("convertToInterfaceSlice: given a non-slice type")
    }

    result := make([]interface{}, s.Len())
    for i := 0; i < s.Len(); i++ {
        result[i] = s.Index(i).Interface()
    }
    return result
}

或者使用泛型(Go 1.18+):

func convertToInterfaceSlice[T any](slice []T) []interface{} {
    result := make([]interface{}, len(slice))
    for i, v := range slice {
        result[i] = v
    }
    return result
}

在后续处理函数中,你需要使用类型断言来恢复具体类型:

func processTables(tables []tableInfo) {
    for _, table := range tables {
        switch table.tablename {
        case "clusterstable":
            if data, ok := table.datastructure.([]dbClusters); ok {
                // 处理 clusters 数据
            }
        case "statestable":
            if data, ok := table.datastructure.([]dbHostStates); ok {
                // 处理 states 数据
            }
        }
    }
}

或者使用更通用的处理方式:

func processTableData(table tableInfo) {
    // 根据表名决定如何处理
    switch table.tablename {
    case "clusterstable":
        for _, item := range table.datastructure {
            if cluster, ok := item.(dbClusters); ok {
                // 处理单个 cluster
            }
        }
    case "statestable":
        for _, item := range table.datastructure {
            if state, ok := item.(dbHostStates); ok {
                // 处理单个 state
            }
        }
    }
}

错误的原因在于Go的类型系统:[]interface{} 是一个切片,其元素类型是 interface{},而 []dbClusters 的元素类型是 dbClusters,两者内存布局不同,不能直接转换。

回到顶部