Golang中切片作为函数接收器的使用解析
Golang中切片作为函数接收器的使用解析 我在某个文件中定义了以下结构体:
type dbHypervisors struct {
HID uint8 `json:"hid" yaml:"hid"`
Hname string `json:"hname" yaml:"hname"`
Haddress string `json:"haddress" yaml:"address"`
}
之后,我这样使用它:
func getHypervisorData(conn *pgx.Conn) []dbHypervisors {
var hyps []dbHypervisors
rows, err := conn.Query(context.Background(), "SELECT hID, hName, hAddress from config.hypervisors")
if err != nil {
fmt.Println("Error: ", err)
}
defer rows.Close()
<SOME PROCESSING>
return hyps
}
现在,一旦处理完成,我希望将结构体序列化为JSON(这部分代码是有效的,所以问题不在这里)。我想通过一个函数类型接收器来实现,例如:
func (h dbHypervisors) serialize(filename string) {
//fmt.Printf("%T \n", jsonObject)
}
然后在我的主函数中,我会这样做:
hypervisors:= getHypervisorData(conn)
hypervisors.serialize(targetFile)
它报错说 serialize() 无法解析。我该如何正确使用函数接收器?我猜它期望的是 dbHypervisor 类型,而我希望在那里使用一个切片。
我希望 serialize 能截断文件并一次性写入所有内容。如果我必须通过 for … range 循环,那就意味着我必须在 serialize() 中改变逻辑来追加切片的每个元素,而我想避免这样做。
有什么想法吗?
更多关于Golang中切片作为函数接收器的使用解析的实战教程也可以访问 https://www.itying.com/category-94-b0.html
……我不明白你为什么要区分单虚拟化管理程序和多虚拟化管理程序?
interface{} 参数不会在意它是否是切片,对吧?
(无论如何,这在这里没有实际意义,因为所有数据库代码仅在存在多个虚拟化管理程序时使用;数据库正是让我的工具能够“感知虚拟化管理程序”的原因。)
好的,现在让我们真正地结束这个问题:
[11:03:12|jfgratton@bergen:source]: jq < hypervisors.json
[
{
"hid": 1,
"hname": "bergen",
"haddress": "10.3.1.1"
},
{
"hid": 2,
"hname": "oslo",
"haddress": "10.2.1.1"
}
]
成功了,这正是预期的结果。
您可以定义一个 dbHypervisorsSlice 类型并为其实现一个 serialize 函数:
func getHypervisorData(conn *pgx.Conn) dbHypervisorsSlice {
// ...
}
type dbHypervisorSlice []dbHypervisors
func (hyps dbHypervisorsSlice) serialize(filename string) {
// 编写一个包含循环的代码,以写入所有内部的 dbHypervisors 值。
// 这样,您就不必修改 dbHypervisors 自身的 serialize 函数。
}
是的,我可以按照您的建议去做,但问题是……我希望它尽可能高效,因为我需要在这个模块中导出三个小表,并且每个表都可以导出为 JSON、YAML 或 SQL 格式;这完全由最终用户决定,所以这里的诀窍是尽可能少写代码。
关于您的代码示例,使用 interface{} 是我最初的选择,但我读过一些关于使用 interface{} 的令人困惑的文章,它们甚至自相矛盾!我认为我对它的理解阻碍了我像您刚才那样使用它。
像 C++ 那样的函数重载本应是显而易见的选择,但您只能使用所选语言提供的功能,所以我现在就是这样做的。
……
我刚刚在 uDemy 上给自己买了 2-3 门课程,我受够了四处搜寻信息,这比实际编写代码浪费的时间还要多。我早就应该这么做了,我知道……
既然Go语言不支持函数重载,你建议我“重载”类型。
你无法重载一个函数,但你可以将 serialize 从 dbHypervisors 中“分离”出来,使其成为一个独立的函数:
func serialize(v interface{}, filename string) error {
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
data, err := json.Marshal(v)
if err != nil {
return err
}
_, err = f.Write(data)
return err
}
// ...
func main() {
// ...
oneHypervisor := dbHypervisors{ /* ... */ }
if err := serialize(oneHypervisor, "single-hypervisor.json"); err != nil {
log.Fatal(err)
}
twoHypervisors := []dbHypervisors{ /* ... */ }
if err := serialize(twoHypervisors, "multiple-hypervisors.json"); err != nil {
log.Fatal(err)
}
}
我理解,你是基于不完整的信息回复的,并据此给出了最佳答案。为了简洁起见,我已将展示的代码量减少到最低限度。你的C++类定义完全正确,如果是我也会采用这种方式,但同样地,我尽量简化了我的解释。
话虽如此,对我来说最重要的事情(学到的教训)是你提到的:type dbHypervisors != type []dbHypervisors,这在我看来有点反直觉,但我会处理的。
我将采用“断开连接的序列化”解决方案。这本来是我的首选方案,但我曾读到(现在在我的浏览器历史记录中找不到了)说这不是一个解决方案,所以我甚至没有尝试。我曾认为空接口是一个完美的解决方案(让我想起了C++中的*object对象),所以读到它不可行时,我有点失望。
再次感谢你花时间指导我这个刚刚开始接触Go的人。我正在构建一个libvirt客户端,用于管理跨多个虚拟机管理器的虚拟机及其资源。通过解决实际问题来学习一门新语言,这是一个非常好的方法。
… 我不明白你为什么要在单个管理程序和多个管理程序之间做区分?
interface{}参数不会在意它是不是切片,对吧?
在你回复我的第一个建议后,提供了更多信息,所以我修改了我的答案 😊,但我的两个答案本质上都可以归结为:
type dbHypervisors != type []dbHypervisors
在你最初的问题中,你提到你的 getHypervisorData 函数返回 []dbHypervisors。然后你表示希望能够这样写:
hypervisors:= getHypervisorData(conn)
hypervisors.serialize(targetFile)
我将其理解为:“我有一个类型 dbHypervisors,它上面有一个 serialize 方法。我还希望能够有一个 dbHypervisors 的切片,并为它们调用一个单一的 serialize 方法。” 我的第一个答案只展示了如何直接实现这一点。dbHypervisors 的切片是一个独立的类型(根据你提到的 C++,我会将 []dbHypervisors 描述为类似于 std::vector<dbHypervisors>),就像在 C++ 中你不能这样做:
std::vector<dbHypervisors> myHypervisors;
// ...
myHypervisors.serialize();
在 Go 中你也不能这样做。相反,你必须定义一个新类型并为其添加成员函数;因此,在 Go 中是:type dbHypervisorsSlice []dbHypervisors,我认为在 C++ 中这类似于:
class dbHypervisorsVector : public std::vector<T>
{
// ...
}
通过拥有一个独立的类型(在 Go 中是 dbHypervisorsSlice,在 C++ 中是 dbHypervisorsVector),你现在可以在其上定义一个 serialize 方法。
在我的第二个建议中,在你提到重载之后,让我想到你可以用一个单一的函数定义来解决问题,就像我建议的那样。在这个例子中,你是对的:不再有理由去区分 dbHypervisors 和 dbHypervisorsSlice,这就是为什么我放弃了 dbHypervisorsSlice 而使用了 []dbHypervisors(事后看来,我应该把这一点说得更明确)。
在Go语言中,接收器类型必须与方法定义的类型完全匹配。你定义的方法接收器是单个dbHypervisors结构体,但调用时使用的是[]dbHypervisors切片,因此会出现错误。
你有两种解决方案:
方案1:为切片类型定义方法(推荐)
创建自定义切片类型,然后为其定义方法:
// 定义自定义切片类型
type dbHypervisorsSlice []dbHypervisors
// 为切片类型定义serialize方法
func (hs dbHypervisorsSlice) serialize(filename string) error {
// 创建或截断文件
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
// 序列化整个切片为JSON
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
return encoder.Encode(hs)
}
// 修改getHypervisorData函数返回自定义切片类型
func getHypervisorData(conn *pgx.Conn) dbHypervisorsSlice {
var hyps dbHypervisorsSlice
rows, err := conn.Query(context.Background(), "SELECT hID, hName, hAddress from config.hypervisors")
if err != nil {
fmt.Println("Error: ", err)
return hyps
}
defer rows.Close()
// 处理rows并填充hyps
for rows.Next() {
var h dbHypervisors
if err := rows.Scan(&h.HID, &h.Hname, &h.Haddress); err != nil {
fmt.Println("Scan error: ", err)
continue
}
hyps = append(hyps, h)
}
return hyps
}
// 使用示例
func main() {
hypervisors := getHypervisorData(conn)
if err := hypervisors.serialize(targetFile); err != nil {
fmt.Println("Serialize error:", err)
}
}
方案2:使用指针接收器处理切片
如果你不想创建自定义类型,可以使用辅助函数:
// 为切片定义函数(不是方法)
func serializeHypervisors(hyps []dbHypervisors, filename string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
return encoder.Encode(hyps)
}
// 或者为切片指针定义方法
type dbHypervisorsList struct {
Items []dbHypervisors
}
func (hl *dbHypervisorsList) serialize(filename string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
return encoder.Encode(hl.Items)
}
方案3:使用接口包装
type Serializable interface {
Serialize(filename string) error
}
type HypervisorCollection struct {
Hypervisors []dbHypervisors
}
func (hc *HypervisorCollection) Serialize(filename string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
return encoder.Encode(hc.Hypervisors)
}
// 使用
func getHypervisorData(conn *pgx.Conn) Serializable {
collection := &HypervisorCollection{}
rows, err := conn.Query(context.Background(), "SELECT hID, hName, hAddress from config.hypervisors")
if err != nil {
fmt.Println("Error: ", err)
return collection
}
defer rows.Close()
for rows.Next() {
var h dbHypervisors
if err := rows.Scan(&h.HID, &h.Hname, &h.Haddress); err != nil {
fmt.Println("Scan error: ", err)
continue
}
collection.Hypervisors = append(collection.Hypervisors, h)
}
return collection
}
第一种方案是最简洁的,它通过类型别名dbHypervisorsSlice为切片添加了方法,同时保持了与普通切


