深入理解Go语言的类型系统与泛型编程
1. 引言
Go语言作为一种现代化的编程语言,以其简洁的语法和强大的并发支持而闻名。在Go语言中,类型系统和泛型编程是两个核心概念,它们不仅决定了代码的结构和行为,还直接影响了程序的性能和可维护性。本文将深入探讨Go语言的类型系统和泛型编程,帮助开发者更好地理解和应用这些特性。
2. Go语言的类型系统
Go语言的类型系统是静态且强类型的,这意味着在编译时所有变量的类型都必须已知,并且类型转换是严格控制的。Go语言支持多种内置类型,包括布尔型、数值型、字符串、指针、结构体、数组、切片、映射和通道等。
2.1 内置类型
Go语言提供了丰富的内置类型,以下是一些常见的内置类型及其特点:
| 类型 | 描述 |
|---|---|
bool | 布尔型,表示真假值 |
int | 整型,表示有符号整数 |
uint | 无符号整型,表示非负整数 |
float32 | 单精度浮点数 |
float64 | 双精度浮点数 |
complex64 | 64位复数 |
complex128 | 128位复数 |
string | 字符串,表示一系列字符 |
byte | uint8 的别名,表示一个字节 |
rune | int32 的别名,表示一个Unicode码点 |
2.2 用户定义类型
除了内置类型,Go语言还允许用户定义自己的类型。用户定义类型可以是现有类型的别名,也可以是新的结构体类型。
2.2.1 类型别名
类型别名用于为现有类型创建一个新的名称,而不改变其底层类型。例如:
type ByteAlias byte
2.2.2 结构体类型
结构体是一种用户定义的复合类型,可以包含多个字段。结构体的定义如下:
type Person struct {
Name string
Age int
}
结构体可以通过字段访问和方法调用来操作其成员。例如:
p := Person{Name: "Alice", Age: 30}
fmt.Println(p.Name) // 输出: Alice
3. 类型约束与接口
Go语言的类型系统支持接口,接口用于定义一组方法的集合。类型通过实现接口中的方法来隐式实现接口。接口类型可以用于运行时多态性和类型约束。
3.1 接口定义
接口由一组方法签名组成,定义如下:
type Reader interface {
Read(p []byte) (n int, err error)
}
3.2 类型约束
Go语言的泛型类型参数可以使用接口作为类型约束,确保类型参数满足一定的条件。例如:
type Ordered interface {
~int | ~float64 | ~string
}
func min[T Ordered](a, b T) T {
if a < b {
return a
}
return b
}
在这个例子中, min 函数的类型参数 T 被约束为 Ordered 接口,这意味着 T 可以是 int 、 float64 或 string 。
4. 泛型编程简介
Go语言自1.18版本开始引入了泛型编程的支持,泛型编程允许编写更加通用和灵活的代码。泛型类型和函数可以通过类型参数化来适应不同的数据类型,从而提高代码的复用性和可维护性。
4.1 泛型类型定义
泛型类型定义使用类型参数列表来指定参数化类型。例如:
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item, true
}
在这个例子中, Stack 是一个泛型类型, T 是类型参数, any 是类型约束,表示 T 可以是任何类型。
4.2 泛型函数定义
泛型函数定义同样使用类型参数列表来指定参数化类型。例如:
func Sum[T int | float64](numbers ...T) T {
var sum T
for _, num := range numbers {
sum += num
}
return sum
}
在这个例子中, Sum 函数的类型参数 T 被约束为 int 或 float64 ,确保传入的参数是数值类型。
5. 泛型编程的应用
泛型编程不仅可以用于容器类型(如栈、队列、列表等),还可以用于算法实现。通过泛型编程,我们可以编写更加通用的代码,避免重复劳动。
5.1 泛型栈的实现
栈是一种后进先出(LIFO)的数据结构,支持两种基本操作: Push (入栈)和 Pop (出栈)。使用泛型编程,我们可以实现一个通用的栈结构,适用于任何类型的元素。
type Node[T any] struct {
Value T
Next *Node[T]
}
type LinkedListStack[T any] struct {
top *Node[T]
}
func NewLinkedListStack[T any]() *LinkedListStack[T] {
return &LinkedListStack[T]{}
}
func (s *LinkedListStack[T]) Push(value T) {
newNode := &Node[T]{Value: value, Next: s.top}
s.top = newNode
}
func (s *LinkedListStack[T]) Pop() (T, bool) {
if s.top == nil {
var zero T
return zero, false
}
value := s.top.Value
s.top = s.top.Next
return value, true
}
5.2 泛型队列的实现
队列是一种先进先出(FIFO)的数据结构,支持两种基本操作: Enqueue (入队)和 Dequeue (出队)。使用泛型编程,我们可以实现一个通用的队列结构,适用于任何类型的元素。
type Queue[T any] struct {
elements []T
}
func NewQueue[T any]() *Queue[T] {
return &Queue[T]{elements: make([]T, 0)}
}
func (q *Queue[T]) Enqueue(value T) {
q.elements = append(q.elements, value)
}
func (q *Queue[T]) Dequeue() (T, bool) {
if len(q.elements) == 0 {
var zero T
return zero, false
}
value := q.elements[0]
q.elements = q.elements[1:]
return value, true
}
6. 泛型编程的优势
泛型编程带来了许多优势,包括但不限于:
- 代码复用 :通过泛型编程,可以编写适用于多种类型的代码,减少重复代码的编写。
- 类型安全 :泛型编程在编译时进行类型检查,确保代码的类型安全性。
- 性能优化 :泛型代码在编译时会生成针对具体类型的代码,避免了运行时的类型检查和转换开销。
6.1 泛型编程的局限性
尽管泛型编程带来了许多好处,但也存在一些局限性:
- 复杂性 :泛型编程的语法和概念较为复杂,初学者可能难以掌握。
- 编译时间 :泛型代码在编译时会生成针对具体类型的代码,可能导致编译时间增加。
7. 类型约束的深入探讨
类型约束是泛型编程中的一个重要概念,它用于限制类型参数的范围,确保类型参数满足一定的条件。Go语言提供了两种内置的类型约束: any 和 comparable 。
7.1 any 类型约束
any 是 interface{} 的别名,表示任何类型。使用 any 作为类型约束时,类型参数可以是任何类型。
type Container[T any] struct {
value T
}
7.2 comparable 类型约束
comparable 表示所有可比较的类型。使用 comparable 作为类型约束时,类型参数必须是可比较的类型,例如整数、浮点数、字符串等。
type Set[T comparable] struct {
elements map[T]struct{}
}
func NewSet[T comparable]() *Set[T] {
return &Set[T]{elements: make(map[T]struct{})}
}
func (s *Set[T]) Add(value T) {
s.elements[value] = struct{}{}
}
func (s *Set[T]) Contains(value T) bool {
_, exists := s.elements[value]
return exists
}
7.3 自定义类型约束
除了内置的类型约束,Go语言还允许用户定义自己的类型约束。自定义类型约束可以通过接口来实现,例如:
type Number interface {
int | float64
}
func Add[T Number](a, b T) T {
return a + b
}
在这个例子中, Number 接口定义了 int 和 float64 两种类型, Add 函数的类型参数 T 被约束为 Number 接口,确保传入的参数是数值类型。
8. 泛型编程的实际应用
泛型编程在实际开发中有着广泛的应用,特别是在容器类型和算法实现中。通过泛型编程,我们可以编写更加通用和灵活的代码,提高代码的复用性和可维护性。
8.1 泛型容器类型
容器类型是泛型编程的经典应用场景之一。通过泛型编程,我们可以实现适用于多种类型的容器类型,例如栈、队列、列表等。
8.1.1 泛型栈
栈是一种后进先出(LIFO)的数据结构,支持两种基本操作: Push (入栈)和 Pop (出栈)。使用泛型编程,我们可以实现一个通用的栈结构,适用于任何类型的元素。
type Stack[T any] struct {
elements []T
}
func NewStack[T any]() *Stack[T] {
return &Stack[T]{elements: make([]T, 0)}
}
func (s *Stack[T]) Push(value T) {
s.elements = append(s.elements, value)
}
func (s *Stack[T]) Pop() (T, bool) {
if len(s.elements) == 0 {
var zero T
return zero, false
}
value := s.elements[len(s.elements)-1]
s.elements = s.elements[:len(s.elements)-1]
return value, true
}
8.1.2 泛型队列
队列是一种先进先出(FIFO)的数据结构,支持两种基本操作: Enqueue (入队)和 Dequeue (出队)。使用泛型编程,我们可以实现一个通用的队列结构,适用于任何类型的元素。
type Queue[T any] struct {
elements []T
}
func NewQueue[T any]() *Queue[T] {
return &Queue[T]{elements: make([]T, 0)}
}
func (q *Queue[T]) Enqueue(value T) {
q.elements = append(q.elements, value)
}
func (q *Queue[T]) Dequeue() (T, bool) {
if len(q.elements) == 0 {
var zero T
return zero, false
}
value := q.elements[0]
q.elements = q.elements[1:]
return value, true
}
8.2 泛型算法实现
泛型编程还可以用于算法实现,例如排序算法、搜索算法等。通过泛型编程,我们可以编写适用于多种类型的算法,提高代码的复用性和可维护性。
8.2.1 泛型排序算法
快速排序是一种经典的排序算法,通过泛型编程,我们可以实现一个适用于多种类型的快速排序算法。
func quickSort[T Ordered](arr []T) {
if len(arr) < 2 {
return
}
pivot := arr[0]
left := make([]T, 0)
right := make([]T, 0)
for _, v := range arr[1:] {
if v < pivot {
left = append(left, v)
} else {
right = append(right, v)
}
}
quickSort(left)
quickSort(right)
copy(arr[:len(left)], left)
arr[len(left)] = pivot
copy(arr[len(left)+1:], right)
}
func main() {
nums := []int{3, 6, 8, 10, 1, 2, 1}
quickSort(nums)
fmt.Println(nums) // 输出: [1 1 2 3 6 8 10]
}
在这个例子中, quickSort 函数的类型参数 T 被约束为 Ordered 接口,确保传入的参数是可比较的类型。
8.3 泛型编程的优化
泛型编程不仅可以提高代码的复用性和可维护性,还可以带来性能上的优化。通过泛型编程,编译器可以在编译时生成针对具体类型的代码,避免运行时的类型检查和转换开销。
8.3.1 泛型性能优化
泛型代码在编译时会生成针对具体类型的代码,避免了运行时的类型检查和转换开销。例如,以下代码展示了泛型排序算法的性能优化:
type Ordered interface {
~int | ~float64 | ~string
}
func quickSort[T Ordered](arr []T) {
if len(arr) < 2 {
return
}
pivot := arr[0]
left := make([]T, 0)
right := make([]T, 0)
for _, v := range arr[1:] {
if v < pivot {
left = append(left, v)
} else {
right = append(right, v)
}
}
quickSort(left)
quickSort(right)
copy(arr[:len(left)], left)
arr[len(left)] = pivot
copy(arr[len(left)+1:], right)
}
func main() {
nums := []int{3, 6, 8, 10, 1, 2, 1}
quickSort(nums)
fmt.Println(nums) // 输出: [1 1 2 3 6 8 10]
}
在这个例子中, quickSort 函数的类型参数 T 被约束为 Ordered 接口,确保传入的参数是可比较的类型。编译器会在编译时生成针对具体类型的代码,避免了运行时的类型检查和转换开销。
9. 泛型编程的最佳实践
在使用泛型编程时,遵循最佳实践可以帮助我们编写更加高效和易读的代码。以下是一些泛型编程的最佳实践:
9.1 使用有意义的类型参数名称
类型参数的名称应该具有描述性,能够清晰地表达其含义。例如:
type Stack[T any] struct {
elements []T
}
在这个例子中, T 表示栈中元素的类型。
9.2 尽量减少类型约束
类型约束应该尽量简洁,避免过度约束。例如:
type Ordered interface {
~int | ~float64 | ~string
}
在这个例子中, Ordered 接口约束了 T 可以是 int 、 float64 或 string ,避免了不必要的复杂性。
9.3 使用接口组合
接口组合可以用于定义更加复杂的类型约束。例如:
type Number interface {
int | float64
}
type Printable interface {
fmt.Stringer
}
type PrintableNumber interface {
Number & Printable
}
func PrintNumber[T PrintableNumber](value T) {
fmt.Println(value)
}
在这个例子中, PrintableNumber 接口组合了 Number 和 Printable 接口,确保传入的参数既是数值类型又是可打印的类型。
10. 泛型编程的挑战与解决方案
尽管泛型编程带来了许多好处,但在实际应用中也面临一些挑战。以下是一些常见的挑战及其解决方案:
10.1 编译时间增加
泛型代码在编译时会生成针对具体类型的代码,可能导致编译时间增加。解决方案包括:
- 减少泛型代码的复杂性 :尽量简化泛型代码,避免过度复杂的逻辑。
- 使用缓存 :使用编译缓存来加速编译过程。
10.2 类型推断问题
在某些情况下,编译器可能无法正确推断类型参数,导致编译错误。解决方案包括:
- 显式指定类型参数 :在调用泛型函数时显式指定类型参数。
- 简化类型约束 :尽量简化类型约束,避免过度复杂的约束条件。
10.3 代码可读性降低
泛型编程可能会导致代码可读性降低,尤其是在类型约束较为复杂的情况下。解决方案包括:
- 使用有意义的类型参数名称 :确保类型参数名称具有描述性,能够清晰地表达其含义。
- 添加注释 :在必要时添加注释,解释类型参数的含义和约束条件。
11. 泛型编程的未来发展方向
随着Go语言的发展,泛型编程将会得到进一步的增强和完善。未来的版本可能会引入更多的内置类型约束,支持更复杂的泛型编程场景。此外,Go语言的泛型编程还可能会与反射机制相结合,提供更多灵活性。
11.1 更多内置类型约束
未来的Go语言版本可能会引入更多的内置类型约束,例如 ordered 、 numeric 等,进一步简化泛型编程的使用。
11.2 泛型编程与反射机制结合
泛型编程与反射机制的结合可以提供更多灵活性。例如,通过反射机制可以在运行时动态获取类型信息,结合泛型编程可以实现更加通用的代码。
11.3 泛型编程的扩展
未来的Go语言版本可能会支持更复杂的泛型编程场景,例如泛型接口、泛型方法等,进一步提升代码的复用性和灵活性。
12. 泛型编程的实际案例
为了更好地理解泛型编程的应用,我们来看一个实际案例:实现一个泛型二叉树数据结构。
12.1 泛型二叉树的定义
二叉树是一种常见的数据结构,每个节点最多有两个子节点。使用泛型编程,我们可以实现一个适用于多种类型的二叉树结构。
type BinaryTree[T any] struct {
root *Node[T]
}
type Node[T any] struct {
value T
left *Node[T]
right *Node[T]
}
func NewBinaryTree[T any]() *BinaryTree[T] {
return &BinaryTree[T]{}
}
func (t *BinaryTree[T]) Insert(value T) {
if t.root == nil {
t.root = &Node[T]{value: value}
return
}
t.insertNode(t.root, value)
}
func (t *BinaryTree[T]) insertNode(node *Node[T], value T) {
if value < node.value {
if node.left == nil {
node.left = &Node[T]{value: value}
} else {
t.insertNode(node.left, value)
}
} else {
if node.right == nil {
node.right = &Node[T]{value: value}
} else {
t.insertNode(node.right, value)
}
}
12.2 泛型二叉树的使用
使用泛型二叉树时,可以根据具体需求选择不同类型的数据。例如:
func main() {
tree := NewBinaryTree[int]()
tree.Insert(10)
tree.Insert(5)
tree.Insert(15)
tree.Insert(3)
tree.Insert(7)
fmt.Println(tree.root.value) // 输出: 10
}
在这个例子中, BinaryTree 是一个泛型类型, T 表示树中节点的类型。通过泛型编程,我们可以轻松实现适用于多种类型的二叉树结构。
12.3 泛型二叉树的优化
为了提高泛型二叉树的性能,我们可以对其进行优化。例如,使用平衡二叉树来减少查找时间。
12.3.1 平衡二叉树的实现
平衡二叉树是一种特殊的二叉树,通过保持树的高度平衡来提高查找效率。实现平衡二叉树的关键在于插入和删除操作时的平衡调整。
type AVLTree[T Ordered] struct {
root *AVLNode[T]
}
type AVLNode[T Ordered] struct {
value T
height int
left *AVLNode[T]
right *AVLNode[T]
}
func (t *AVLTree[T]) Insert(value T) {
t.root = t.insertNode(t.root, value)
}
func (t *AVLTree[T]) insertNode(node *AVLNode[T], value T) *AVLNode[T] {
if node == nil {
return &AVLNode[T]{value: value, height: 1}
}
if value < node.value {
node.left = t.insertNode(node.left, value)
} else {
node.right = t.insertNode(node.right, value)
}
node.height = 1 + max(height(node.left), height(node.right))
balanceFactor := getBalanceFactor(node)
if balanceFactor > 1 && value < node.left.value {
return rotateRight(node)
}
if balanceFactor < -1 && value > node.right.value {
return rotateLeft(node)
}
if balanceFactor > 1 && value > node.left.value {
node.left = rotateLeft(node.left)
return rotateRight(node)
}
if balanceFactor < -1 && value < node.right.value {
node.right = rotateRight(node.right)
return rotateLeft(node)
}
return node
}
func height[T Ordered](node *AVLNode[T]) int {
if node == nil {
return 0
}
return node.height
}
func getBalanceFactor[T Ordered](node *AVLNode[T]) int {
if node == nil {
return 0
}
return height(node.left) - height(node.right)
}
func rotateLeft[T Ordered](z *AVLNode[T]) *AVLNode[T] {
y := z.right
T := y.left
y.left = z
z.right = T
z.height = max(height(z.left), height(z.right)) + 1
y.height = max(height(y.left), height(y.right)) + 1
return y
}
func rotateRight[T Ordered](z *AVLNode[T]) *AVLNode[T] {
y := z.left
T := y.right
y.right = z
z.left = T
z.height = max(height(z.left), height(z.right)) + 1
y.height = max(height(y.left), height(y.right)) + 1
return y
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func main() {
tree := NewAVLTree[int]()
tree.Insert(10)
tree.Insert(5)
tree.Insert(15)
tree.Insert(3)
tree.Insert(7)
fmt.Println(tree.root.value) // 输出: 10
}
在这个例子中, AVLTree 是一个泛型类型, T 表示树中节点的类型。通过泛型编程,我们可以轻松实现适用于多种类型的平衡二叉树结构。平衡二叉树通过保持树的高度平衡来提高查找效率,适用于大规模数据的高效查找。
12.4 泛型编程的扩展
泛型编程不仅可以用于容器类型和算法实现,还可以用于其他场景,例如泛型接口、泛型方法等。通过泛型编程,我们可以编写更加通用和灵活的代码,提高代码的复用性和可维护性。
12.4.1 泛型接口
泛型接口允许我们定义适用于多种类型的接口。例如:
type Printable interface {
fmt.Stringer
}
type PrintableNumber interface {
Number & Printable
}
func PrintNumber[T PrintableNumber](value T) {
fmt.Println(value)
}
12.4.2 泛型方法
泛型方法允许我们在方法中使用类型参数,进一步提升代码的灵活性。例如:
type Container[T any] struct {
elements []T
}
func (c *Container[T]) Add(element T) {
c.elements = append(c.elements, element)
}
func (c *Container[T]) Remove(index int) T {
if index < 0 || index >= len(c.elements) {
var zero T
return zero
}
element := c.elements[index]
c.elements = append(c.elements[:index], c.elements[index+1:]...)
return element
}
在这个例子中, Container 是一个泛型类型, T 表示容器中元素的类型。通过泛型方法,我们可以轻松实现适用于多种类型的容器操作。
12.5 泛型编程的总结
泛型编程是Go语言的重要特性之一,它不仅提高了代码的复用性和可维护性,还带来了性能上的优化。通过泛型编程,我们可以编写更加通用和灵活的代码,适用于多种类型的场景。在未来的发展中,泛型编程将会得到进一步的增强和完善,为开发者带来更多便利。
接下来,我们将继续探讨泛型编程在实际开发中的更多应用场景,包括泛型多映射、泛型排序函数等。同时,我们还会深入分析泛型编程的性能优化和最佳实践,帮助开发者更好地掌握这一强大工具。
13. 泛型多映射的实现
多映射是一种特殊的映射类型,允许一个键对应多个值。使用泛型编程,我们可以实现一个适用于多种类型的多映射结构。
13.1 泛型多映射的定义
多映射可以看作是一个键值对的集合,其中每个键可以对应多个值。我们可以通过泛型编程来实现一个多映射结构,适用于多种类型的键和值。
type MultiMap[K comparable, V any] struct {
entries map[K][]V
}
func NewMultiMap[K comparable, V any]() *MultiMap[K, V] {
return &MultiMap[K, V]{entries: make(map[K][]V)}
}
func (m *MultiMap[K, V]) Add(key K, value V) {
m.entries[key] = append(m.entries[key], value)
}
func (m *MultiMap[K, V]) Get(key K) []V {
return m.entries[key]
}
func (m *MultiMap[K, V]) Remove(key K, value V) {
values := m.entries[key]
if values == nil {
return
}
for i, v := range values {
if v == value {
m.entries[key] = append(values[:i], values[i+1:]...)
return
}
}
}
13.2 泛型多映射的使用
使用泛型多映射时,可以根据具体需求选择不同类型的键和值。例如:
func main() {
mm := NewMultiMap[string, int]()
mm.Add("fruits", 1)
mm.Add("fruits", 2)
mm.Add("vegetables", 3)
fmt.Println(mm.Get("fruits")) // 输出: [1 2]
fmt.Println(mm.Get("vegetables")) // 输出: [3]
mm.Remove("fruits", 1)
fmt.Println(mm.Get("fruits")) // 输出: [2]
}
在这个例子中, MultiMap 是一个泛型类型, K 表示键的类型, V 表示值的类型。通过泛型编程,我们可以轻松实现适用于多种类型的多映射结构。
13.3 泛型多映射的优化
为了提高泛型多映射的性能,我们可以对其进行优化。例如,使用并发安全的多映射来支持多线程操作。
13.3.1 并发安全的多映射
并发安全的多映射通过加锁来确保多线程环境下的安全操作。实现并发安全的多映射的关键在于在并发操作时进行同步控制。
type ConcurrentMultiMap[K comparable, V any] struct {
mu sync.RWMutex
entries map[K][]V
}
func NewConcurrentMultiMap[K comparable, V any]() *ConcurrentMultiMap[K, V] {
return &ConcurrentMultiMap[K, V]{entries: make(map[K][]V)}
}
func (m *ConcurrentMultiMap[K, V]) Add(key K, value V) {
m.mu.Lock()
defer m.mu.Unlock()
m.entries[key] = append(m.entries[key], value)
}
func (m *ConcurrentMultiMap[K, V]) Get(key K) []V {
m.mu.RLock()
defer m.mu.RUnlock()
return m.entries[key]
}
func (m *ConcurrentMultiMap[K, V]) Remove(key K, value V) {
m.mu.Lock()
defer m.mu.Unlock()
values := m.entries[key]
if values == nil {
return
}
for i, v := range values {
if v == value {
m.entries[key] = append(values[:i], values[i+1:]...)
return
}
}
}
在这个例子中, ConcurrentMultiMap 是一个泛型类型, K 表示键的类型, V 表示值的类型。通过泛型编程,我们可以轻松实现适用于多种类型的并发安全多映射结构。并发安全的多映射通过加锁来确保多线程环境下的安全操作,适用于高并发场景。
14. 泛型排序函数的实现
排序函数是编程中最常见的算法之一。通过泛型编程,我们可以实现一个适用于多种类型的排序函数,提高代码的复用性和可维护性。
14.1 泛型快速排序算法
快速排序是一种经典的排序算法,通过泛型编程,我们可以实现一个适用于多种类型的快速排序算法。
type Ordered interface {
~int | ~float64 | ~string
}
func quickSort[T Ordered](arr []T) {
if len(arr) < 2 {
return
}
pivot := arr[0]
left := make([]T, 0)
right := make([]T, 0)
for _, v := range arr[1:] {
if v < pivot {
left = append(left, v)
} else {
right = append(right, v)
}
}
quickSort(left)
quickSort(right)
copy(arr[:len(left)], left)
arr[len(left)] = pivot
copy(arr[len(left)+1:], right)
}
func main() {
nums := []int{3, 6, 8, 10, 1, 2, 1}
quickSort(nums)
fmt.Println(nums) // 输出: [1 1 2 3 6 8 10]
}
14.2 泛型冒泡排序算法
冒泡排序是一种简单的排序算法,通过泛型编程,我们可以实现一个适用于多种类型的冒泡排序算法。
type Ordered interface {
~int | ~float64 | ~string
}
func bubbleSort[T Ordered](arr []T) {
n := len(arr)
for i := 0; i < n-1; i++ {
for j := 0; j < n-i-1; j++ {
if arr[j] > arr[j+1] {
arr[j], arr[j+1] = arr[j+1], arr[j]
}
}
}
}
func main() {
nums := []int{3, 6, 8, 10, 1, 2, 1}
bubbleSort(nums)
fmt.Println(nums) // 输出: [1 1 2 3 6 8 10]
}
14.3 泛型排序函数的优化
为了提高泛型排序函数的性能,我们可以对其进行优化。例如,使用并行排序来加速排序过程。
14.3.1 并行快速排序算法
并行快速排序通过多线程并行处理来加速排序过程。实现并行快速排序的关键在于合理划分任务和管理线程池。
type Ordered interface {
~int | ~float64 | ~string
}
func parallelQuickSort[T Ordered](arr []T) {
if len(arr) < 2 {
return
}
pivot := arr[0]
left := make([]T, 0)
right := make([]T, 0)
for _, v := range arr[1:] {
if v < pivot {
left = append(left, v)
} else {
right = append(right, v)
}
}
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
parallelQuickSort(left)
}()
go func() {
defer wg.Done()
parallelQuickSort(right)
}()
wg.Wait()
copy(arr[:len(left)], left)
arr[len(left)] = pivot
copy(arr[len(left)+1:], right)
}
func main() {
nums := []int{3, 6, 8, 10, 1, 2, 1}
parallelQuickSort(nums)
fmt.Println(nums) // 输出: [1 1 2 3 6 8 10]
}
在这个例子中, parallelQuickSort 函数的类型参数 T 被约束为 Ordered 接口,确保传入的参数是可比较的类型。通过并行处理,可以显著提高排序的效率,适用于大规模数据的排序。
15. 泛型编程的性能优化
泛型编程不仅可以提高代码的复用性和可维护性,还可以带来性能上的优化。通过泛型编程,编译器可以在编译时生成针对具体类型的代码,避免运行时的类型检查和转换开销。
15.1 编译时优化
编译时优化是泛型编程带来的主要性能提升之一。编译器会在编译时生成针对具体类型的代码,避免了运行时的类型检查和转换开销。例如:
type Ordered interface {
~int | ~float64 | ~string
}
func quickSort[T Ordered](arr []T) {
if len(arr) < 2 {
return
}
pivot := arr[0]
left := make([]T, 0)
right := make([]T, 0)
for _, v := range arr[1:] {
if v < pivot {
left = append(left, v)
} else {
right = append(right, v)
}
}
quickSort(left)
quickSort(right)
copy(arr[:len(left)], left)
arr[len(left)] = pivot
copy(arr[len(left)+1:], right)
}
func main() {
nums := []int{3, 6, 8, 10, 1, 2, 1}
quickSort(nums)
fmt.Println(nums) // 输出: [1 1 2 3 6 8 10]
}
在这个例子中, quickSort 函数的类型参数 T 被约束为 Ordered 接口,确保传入的参数是可比较的类型。编译器会在编译时生成针对具体类型的代码,避免了运行时的类型检查和转换开销。
15.2 运行时优化
除了编译时优化,泛型编程还可以带来运行时优化。例如,通过减少不必要的内存分配和复制操作,可以显著提高性能。
15.2.1 减少内存分配
减少内存分配是提高性能的关键之一。通过优化内存分配策略,可以显著减少内存碎片和垃圾回收压力。
type Ordered interface {
~int | ~float64 | ~string
}
func optimizedQuickSort[T Ordered](arr []T) {
if len(arr) < 2 {
return
}
pivotIndex := partition(arr)
optimizedQuickSort(arr[:pivotIndex])
optimizedQuickSort(arr[pivotIndex+1:])
}
func partition[T Ordered](arr []T) int {
pivot := arr[0]
i := 1
for j := 1; j < len(arr); j++ {
if arr[j] < pivot {
arr[i], arr[j] = arr[j], arr[i]
i++
}
}
arr[0], arr[i-1] = arr[i-1], arr[0]
return i - 1
}
func main() {
nums := []int{3, 6, 8, 10, 1, 2, 1}
optimizedQuickSort(nums)
fmt.Println(nums) // 输出: [1 1 2 3 6 8 10]
}
在这个例子中, optimizedQuickSort 函数通过减少内存分配和复制操作,显著提高了性能。
15.3 泛型编程的内存管理
泛型编程还可以通过优化内存管理来提高性能。例如,使用预分配的切片来减少内存分配次数。
15.3.1 预分配切片
预分配切片可以显著减少内存分配次数,提高性能。例如:
type Ordered interface {
~int | ~float64 | ~string
}
func preAllocatedQuickSort[T Ordered](arr []T) {
if len(arr) < 2 {
return
}
pivot := arr[0]
left := make([]T, 0, len(arr)/2)
right := make([]T, 0, len(arr)/2)
for _, v := range arr[1:] {
if v < pivot {
left = append(left, v)
} else {
right = append(right, v)
}
}
preAllocatedQuickSort(left)
preAllocatedQuickSort(right)
copy(arr[:len(left)], left)
arr[len(left)] = pivot
copy(arr[len(left)+1:], right)
}
func main() {
nums := []int{3, 6, 8, 10, 1, 2, 1}
preAllocatedQuickSort(nums)
fmt.Println(nums) // 输出: [1 1 2 3 6 8 10]
}
在这个例子中, preAllocatedQuickSort 函数通过预分配切片来减少内存分配次数,显著提高了性能。
16. 泛型编程的错误处理
在泛型编程中,错误处理是一个重要的环节。通过合理的错误处理机制,可以确保代码的健壮性和可靠性。
16.1 泛型错误处理的实现
泛型编程可以通过接口和类型约束来实现错误处理。例如:
type Error interface {
Error() string
}
type Result[T any] struct {
value T
err error
}
func Divide[T Ordered](a, b T) Result[T] {
if b == 0 {
return Result[T]{err: errors.New("division by zero")}
}
return Result[T]{value: a / b}
}
func main() {
result := Divide[int](10, 2)
if result.err != nil {
fmt.Println(result.err)
return
}
fmt.Println(result.value) // 输出: 5
}
在这个例子中, Divide 函数的类型参数 T 被约束为 Ordered 接口,确保传入的参数是可比较的类型。通过返回 Result 结构体,可以方便地处理错误情况。
16.2 泛型错误处理的最佳实践
在泛型编程中,遵循最佳实践可以帮助我们编写更加健壮和可靠的代码。以下是一些泛型错误处理的最佳实践:
- 使用
Result结构体 :通过返回Result结构体,可以方便地处理错误情况。 - 定义自定义错误类型 :通过定义自定义错误类型,可以提供更加详细的错误信息。
- 避免过度捕获错误 :只捕获必要的错误,避免过度捕获导致代码复杂度增加。
16.3 泛型错误处理的优化
为了提高泛型错误处理的性能,我们可以对其进行优化。例如,使用延迟错误处理来减少不必要的错误检查。
16.3.1 延迟错误处理
延迟错误处理通过延迟错误检查来减少不必要的错误检查。例如:
type Error interface {
Error() string
}
type Result[T any] struct {
value T
err error
}
func Divide[T Ordered](a, b T) Result[T] {
if b == 0 {
return Result[T]{err: errors.New("division by zero")}
}
return Result[T]{value: a / b}
}
func main() {
result := Divide[int](10, 2)
if result.err != nil {
fmt.Println(result.err)
return
}
fmt.Println(result.value) // 输出: 5
}
在这个例子中, Divide 函数通过返回 Result 结构体,可以方便地处理错误情况。通过延迟错误处理,可以减少不必要的错误检查,提高性能。
17. 泛型编程的测试与调试
在泛型编程中,测试和调试是确保代码质量的重要环节。通过合理的测试和调试机制,可以确保代码的正确性和可靠性。
17.1 泛型测试的实现
泛型测试可以通过编写通用的测试用例来覆盖多种类型。例如:
func TestGenericFunction(t *testing.T) {
tests := []struct {
name string
input []int
expected []int
}{
{"empty slice", []int{}, []int{}},
{"single element", []int{1}, []int{1}},
{"multiple elements", []int{3, 6, 8, 10, 1, 2, 1}, []int{1, 1, 2, 3, 6, 8, 10}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := quickSort(tt.input)
if !reflect.DeepEqual(result, tt.expected) {
t.Errorf("quickSort(%v) = %v, want %v", tt.input, result, tt.expected)
}
})
}
}
在这个例子中, TestGenericFunction 函数通过编写通用的测试用例来覆盖多种类型,确保代码的正确性和可靠性。
17.2 泛型调试的实现
泛型调试可以通过添加日志和断点来追踪代码执行过程。例如:
func quickSort[T Ordered](arr []T) {
if len(arr) < 2 {
return
}
pivot := arr[0]
left := make([]T, 0)
right := make([]T, 0)
for _, v := range arr[1:] {
if v < pivot {
left = append(left, v)
} else {
right = append(right, v)
}
}
log.Printf("Sorting left: %v", left)
quickSort(left)
log.Printf("Sorting right: %v", right)
quickSort(right)
copy(arr[:len(left)], left)
arr[len(left)] = pivot
copy(arr[len(left)+1:], right)
}
在这个例子中, quickSort 函数通过添加日志和断点来追踪代码执行过程,方便调试和排查问题。
17.3 泛型测试与调试的最佳实践
在泛型编程中,遵循最佳实践可以帮助我们编写更加可靠和易于调试的代码。以下是一些泛型测试与调试的最佳实践:
- 编写通用测试用例 :通过编写通用的测试用例来覆盖多种类型,确保代码的正确性和可靠性。
- 添加日志和断点 :通过添加日志和断点来追踪代码执行过程,方便调试和排查问题。
- 使用调试工具 :使用调试工具(如
delve)来辅助调试,提高调试效率。
18. 泛型编程的扩展应用
泛型编程不仅可以用于容器类型和算法实现,还可以用于其他场景,例如泛型接口、泛型方法等。通过泛型编程,我们可以编写更加通用和灵活的代码,提高代码的复用性和可维护性。
18.1 泛型接口
泛型接口允许我们定义适用于多种类型的接口。例如:
type Printable interface {
fmt.Stringer
}
type PrintableNumber interface {
Number & Printable
}
func PrintNumber[T PrintableNumber](value T) {
fmt.Println(value)
}
18.2 泛型方法
泛型方法允许我们在方法中使用类型参数,进一步提升代码的灵活性。例如:
type Container[T any] struct {
elements []T
}
func (c *Container[T]) Add(element T) {
c.elements = append(c.elements, element)
}
func (c *Container[T]) Remove(index int) T {
if index < 0 || index >= len(c.elements) {
var zero T
return zero
}
element := c.elements[index]
c.elements = append(c.elements[:index], c.elements[index+1:]...)
return element
}
18.3 泛型编程的扩展应用案例
为了更好地理解泛型编程的扩展应用,我们来看一个实际案例:实现一个泛型排序函数。
type Ordered interface {
~int | ~float64 | ~string
}
func quickSort[T Ordered](arr []T) {
if len(arr) < 2 {
return
}
pivot := arr[0]
left := make([]T, 0)
right := make([]T, 0)
for _, v := range arr[1:] {
if v < pivot {
left = append(left, v)
} else {
right = append(right, v)
}
}
quickSort(left)
quickSort(right)
copy(arr[:len(left)], left)
arr[len(left)] = pivot
copy(arr[len(left)+1:], right)
}
func main() {
nums := []int{3, 6, 8, 10, 1, 2, 1}
quickSort(nums)
fmt.Println(nums) // 输出: [1 1 2 3 6 8 10]
}
18.4 泛型编程的总结
泛型编程是Go语言的重要特性之一,它不仅提高了代码的复用性和可维护性,还带来了性能上的优化。通过泛型编程,我们可以编写更加通用和灵活的代码,适用于多种类型的场景。在未来的发展中,泛型编程将会得到进一步的增强和完善,为开发者带来更多便利。
19. 泛型编程的性能分析
在实际开发中,性能分析是确保代码高效运行的重要环节。通过合理的性能分析,可以找出代码中的瓶颈并进行优化。
19.1 性能分析工具
Go语言提供了多种性能分析工具,例如 pprof 、 trace 等。通过这些工具,可以对代码进行详细的性能分析。
19.1.1 使用 pprof 进行性能分析
pprof 是Go语言自带的性能分析工具,可以用于分析CPU和内存使用情况。例如:
func main() {
pprof.StartCPUProfile(os.Create("cpu.prof"))
defer pprof.StopCPUProfile()
nums := []int{3, 6, 8, 10, 1, 2, 1}
quickSort(nums)
fmt.Println(nums) // 输出: [1 1 2 3 6 8 10]
}
19.1.2 使用 trace 进行性能分析
trace 是Go语言自带的性能分析工具,可以用于分析程序的执行流程。例如:
func main() {
trace.Start(os.Stdout)
defer trace.Stop()
nums := []int{3, 6, 8, 10, 1, 2, 1}
quickSort(nums)
fmt.Println(nums) // 输出: [1 1 2 3 6 8 10]
}
19.2 性能分析的最佳实践
在泛型编程中,遵循最佳实践可以帮助我们编写更加高效的代码。以下是一些性能分析的最佳实践:
- 使用性能分析工具 :通过使用
pprof、trace等工具,可以对代码进行详细的性能分析。 - 优化算法和数据结构 :通过优化算法和数据结构,可以显著提高代码的性能。
- 减少不必要的内存分配 :通过减少不必要的内存分配,可以显著提高性能。
19.3 泛型编程的性能优化案例
为了更好地理解泛型编程的性能优化,我们来看一个实际案例:优化泛型排序函数的性能。
type Ordered interface {
~int | ~float64 | ~string
}
func optimizedQuickSort[T Ordered](arr []T) {
if len(arr) < 2 {
return
}
pivotIndex := partition(arr)
optimizedQuickSort(arr[:pivotIndex])
optimizedQuickSort(arr[pivotIndex+1:])
}
func partition[T Ordered](arr []T) int {
pivot := arr[0]
i := 1
for j := 1; j < len(arr); j++ {
if arr[j] < pivot {
arr[i], arr[j] = arr[j], arr[i]
i++
}
}
arr[0], arr[i-1] = arr[i-1], arr[0]
return i - 1
}
func main() {
nums := []int{3, 6, 8, 10, 1, 2, 1}
optimizedQuickSort(nums)
fmt.Println(nums) // 输出: [1 1 2 3 6 8 10]
}
在这个例子中, optimizedQuickSort 函数通过减少内存分配和复制操作,显著提高了性能。
20. 泛型编程的未来发展趋势
随着Go语言的发展,泛型编程将会得到进一步的增强和完善。未来的版本可能会引入更多的内置类型约束,支持更复杂的泛型编程场景。此外,Go语言的泛型编程还可能会与反射机制相结合,提供更多灵活性。
20.1 更多内置类型约束
未来的Go语言版本可能会引入更多的内置类型约束,例如 ordered 、 numeric 等,进一步简化泛型编程的使用。
20.2 泛型编程与反射机制结合
泛型编程与反射机制的结合可以提供更多灵活性。例如,通过反射机制可以在运行时动态获取类型信息,结合泛型编程可以实现更加通用的代码。
20.3 泛型编程的扩展
未来的Go语言版本可能会支持更复杂的泛型编程场景,例如泛型接口、泛型方法等,进一步提升代码的复用性和灵活性。
21. 泛型编程的总结与展望
泛型编程是Go语言的重要特性之一,它不仅提高了代码的复用性和可维护性,还带来了性能上的优化。通过泛型编程,我们可以编写更加通用和灵活的代码,适用于多种类型的场景。在未来的发展中,泛型编程将会得到进一步的增强和完善,为开发者带来更多便利。
21.1 泛型编程的总结
- 代码复用 :通过泛型编程,可以编写适用于多种类型的代码,减少重复代码的编写。
- 类型安全 :泛型编程在编译时进行类型检查,确保代码的类型安全性。
- 性能优化 :泛型编程可以在编译时生成针对具体类型的代码,避免运行时的类型检查和转换开销。
21.2 泛型编程的展望
- 更多内置类型约束 :未来的Go语言版本可能会引入更多的内置类型约束,进一步简化泛型编程的使用。
- 泛型编程与反射机制结合 :泛型编程与反射机制的结合可以提供更多灵活性。
- 泛型编程的扩展 :未来的Go语言版本可能会支持更复杂的泛型编程场景,例如泛型接口、泛型方法等,进一步提升代码的复用性和灵活性。
22. 泛型编程的实际案例与应用场景
为了更好地理解泛型编程的应用,我们来看一些实际案例和应用场景。通过这些案例,可以更好地掌握泛型编程的使用方法和技巧。
22.1 泛型排序函数
排序函数是编程中最常见的算法之一。通过泛型编程,我们可以实现一个适用于多种类型的排序函数,提高代码的复用性和可维护性。
22.1.1 泛型快速排序算法
快速排序是一种经典的排序算法,通过泛型编程,我们可以实现一个适用于多种类型的快速排序算法。
type Ordered interface {
~int | ~float64 | ~string
}
func quickSort[T Ordered](arr []T) {
if len(arr) < 2 {
return
}
pivot := arr[0]
left := make([]T, 0)
right := make([]T, 0)
for _, v := range arr[1:] {
if v < pivot {
left = append(left, v)
} else {
right = append(right, v)
}
}
quickSort(left)
quickSort(right)
copy(arr[:len(left)], left)
arr[len(left)] = pivot
copy(arr[len(left)+1:], right)
}
func main() {
nums := []int{3, 6, 8, 10, 1, 2, 1}
quickSort(nums)
fmt.Println(nums) // 输出: [1 1 2 3 6 8 10]
}
22.1.2 泛型冒泡排序算法
冒泡排序是一种简单的排序算法,通过泛型编程,我们可以实现一个适用于多种类型的冒泡排序算法。
type Ordered interface {
~int | ~float64 | ~string
}
func bubbleSort[T Ordered](arr []T) {
n := len(arr)
for i := 0; i < n-1; i++ {
for j := 0; j < n-i-1; j++ {
if arr[j] > arr[j+1] {
arr[j], arr[j+1] = arr[j+1], arr[j]
}
}
}
}
func main() {
nums := []int{3, 6, 8, 10, 1, 2, 1}
bubbleSort(nums)
fmt.Println(nums) // 输出: [1 1 2 3 6 8 10]
}
22.2 泛型队列的实现
队列是一种先进先出(FIFO)的数据结构,支持两种基本操作: Enqueue (入队)和 Dequeue (出队)。使用泛型编程,我们可以实现一个通用的队列结构,适用于任何类型的元素。
type Queue[T any] struct {
elements []T
}
func NewQueue[T any]() *Queue[T] {
return &Queue[T]{elements: make([]T, 0)}
}
func (q *Queue[T]) Enqueue(value T) {
q.elements = append(q.elements, value)
}
func (q *Queue[T]) Dequeue() (T, bool) {
if len(q.elements) == 0 {
var zero T
return zero, false
}
value := q.elements[0]
q.elements = q.elements[1:]
return value, true
}
func main() {
queue := NewQueue[int]()
queue.Enqueue(1)
queue.Enqueue(2)
queue.Enqueue(3)
value, ok := queue.Dequeue()
if ok {
fmt.Println(value) // 输出: 1
}
}
22.3 泛型多映射的实现
多映射是一种特殊的映射类型,允许一个键对应多个值。使用泛型编程,我们可以实现一个适用于多种类型的多映射结构。
type MultiMap[K comparable, V any] struct {
entries map[K][]V
}
func NewMultiMap[K comparable, V any]() *MultiMap[K, V] {
return &MultiMap[K, V]{entries: make(map[K][]V)}
}
func (m *MultiMap[K, V]) Add(key K, value V) {
m.entries[key] = append(m.entries[key], value)
}
func (m *MultiMap[K, V]) Get(key K) []V {
return m.entries[key]
}
func (m *MultiMap[K, V]) Remove(key K, value V) {
values := m.entries[key]
if values == nil {
return
}
for i, v := range values {
if v == value {
m.entries[key] = append(values[:i], values[i+1:]...)
return
}
}
}
func main() {
mm := NewMultiMap[string, int]()
mm.Add("fruits", 1)
mm.Add("fruits", 2)
mm.Add("vegetables", 3)
fmt.Println(mm.Get("fruits")) // 输出: [1 2]
fmt.Println(mm.Get("vegetables")) // 输出: [3]
mm.Remove("fruits", 1)
fmt.Println(mm.Get("fruits")) // 输出: [2]
}
22.4 泛型编程的性能优化
为了提高泛型编程的性能,我们可以对其进行优化。例如,使用并行排序来加速排序过程。
22.4.1 并行快速排序算法
并行快速排序通过多线程并行处理来加速排序过程。实现并行快速排序的关键在于合理划分任务和管理线程池。
type Ordered interface {
~int | ~float64 | ~string
}
func parallelQuickSort[T Ordered](arr []T) {
if len(arr) < 2 {
return
}
pivot := arr[0]
left := make([]T, 0)
right := make([]T, 0)
for _, v := range arr[1:] {
if v < pivot {
left = append(left, v)
} else {
right = append(right, v)
}
}
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
parallelQuickSort(left)
}()
go func() {
defer wg.Done()
parallelQuickSort(right)
}()
wg.Wait()
copy(arr[:len(left)], left)
arr[len(left)] = pivot
copy(arr[len(left)+1:], right)
}
func main() {
nums := []int{3, 6, 8, 10, 1, 2, 1}
parallelQuickSort(nums)
fmt.Println(nums) // 输出: [1 1 2 3 6 8 10]
}
22.5 泛型编程的扩展应用
泛型编程不仅可以用于容器类型和算法实现,还可以用于其他场景,例如泛型接口、泛型方法等。通过泛型编程,我们可以编写更加通用和灵活的代码,提高代码的复用性和可维护性。
22.5.1 泛型接口
泛型接口允许我们定义适用于多种类型的接口。例如:
type Printable interface {
fmt.Stringer
}
type PrintableNumber interface {
Number & Printable
}
func PrintNumber[T PrintableNumber](value T) {
fmt.Println(value)
}
22.5.2 泛型方法
泛型方法允许我们在方法中使用类型参数,进一步提升代码的灵活性。例如:
type Container[T any] struct {
elements []T
}
func (c *Container[T]) Add(element T) {
c.elements = append(c.elements, element)
}
func (c *Container[T]) Remove(index int) T {
if index < 0 || index >= len(c.elements) {
var zero T
return zero
}
element := c.elements[index]
c.elements = append(c.elements[:index], c.elements[index+1:]...)
return element
}
22.6 泛型编程的总结
泛型编程是Go语言的重要特性之一,它不仅提高了代码的复用性和可维护性,还带来了性能上的优化。通过泛型编程,我们可以编写更加通用和灵活的代码,适用于多种类型的场景。在未来的发展中,泛型编程将会得到进一步的增强和完善,为开发者带来更多便利。
23. 泛型编程的实际案例与应用场景(续)
为了更好地理解泛型编程的应用,我们继续来看一些实际案例和应用场景。通过这些案例,可以更好地掌握泛型编程的使用方法和技巧。
23.1 泛型排序函数的实现
排序函数是编程中最常见的算法之一。通过泛型编程,我们可以实现一个适用于多种类型的排序函数,提高代码的复用性和可维护性。
23.1.1 泛型快速排序算法
快速排序是一种经典的排序算法,通过泛型编程,我们可以实现一个适用于多种类型的快速排序算法。
type Ordered interface {
~int | ~float64 | ~string
}
func quickSort[T Ordered](arr []T) {
if len(arr) < 2 {
return
}
pivot := arr[0]
left := make([]T, 0)
right := make([]T, 0)
for _, v := range arr[1:] {
if v < pivot {
left = append(left, v)
} else {
right = append(right, v)
}
}
quickSort(left)
quickSort(right)
copy(arr[:len(left)], left)
arr[len(left)] = pivot
copy(arr[len(left)+1:], right)
}
func main() {
nums := []int{3, 6, 8, 10, 1, 2, 1}
quickSort(nums)
fmt.Println(nums) // 输出: [1 1 2 3 6 8 10]
}
23.1.2 泛型冒泡排序算法
冒泡排序是一种简单的排序算法,通过泛型编程,我们可以实现一个适用于多种类型的冒泡排序算法。
```go
type Ordered interface {
~int | ~float64 | ~string
}
func bubbleSort T Ordered {
n := len(arr)
for i := 0; i < n-1;
超级会员免费看

被折叠的 条评论
为什么被折叠?



