golang学习:泛型

本文详细介绍了Go语言1.18引入的泛型特性,包括函数、结构体、切片、映射、通道、接口中的泛型用法,并探讨了any类型、comparable约束以及Ordered接口的应用。同时,文章提到了新的接口形式,如一般接口、底层约束类型和单一递归继承。通过实例展示了泛型在Go语言中的各种实践和限制。
摘要由CSDN通过智能技术生成

golang从1.18加入了泛型,采取的是[ ]这个符号,仅供自己学习使用,示例代码之中会大量使用腾讯云开发那篇文章的例子

一、泛型的简单使用

包括在函数、方法和结构体等中如何添加泛型

1.函数上泛型的使用

直接框定T的类型

// 定义
func add1[T int | float32 | float64](a, b T) T {
	c := a + b
	return c
}

// 调用,两种调用方式都可以,也就是说golang会推到出泛型的类型
add1(1,2)
add1[int](1,2)

这样写泛型的定义可能过长,因此可以使用使用一个type去定义一下泛型的具体内容,这个interface的用法明显不同于传统的用法,我们后面会讲到

type mixType interface {
	float32 | int | float64
}

func add[T mixType](a, b T) T {
	c := a + b
	return c
}

当然可以定义多个泛型,比如K/V/Z之类的,这个就不写了

2.结构体之中的泛型

// 定义
type Struct1 [T string|int|float64] struct {
  Title string
  Content  T
}

// 实例化
test:= Struct1[string]{
    Title:"aaa"
    Content :"bbb"
}
// 带泛型的结构体的实例化没有类型推导,也就是说必须要具体给出类型

除此之外还有一个匿名结构体的问题,golang之中的结构体可以直接定义并初始化,如下:

test := struct{
		Name string
	}{
		Name: "huangfeng",
	}

这种方式不能带上泛型,也就是如下的写法是没有的

test := struct[T string|int]{
		Name T
	}[string]{
		Name: "huangfeng",
	}

 编辑器会在struct关键词下报错:expected '{', found '[',struct后面不能跟“[”

3.type自定义类型

和ts一样,type关键词可以自定义类型并且还可以在类型上挂载对应的方法

//申明一个泛型切片
type Slice1 [T int|float64|string] []T
 
 
//实例化,并赋值
var MySlice Slice1[int] = []int{1,2,3}
//这里的泛型类型不能自动推导,也需要明确指定出来

4.map中的泛型

map的具体类型我们一般是在make函数之中进行确定,在使用泛型之后,我们可以借助type自定义类型去确定泛型

// 定义
type MyMap[K string | int, V string | int] map[K]V

// 实例化
test := MyMap[string, int]{
		"1": 1,
		"2": 2,
	}
	fmt.Println(test["1"])

这样的实例化方法还是蛮新潮的,感觉都有点不像golang

5.chan中的泛型

type gchan[T any] chan T
实例化
strchan := make(gchan[string])

还是借助了type自定义类型的方法 

6.interface中的泛型

我们后面会提到一种新类型的interface,这里的interface指的我们传统意义上的接口

package main

import "fmt"

type MyInterface[T int | string] interface {
	WriteOne(data T) T
	ReadOne() T
}

type Note struct {
}

func (n Note) WriteOne(one string) string {
	return "hello"
}

func (n Note) ReadOne() string {
	return "small"
}

//   实例化
func main() {
	var one MyInterface[string] = Note{}
	fmt.Println(one.WriteOne("hello"))
	fmt.Println(one.ReadOne())
}

这边的方法的调用是不能直接指定泛型的,它的泛型由接口去控制,看上去方法上的泛型应该比较复杂

7.方法上的泛型

这一部分是腾讯那篇文章上提到的问题,在方法上不能主动指定泛型,这句话可能比较难以理解,但是写出来就懂了

package main

import "fmt"

type Person[T int | string] struct {
	Name T
	Age  T
}

func (p Person[T]) SayName() T {
	return p.Name
}
//不支持的写法
func (p Person[T]) SayName[V](age V) T {
	return p.Name
}

// 实例化
func main() {
	test := Person[string]{
		Name: "huanfeng",
		Age:  "12",
	}
	fmt.Println(test.SayName())

}

这里的SayName作为方法是不能像函数一样在后面再次指定泛型的,我想这是因为方法的泛型应该由interface的泛型和机构体的泛型相影响,个人认为这是一个比较合理的解释。 

二、一些泛型上的细节

1.any类型和调用

any类型本身就是空接口的别名

func add2[T any](a T) {
	fmt.Printf("a的类型是%T\n", a)
}
add2(float64(1)) //any类型会根据传入的参数来进行改变

func add3[T any]() T {
	var a T
	return a
}
fmt.Printf("add3结果的类型是%T\n", add3[float64]())        
//float64 现在这种没有参数的情况就需要指定泛型的类型

但是any类型不是万能的,比如有些操作只能由部分类型进行

func Sum[T any] (a, b T) T {
  return a+b
}
 
 
//报错:
invalid operation: operator + not defined on a (variable of type T constrained by any)

 这里的话两个any类型之间不一定能进行加的操作

2.关于comparable

func compare[T any](arg []T, v T) int {
	for i, num := range arg {
		if num == v {
			return i
		}
	}
	return -1
}
这样的写法是不成立的,因为golang无法识别num和v是否是可以比较的类型
func compare[T comparable](arg []T, v T) int {
	for i, num := range arg {
		if num == v {
			return i
		}
	}
	return -1
}
定义为comparable就是告诉哦golang这个泛型是可以比较的

重点说一下哪些类型是可以比较,哪些是不可以比较

1.这些类型是可以比较的:布尔,整数,浮点,complex,字符串,指针,channel,
interface,可比较类型组成的结构体,可比较类型组成的数组

2.此外,func map和slice是不可比较的,此外以上三者类型组成的结果体和数组也是不可比较的

3.这些比较是==和!=的比较,不涉及大小

3.Orderd

这个类型就涉及到大小比较的例子,比如我们的int和字符串等等,但是这个类型是需要从其他包中引入的,下面是腾讯的例子

//导入constraints包
import (
  "fmt"
  "golang.org/x/exp/constraints"
)
 
 
//T的约束类型是:constraints.Ordered
func Max[T constraints.Ordered](a, b T) T {
  if a > b {
    return a
  } else {
    return b
  }
}

三、新的interface

1.最后再说一说1.18之后interface的变化

原先的interface就是方法的集合(基本接口),看到上面应该知道了还多了一种混合类型的功能(一般接口,我有点搞),看一下例子

下面这个是基本接口,只有方法
type error interface {
  Error() string
}

下面这个是一般接口

type MyError interface {
  int | float64
  Error() string
}
只要带类型,不管有没有方法都叫一般接口

对于下面那个带类型又带接口的泛型,是不能实例化的,至少是现在没有找到实例化的方法(腾讯云那篇文章里是这样的说的,我也没找到对应的方法)

2.一般接口的使用

此处讨论的一般接口指的是没有方法的接口,我们从上面的实践也可以看出了,它是一个取并集的操作,如下

package main

type MyNumber interface {
	int | float32
}

func Add[T MyNumber](a, b T) T {
	return a + b
}

// 实例化
func main() {
	Add[int](1, 2)
	Add[float32](1, 2)
}

当然我们还有一个取交集的操作

package main

type MyNumber interface {
	int | float32
}
type MyNumber1 interface {
	int | float64
}

type MyNumber3 interface {
	MyNumber
	MyNumber1
}

func Add[T MyNumber3](a, b T) T {
	return a + b
}

// 实例化
func main() {
	Add[int](1, 2)
}

MyNumber3这个类型就只能是int了,如果取交集的类型之中没有任何兼容的部分,那么这个泛型传什么都不行

3.底层约束类型

package main

type MyNumber interface {
	~int | ~float32
}

type MyInt int

func Add[T MyNumber](a, b T) T {
	return a + b
}

// 实例化
func main() {
	Add[MyInt](1, 2)
}

在这个MyNumber之中,我们在基础类型前面加上了~,这个蝌蚪尾巴表示只要是底层类型是int或者float32的都可以,比如这里我创建的这个MyInt

4.单一递归继承

抄腾讯的,如下

type Slice1[T bool | float64 | string | int] []T
type Slice2[T bool | float64 | string] Slice1[T]
type Slice3[T bool | int] Slice2[T] 该行出现报错

因为它是单一递归继承的,只会检查它的上一级的取值范围是否覆盖,也就是Slice2类型中有没有int

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Golang 是不支持泛型的,所以你不能使用泛型。但是你可以使用类似于泛型的方法来实现类似的效果。 你可以使用 interface{} 类型来表示任何类型。例如: ``` func Print(a interface{}) { fmt.Println(a) } ``` 这样就可以使用 Print 函数输出任何类型的值了。 你也可以使用反射来实现类似的效果。例如: ``` func Print(a interface{}) { v := reflect.ValueOf(a) fmt.Println(v) } ``` 这样就可以使用 Print 函数输出任何类型的值了。 你也可以使用模板函数来实现类似的效果。例如: ``` func Print(a interface{}) { t := template.Must(template.New("").Parse("{{.}}")) t.Execute(os.Stdout, a) } ``` 这样就可以使用 Print 函数输出任何类型的值了。 希望这对你有帮助。 ### 回答2: 截至2021年1月,Go语言(Golang)尚不具备内建的泛型机制,即没有直接的方法来创建通用的数据结构和算法。然而,Go社区一直在探索如何在缺少泛型的情况下实现通用性。 在没有泛型的情况下,开发者通常采用特定类型的接口(interface{})或类型断言(type assertion)来实现类似泛型的效果。使用接口可以定义通用的函数签名,使函数能够接受多种类型参数。同时,使用类型断言可以在运行时检查参数的实际类型,并进行相应的处理。 此外,Go语言还提供了一些通用的数据结构和算法库,如container包中的List、HashMap等。通过在这些数据结构和算法中使用interface{}类型,并利用类型断言来确保类型的正确性,可以实现相对通用的功能。 Go语言的开发团队也一直在推动泛型的研究和开发。在2022年将发布的Go 1.18版本中,预计将加入泛型的支持。该特性将为开发者提供使用更为简洁和安全的方式来创建通用的数据结构和算法。 总之,目前在Go语言中还没有内建的泛型机制,但可以通过接口和类型断言来实现类似的功能。此外,Go语言的开发者正在努力研发并计划在未来的版本中添加泛型支持。 ### 回答3: 目前(截至2021年),Go语言(Golang)尚不支持原生泛型,这意味着在编写Go代码时,无法直接声明泛型类型或方法。然而,Go社区一直在积极探索和讨论如何实现泛型,有一些可选方案可以用来模拟泛型的使用。 其中,最常见的一种方法是使用接口类型实现泛型。通过创建一个接口类型并在函数签名或结构体中使用该接口类型作为参数或字段,可以实现对各种类型的参数和字段的通用操作。这种方法虽然可以达到类似泛型的效果,但在类型实参和类型断言方面存在一些限制和复杂性。 另一种方法是使用代码生成工具模拟泛型。通过在编译时使用代码生成工具,可以根据不同的类型参数生成特定的代码,并将其插入到源代码中。这种方式可以实现在编译时生成针对不同类型的特定代码,从而实现类似泛型的效果。但是,这种方法需要使用额外的工具和开发流程,并且会增加代码的复杂性。 除了这些方法外,Go社区还在不断探索其他更为原生的泛型实现方式,例如Go2泛型的设计,该设计目前正在积极开发中。Go2泛型通过添加泛型类型和泛型函数的直接支持,将大大简化使用泛型的过程,并提供更好的类型安全和可读性。尽管Go2泛型还未正式发布,但对于在项目中使用泛型的需求,可以关注最新的Go官方进展和社区讨论。 总的来说,目前在Go语言中,尚不支持原生的泛型,但可以使用接口类型或代码生成工具等方式来模拟泛型的使用。未来,随着Go2泛型的正式发布,将会提供更为便捷和直接的方式来使用泛型。环境和项目需求决定了选择合适的方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值