如何编写接受任何数字类型的通用函数?

Lav*_*iet 5 generics go

来自 JavaScript 和 TypeScript,我想尝试一下 Go 并制作一个简单的计算器。既然 int 和 float 之间存在差异,那么编写接受任意数字的函数的首选方法是什么?

例如:

package main

func add(a float64, b float64) float64 {
  return a + b;
}

func main() {
  a := 1;
  b := 2;
  fmt.Println(add(1, 2)); // 3
  fmt.Println(add(a, b)); // Cannot use a (type int) as type float64 in argument to add
  fmt.Println(add(1.5, 3.2)); // 4.7
  fmt.Println(add(2.5, 2)); // 4.5
}
Run Code Online (Sandbox Code Playgroud)

我是否需要将所有内容都转换为浮点型(因为它“覆盖”了 int 范围),还是为每种类型创建一个单独的函数,例如addInt(a int, b int) intand addFloat(a float64, b float64) float64?或者可能有一种更优雅的方式吗?

bla*_*een 24

Go 1.18及以上版本

随着 Go 1.18 中类型参数的引入,这更容易实现。

您可以定义参数化函数T并使用接口约束来限制T为数字类型。

func add[T Number](a, b T) T {
    return a + b
}
Run Code Online (Sandbox Code Playgroud)

可以使用包Number定义约束(仍处于实验阶段):golang.org/x/exp/constraints

import "golang.org/x/exp/constraints"

type Number interface {
    constraints.Integer | constraints.Float
}
Run Code Online (Sandbox Code Playgroud)

在哪里:

  • Numberconstraints.Integer是和的类型集的并集constraints.Float
  • constraints.Integer是所有有符号和无符号整数类型的集合
  • contraints.Float是浮点类型的集合

这将允许您add使用任意两个数字类型的参数进行调用。然后在函数体中,您将能够使用约束中所有类型支持的任何操作。因此,对于数字,这还包括算术运算符。然后声明类似的函数很容易:

func multiply[T Number](a, b T) T {
    return a * b
}
Run Code Online (Sandbox Code Playgroud)

请记住,参数必须具有相同的类型。无论泛型如何,都不能使用不同的类型;从规范操作员

[...] 操作数类型必须相同,除非操作涉及移位或无类型常量。

因此,我们的泛型addmultiply函数仅使用一个类型参数来定义T。这意味着您也无法使用add默认类型不兼容的无类型常量调用该函数:

add(2.5, 2) // won't compile
Run Code Online (Sandbox Code Playgroud)

T在这种情况下,编译器将从第一个参数2.5(默认为 )推断 的类型float64,然后将无法匹配 的类型2(默认为 )int

完整程序:

package main

import (
    "fmt"

    "golang.org/x/exp/constraints"
)

type Number interface {
    constraints.Integer | constraints.Float
}

func main() {
    a := 1
    b := 2
    
    fmt.Println(add(1, 2))     // 3
    fmt.Println(add(a, b))     // 3
    fmt.Println(add(1.5, 3.2)) // 4.7
    // fmt.Println(add(2.5, 2)) // default type int of 2 does not match inferred type float64 for T
}

func add[T Number](a, b T) T {
    return a + b
}
Run Code Online (Sandbox Code Playgroud)

游乐场:https://go.dev/play/p/rdqi3_-EdHp

警告:由于这些函数还处理浮点数,因此请记住浮点数可以保存NaN值和无穷大。


关于复数

Go 有complex64预先complex128声明的类型。您也可以在约束中使用它们Number

import "golang.org/x/exp/constraints"

type Number interface {
    constraints.Integer | constraints.Float | constraints.Complex
}
Run Code Online (Sandbox Code Playgroud)

这并不限制这些泛型函数的功能:整数和浮点数(仅+-*)支持的算术运算符/以及复杂类型也支持所有顺序运算符。余数运算符%和按位运算符仅受整数支持,因此受限制为 的类型参数支持constraints.Integer


Mat*_*iak 5

由于泛型,现在这是可能的,但它非常乏味,因为您必须在函数声明中手动指定每个数字类型。

// Adds two integers and returns the result together with a boolean
// which indicates whether an overflow has occurred
func AddInt[I int | uint | int8 | uint8 | int16 | uint16 | int32 | uint32 | int64 | uint64](a, b I) (I, bool) {
    c := a + b
    if (c > a) == (b > 0) {
        return c, true
    }
    return c, false
}
Run Code Online (Sandbox Code Playgroud)

您还可以定义一个包含 int 类型的详细列表的接口

type Int interface {
    int | uint | int8 | uint8 | int16 | uint16 | int32 | uint32 | int64 | uint64
}

// Adds two integers and returns the result together with a boolean
// which indicates whether an overflow has occurred
func AddInt[I Int](a, b I) (I, bool) {
    c := a + b
    if (c > a) == (b > 0) {
            return c, true
    }
    return c, false
}
Run Code Online (Sandbox Code Playgroud)

有一个约束包提供了一种更干燥的方式来定义此类函数,但它是实验性的,可能会在某个时候从语言中删除,所以我不建议使用它。

  • 尽管看起来很丑陋,但此选项确实允许您使用“I(1234)”转换为所选类型,这是上面使用“constraints”的答案所不允许的。 (2认同)