Golang新手提问:如何通过函数传递可变结构体

Golang新手提问:如何通过函数传递可变结构体 大家好,

我很久没有接触实际编程了,现在刚开始学习 Go。我想创建一个函数,传入一个整数,然后根据这个整数返回不同的结构体。我不确定这是否可行,也尝试过返回指针,但还是不行。这个函数还出现了一个奇怪的编译错误,我不明白为什么会失败。

这个函数的调用方式如下:

csvdata := csvinit(cmds.InPrecision)

函数 ‘csvinit’ 的内容在: https://play.golang.org/p/_bQjKvnR9-n

我只需要一点提示,告诉我发生了什么以及如何实现我的需求。用函数来实现这个功能是正确的做法吗?我应该使用方法或接口来实现吗?

谢谢!


更多关于Golang新手提问:如何通过函数传递可变结构体的实战教程也可以访问 https://www.itying.com/category-94-b0.html

15 回复

您想要的方式无法实现,需要为两个结构体实现一个公共接口,然后使用该接口类型作为返回类型。

更多关于Golang新手提问:如何通过函数传递可变结构体的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


虽然这是正确的,但这里的问题不在这里。结构体被读取了,只是标签没有被读取。

我正在尝试避免使用接口和方法,以保持简单性,因为会有几十种不同的结构体。

是否可以通过转换来实现?

也就是说,将空的 ‘csvdata’ 结构体转换为其中一个已填充的结构体?

你可以使用空接口并配合类型保护。但老实说,你真的不应该这样做。最终代码会变得难以维护。

好的,现在可以正常工作了: https://play.golang.org/p/FmwxItE1Bl4

但它会丢弃所有标签。所以现在又出现了另一个问题。😭😭

没有任何问题,但是反射无法查看私有字段。当你使用大写的字段标识符导出它们时,它就能正常工作了。

没有任何内容被丢弃,完整结果符合预期,这是该结构体的零值。

如果你为处理每种类型创建一个通用接口或完全独立的代码路径,你会遇到更少的麻烦,与语言的对抗也会大大减少。

Koguma:

你指的是哪个大写的字段标识符?

我指的是你结构体中的字段标识符。你使用了小写字母命名,因此包外部的代码无法看到这些字段。你需要将它们改为大写字母开头,才能使其可见并可访问。

我可能需要澄清一下。我确实让它工作了,但gocsv包丢失了结构体标签。是gocsv包有问题:https://github.com/gocarina/gocsv/issues/89

你指的是哪个大写的字段标识符?

是的,这很相似,但大写的字段名并没有起到作用,这个包存在一个bug。

不管怎样,我直接使用了该包中的CSVReader方法,放弃了使用它们的编组方法和结构体。虽然这样不太优雅,但实际上事情反而变得更简单了。

你的意思是当CSV函数使用结构体时,这些字段会被忽略吗?我没有使用Go读取CSV文件的经验,但如果它的工作方式与JSON类似,你必须使用大写的字段名,这样它们才能被导出,其他包才能读取它们。

感谢Nobbz!我遇到的部分问题似乎是我一直在使用的gocsv包中的反射机制有问题。它无法从接口反射回结构体。不过这确实是个很好的学习经历。

我会考虑重写代码,因为我要放弃使用gocsv包了… 😛

我发现了你的示例与包说明文档中示例的一个区别。在他们的函数调用中,他们传递了一个指针切片,而你只传入了一个结构体。这可能是问题所在。

clients := []*Client{}

if err := gocsv.UnmarshalFile(clientsFile, &clients); err != nil { // Load clients from file
	panic(err)
}

然后我阅读了代码,发现Unmarshal函数使用的readTo函数首先会查找数组或切片的基础类型。

是的,我想避免那样做。

我尝试过转换,但出现了错误。 似乎也不能直接将一个全局结构体的指针赋值给另一个。 类似这样的写法 *csvdata{} := *CSVdataDB1{} 是行不通的。

我原本以为转换会有效,类似这样:

package main

import "fmt"

type CSVdataDB1 struct {
    Name string
    Age  int
}

type csvdata struct {
    Name string
    Age  int
}

func main() {
    db1 := CSVdataDB1{Name: "John", Age: 30}
    var data csvdata
    data = csvdata(db1) // 尝试转换
    fmt.Println(data)
}

那么,使用变量的内容来初始化结构体呢?有没有办法做到类似这样:

package main

import "fmt"

type CSVdataDB1 struct {
    Name string
    Age  int
}

type csvdata struct {
    Name string
    Age  int
}

func main() {
    db1 := CSVdataDB1{Name: "John", Age: 30}
    // 尝试使用 db1 的内容来初始化 csvdata 结构体
    data := csvdata{Name: db1.Name, Age: db1.Age}
    fmt.Println(data)
}

难道没有简单的方法来实现吗?

在Go语言中,通过函数返回不同类型的结构体是可行的,但需要处理类型系统约束。你的代码问题在于返回类型声明为 *csvData,但实际返回了不同类型的结构体指针,导致类型不匹配错误。

以下是解决方案和示例代码:

方法一:使用接口(推荐)

定义接口,让不同结构体实现相同的方法:

type CSVData interface {
    GetHeaders() []string
    GetData() [][]string
}

type csvDataPrecise struct {
    Headers []string
    Data    [][]string
}

type csvDataSimple struct {
    Headers []string
    Data    [][]string
}

func (c *csvDataPrecise) GetHeaders() []string {
    return c.Headers
}

func (c *csvDataPrecise) GetData() [][]string {
    return c.Data
}

func (c *csvDataSimple) GetHeaders() []string {
    return c.Headers
}

func (c *csvDataSimple) GetData() [][]string {
    return c.Data
}

func csvinit(precision int) CSVData {
    if precision == 1 {
        return &csvDataPrecise{
            Headers: []string{"Time", "Value"},
            Data:    [][]string{},
        }
    }
    return &csvDataSimple{
        Headers: []string{"Timestamp", "Data"},
        Data:    [][]string{},
    }
}

使用方式:

csvdata := csvinit(cmds.InPrecision)
headers := csvdata.GetHeaders()
data := csvdata.GetData()

方法二:返回空接口配合类型断言

func csvinit(precision int) interface{} {
    if precision == 1 {
        return &csvDataPrecise{
            Headers: []string{"Time", "Value"},
            Data:    [][]string{},
        }
    }
    return &csvDataSimple{
        Headers: []string{"Timestamp", "Data"},
        Data:    [][]string{},
    }
}

// 使用时需要类型断言
csvdata := csvinit(cmds.InPrecision)
if precise, ok := csvdata.(*csvDataPrecise); ok {
    // 使用 precise
} else if simple, ok := csvdata.(*csvDataSimple); ok {
    // 使用 simple
}

方法三:使用相同结构体,通过配置区分

type csvData struct {
    Headers []string
    Data    [][]string
    IsPrecise bool
}

func csvinit(precision int) *csvData {
    if precision == 1 {
        return &csvData{
            Headers:  []string{"Time", "Value"},
            Data:     [][]string{},
            IsPrecise: true,
        }
    }
    return &csvData{
        Headers:  []string{"Timestamp", "Data"},
        Data:     [][]string{},
        IsPrecise: false,
    }
}

接口方法是实现这种需求的最佳实践,它提供了类型安全和清晰的抽象。函数是实现此功能的正确方式,不需要使用方法,除非这些结构体有相关的接收者方法。

回到顶部