Go 1.18 中引入的泛型彻底改变了编写可重用和类型安全代码的方式。泛型带来了灵活性和强大的功能,同时保持了 Go 的简单哲学。然而,要了解细微差别、优点以及泛型与传统方法(如 interface{} )的比较,需要仔细观察。
让我们探索泛型的复杂性,深入研究约束,将泛型与接口{}进行比较,并演示它们的实际应用。我们还将讨论性能考虑因素和二进制大小的影响。让我们开始吧!
泛型允许开发人员编写可以在任何类型上操作的函数和数据结构,同时保持类型安全。泛型不依赖于涉及运行时类型断言的接口{},而是让您指定一组约束来规定类型上允许的操作。
语法
func FunctionName[T TypeConstraint](parameterName T) ReturnType { // Function body using T }
T: 类型参数,表示类型的占位符。
TypeConstraint:将 T 的类型限制为特定类型或一组类型。
parameterName T:参数使用泛型类型T.
ReturnType:函数还可以返回 T.
类型的值示例
func Sum[T int | float64](a, b T) T { return a + b }
func Sum: 声明函数名称 Sum
[T int | float64]: 指定引入 T 作为类型参数的类型参数列表,约束为特定类型(int 或 float64)。 Sum 函数只能采用 int 或 float64 参数,不能组合使用,两者都必须是 int 或 float64。我们将在下面的部分中进一步探讨这一点。
(a, b T): 声明两个参数 a 和 b,均为 T 类型(泛型类型) ).
T: 指定函数的返回类型,与类型参数 T.
匹配约束定义哪些操作对于泛型类型有效。 Go 提供了强大的约束工具,包括实验性约束包(golang.org/x/exp/constraints)。
Go 引入了带有泛型的内置约束,以提供类型安全,同时允许灵活地定义可重用和泛型代码。这些约束使开发人员能够对泛型函数或类型中使用的类型强制执行规则。
func FunctionName[T TypeConstraint](parameterName T) ReturnType { // Function body using T }
func Sum[T int | float64](a, b T) T { return a + b }
实验限制
func PrintValues[T any](values []T) { for _, v := range values { fmt.Println(v) } }
自定义约束是定义泛型类型参数必须满足的一组类型或类型行为的接口。通过创建您自己的约束,我们可以;
将类型限制为特定子集,例如数字类型。
需要类型来实现特定的方法或行为。
为您的通用函数和类型添加更多控制和特异性。
语法
func CheckDuplicates[T comparable](items []T) []T { seen := make(map[T]bool) duplicates := []T{} for _, item := range items { if seen[item] { duplicates = append(duplicates, item) } else { seen[item] = true } } return duplicates }
示例
import ( "golang.org/x/exp/constraints" "fmt" ) func SortSlice[T constraints.Ordered](items []T) []T { sorted := append([]T{}, items...) // Copy slice sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] }) return sorted } func main() { nums := []int{5, 2, 9, 1} fmt.Println(SortSlice(nums)) // Output: [1 2 5 9] words := []string{"banana", "apple", "cherry"} fmt.Println(SortSlice(words)) // Output: [apple banana cherry] }
Sum 函数 只能使用 int、int64 和 float64 参数调用。
如果你想强制类型必须实现某些方法,你可以使用这些方法来定义它。
type Numeric interface { int | float64 | uint }
Formatter 约束要求任何用作 T 的类型必须具有返回 字符串.
组合约束
type Number interface { int | int64 | float64 } func Sum[T Number](a, b T) T { return a + b }
int、float54),并且需要存在 abs 方法。
泛型与接口{}类型安全
接口{}:由于额外的运行时类型检查,速度较慢。
泛型:更快,因为编译器会生成特定于类型的优化代码路径。
界面{}:通常冗长且不太直观,使代码更难维护。
泛型:更简洁的语法可以带来更直观和可维护的代码。
interface{}:生成更小的二进制文件,因为它不会为不同类型重复代码。
泛型:由于类型专门化而略微增加二进制大小以获得更好的性能。
示例
func FunctionName[T TypeConstraint](parameterName T) ReturnType { // Function body using T }
代码运行良好,类型断言是开销。 Add 函数可以使用任何参数调用,a 和 b 参数可以是不同的类型,但是代码会在运行时崩溃。
func Sum[T int | float64](a, b T) T { return a + b }
泛型消除了由不正确的类型断言引起的运行时恐慌的风险并提高了清晰度。
泛型为每种类型生成专门的代码,与接口{}相比,可以带来更好的运行时性能。
存在一个权衡:泛型由于每种类型的代码重复而增加了二进制大小,但这与好处相比通常可以忽略不计。
约束的复杂性: 虽然像 Constraints.Ordered 这样的约束简化了常见用例,但定义高度自定义的约束可能会变得冗长。
结构体中没有类型推断: 与函数不同,您必须为结构体显式指定类型参数。
func PrintValues[T any](values []T) { for _, v := range values { fmt.Println(v) } }
仅限于编译时约束:Go 泛型专注于编译时安全,而 Rust 这样的语言使用生命周期和特征提供更强大的约束。
我们将实现一个具有接口{}和通用的简单队列,并对结果进行基准测试。
func CheckDuplicates[T comparable](items []T) []T { seen := make(map[T]bool) duplicates := []T{} for _, item := range items { if seen[item] { duplicates = append(duplicates, item) } else { seen[item] = true } } return duplicates }
import ( "golang.org/x/exp/constraints" "fmt" ) func SortSlice[T constraints.Ordered](items []T) []T { sorted := append([]T{}, items...) // Copy slice sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] }) return sorted } func main() { nums := []int{5, 2, 9, 1} fmt.Println(SortSlice(nums)) // Output: [1 2 5 9] words := []string{"banana", "apple", "cherry"} fmt.Println(SortSlice(words)) // Output: [apple banana cherry] }
type Numeric interface { int | float64 | uint }
执行时间:
通用实现比 interface{} 版本快大约 63.64%,因为它避免了运行时类型断言并直接对给定类型进行操作。
分配:
Interface{} 版本的分配量增加了 3 倍,这主要是由于插入和检索值时的装箱/拆箱。这增加了垃圾收集的开销。
对于较大的工作负载,例如 100 万次入队/出队操作,性能差距会扩大。具有高吞吐量要求的实际应用程序(例如消息队列、作业调度程序)可以从泛型中受益匪浅。
Go 中的泛型在功能强大和简单性之间取得了平衡,为编写可重用和类型安全的代码提供了实用的解决方案。虽然不像 Rust 或 C 那样功能丰富,但与 Go 的极简主义哲学完美契合。了解约束等约束。有效有序和利用泛型可以极大地提高代码质量和可维护性。
随着泛型的不断发展,它们注定会在 Go 的生态系统中发挥核心作用。因此,潜入、实验并拥抱 Go 编程类型安全性和灵活性的新时代!
查看 github 存储库以获取一些关于泛型的示例。
欢迎来到Go 泛型存储库!该存储库是 1.18 版本中引入的用于理解、学习和掌握 Go 中泛型的一站式资源。泛型为 Go 带来了类型参数的强大功能,使开发人员能够编写可重用且类型安全的代码,而不会影响性能或可读性。
该存储库包含精心策划的示例,涵盖广泛的主题,从基本语法到高级模式和实际用例。无论您是初学者还是经验丰富的 Go 开发人员,此集合都将帮助您在项目中有效地利用泛型。
这些示例介绍了泛型的基本概念,帮助您掌握语法和核心功能:
以上是Go 中的泛型:改变代码可重用性的详细内容。更多信息请关注PHP中文网其他相关文章!