Golang中惯用元组的实现与应用

Golang中惯用元组的实现与应用 在 Go 语言中表示元组的惯用方式是什么?人们通常使用结构体,还是有更轻量级的方法?

10 回复

与其他大多数事情的做法相同:要么自己编写代码,要么寻找一些外部包。

更多关于Golang中惯用元组的实现与应用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


如果你能假设可以使用字面量创建数组或切片,而不是将其作为函数参数接收,那么你可以通过匿名结构体来解决这个问题。如果必须在函数或方法参数、返回值、字段或其他结构体中指定该结构体,或者在其他多种构造中使用,则它不能是匿名的。

有什么能比结构体更轻量呢?

元组本质上不过是一个通用的双字段结构体。

一旦 1.18 版本发布,你甚至可以自己定义它。

struct Tuple[A, B] {
   fst A
   snd B
}

或者至少是类似这样的东西……我还没有太多时间深入研究 Go 语言的泛型……

我正在准备一场筛选面试,其中一个过去使用过的问题(与语言无关)将元组作为输入。我希望能够准备好为这类问题实现一个解决方案。

此外,我只是出于好奇。我同意,在大多数语言中,每当我想使用元组时,结构体几乎总是能更好地工作。

对于这种情况,我倾向于使用匿名结构体类型的数组:

技术电话面试(LC 会议室问题):给定一个由元组(开始时间,结束时间)表示的预约列表,编写一个函数,如果所有预约都能由一名医生无重叠地处理则返回 True,否则返回 False。

我犹豫是否要提及这一点,因为有人可能会认为这是个好方法,但在Go语言中,与动态语言中的元组最接近的类型是 interface{} 的数组。数组是固定长度的,而 interface{} 可以容纳任何类型的值。实际使用它的麻烦之处应该能劝阻任何人将其作为元组的替代品。请改用结构体。

func main() {
    fmt.Println("hello world")
}

Dean_Davidson:

如果你想返回一个元组,直接返回多个值即可。如果你有其他用例,请告诉我!

一个典型的用例是,元组被用作一种临时结构,将值组合在一起并存储在容器中。在 Go 中,结构体在许多层面上是更好的方法。唯一的缺点是,你需要稍微花点力气来声明结构体类型,但即便如此,匿名结构体也可以减轻一些“负担”。你可能不知道的关于 Go 的 10 件事

有意思。所以你说的是一个通用的面试,但你在写代码并且选择的语言是 Go?我可能会快速创建一个结构体,并解释说在 Go 中确实没有一种惯用的方式来表示元组。或者,如果他们的示例使用的是相同类型的对象,你可以用一个可变参数函数来解决。加上泛型,你就可以在不同的类型上重用这个函数:

func doStuff[T any](input ...T) {
	for _, v := range input {
		fmt.Println(v)
	}
}

// 可行
doStuff("Hello", "world")
// 也可行
doStuff(1, 2, 3)
// 多种类型时不行
doStuff("Hello", 1)

你可以按照提到的方法,使用 interface{} 来实现更通用的功能:

func dontDoThis(input ...interface{}) {
	for _, v := range input {
		fmt.Println(v)
	}
}

// 可行,但代价是什么?
dontDoThis("Hello", 1)

……但通常不鼓励这样做,因为更具体的类型通常是首选。我的意思是——如果你对输入一无所知,这个函数能有多大用处?在那种情况下,你能对输入进行什么合理的操作呢?

关于这个话题,这里有一些很好的讨论。另外,@NobbZ,我认为你的例子并不完全是一个完整的元组。你的 Tuple[A, B] 例子是一个二元组,也称为对(pair),但一个元组可以有 n 个值。

回到最初的问题:我想更多地了解你的具体用例。很多年前,我和一个团队一起构建了一个持续多年的 WPF 项目。我发现我们经常在偷懒、不想对返回类型规定得太具体时使用元组。而这总是导致代码难以阅读,因为你必须根据顺序在脑子里记住哪个值代表什么。代码库几乎总是能通过使用某种带有描述性值的类/结构体(或者在 .NET 的情况下,投影到匿名类型)而得到更好的维护。你的用例是什么?

在 Go 的情况下,我发现元组的概念远没有那么有趣,因为 Go 内置了对多返回值的支持。例如,如果我想在像 C# 这样的语言中定义一个返回 Tuple<string, int, int> 的东西,我宁愿直接返回 (string, int, int)。不需要元组。

想要内联声明某些东西并希望声明多个值?让我们看看一个元组:

// c# 使用元组
var myTuple = new Tuple<string, int>("Jack", 78);
// 哎呀。你只能知道 item 1 是名字,item 2 是 ID...
Console.WriteLine("Name {0} ID {1}", myTuple.Item1, myTuple.Item2);

……对比直接声明多个值:

name, id := "Jack", 78
// 命名变量?我们真的生活在未来...
fmt.Println("Name:", name, ", ID:", id)

总结一下:如果你想要简单的内联变量存储,可以一次性声明多个变量。如果你想返回一个元组,直接返回多个值即可。如果你有其他用例,请告诉我!

在Go语言中,惯用的元组实现方式是使用结构体或返回多个值。结构体适合命名和复用,而多返回值则更轻量级,适用于临时组合数据。

1. 使用结构体(命名元组)

结构体为元组字段提供明确的命名和类型安全,适合复杂或需要复用的场景。

type Point struct {
    X, Y int
}

func getPoint() Point {
    return Point{X: 10, Y: 20}
}

func main() {
    p := getPoint()
    fmt.Printf("X: %d, Y: %d\n", p.X, p.Y) // 输出: X: 10, Y: 20
}

2. 多返回值(轻量级元组)

Go函数支持多返回值,这是最简洁的临时元组实现方式,无需额外类型定义。

func getCoordinates() (int, int) {
    return 30, 40
}

func main() {
    x, y := getCoordinates()
    fmt.Printf("X: %d, Y: %d\n", x, y) // 输出: X: 30, Y: 40
    
    // 使用匿名变量忽略部分返回值
    xOnly, _ := getCoordinates()
    fmt.Println("X only:", xOnly) // 输出: X only: 30
}

3. 切片或数组(类型相同元素)

当元组元素类型相同时,可以使用切片或数组,但会失去类型安全性。

func getPair() []int {
    return []int{50, 60}
}

func main() {
    pair := getPair()
    fmt.Printf("First: %d, Second: %d\n", pair[0], pair[1]) // 输出: First: 50, Second: 60
}

4. 使用any类型(动态类型)

Go 1.18+支持泛型前,可使用any(原interface{})实现动态类型元组,但需类型断言。

func getDynamicTuple() (any, any) {
    return "hello", 42
}

func main() {
    a, b := getDynamicTuple()
    str := a.(string)
    num := b.(int)
    fmt.Printf("String: %s, Int: %d\n", str, num) // 输出: String: hello, Int: 42
}

实际应用示例

// 数据库查询返回多值
func queryUser(id int) (string, int, error) {
    // 模拟查询
    if id > 0 {
        return "Alice", 30, nil
    }
    return "", 0, errors.New("user not found")
}

func main() {
    name, age, err := queryUser(1)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Printf("Name: %s, Age: %d\n", name, age) // 输出: Name: Alice, Age: 30
}

结构体提供更好的可读性和维护性,而多返回值在简单场景中更简洁。选择取决于是否需要命名字段、类型安全或复用需求。

回到顶部