golang-泛型基础篇(二)

前言

在上一篇文章中介绍了 golang中泛型的定义,泛型结构体,部分错误的泛型写法。

type MyStruct[T int | string] struct {
    Data     []T
}

注意:匿名结构体不支持泛型
原因(个人理解):定义泛型,相当于定义新的类型,然后对其进行初始化。使用匿名结构体时,定义好匿名结构体之后直接初始化,相当于每次定义一个新的结构体类型进行初始化。不太符合泛型的要求。

泛型receiver

定义了新的普通类型之后可以给类型添加方法。那么可以给泛型类型添加方法吗?答案是可以的,如下:

type MySlice[T int | float32] []T

func (s MySlice[T]) Sum() T {
	var sum T
	for _, value := range s {
		sum += value
	}
	return sum
}

func TestSlice(t *testing.T) {
	var mySlice = MySlice[int]{}
	mySlice = append(mySlice, []int{1, 2, 3, 4, 5, 6, 7, 8, 9}...)
	fmt.Println(mySlice.Sum())
}

泛型receiver的实用性。在没有泛型之前如果想实现通用的数据结构,诸如:堆、栈、队列、链表之类的话,我们的选择只有两个:

  • 为每种类型写一个实现(每个类型写一次,代码量大)
  • 使用 接口+反射(反射效率低)

基于泛型的队列

//基于泛型的队列
type MyQueue[T interface{}] struct {
	elements []T
}

func (q *MyQueue[T]) Push(value T) {
	q.elements = append(q.elements, value)
}

func (q *MyQueue[T]) Pop() (value T, isEmtry bool) {
	if len(q.elements) == 0 {
		return value, true
	}
	value = q.elements[0]
	q.elements = q.elements[1:]
	return value, len(q.elements) == 0
}

// 队列大小
func (q MyQueue[T]) Size() int {
	return len(q.elements)
}

基于泛型的栈

//基于泛型的栈
type MyStack[T interface{}] struct {
	elements []T
}

func (q *MyStack[T]) Push(value T) {
	q.elements = append(q.elements, value)
}

func (q *MyStack[T]) Pull() (value T, isEmtry bool) {
	if len(q.elements) == 0 {
		return value, true
	}
	value = q.elements[len(q.elements)-1]
	q.elements = q.elements[:len(q.elements)-1]
	return value, len(q.elements) == 0
}

// 队列大小
func (q MyStack[T]) Size() int {
	return len(q.elements)
}

泛型不能进行类型断言

对于interface类型的参数,可以对其进行类型断言,然后不同类型做出不同处理呢。对于T这样通过类型形参定义的变量,我们不能判断具体类型

func (q *Queue[T]) Put(value T) {
    value.(int) // 错误。泛型类型定义的变量不能使用类型断言

    // 错误。不允许使用type switch 来判断 value 的具体类型
    switch value.(type) {
    case int:
        // do something
    case string:
        // do something
    default:
        // do something
    }
}

虽然泛型类型断言一起不能用,但我们可通过反射机制达到目的:

func (receiver Queue[T]) Put(value T) {
    // Printf() 可输出变量value的类型(底层就是通过反射实现的)
    fmt.Printf("%T", value) 

    // 通过反射可以动态获得变量value的类型从而分情况处理
    v := reflect.ValueOf(value)

    switch v.Kind() {
    case reflect.Int:
        // do something
    case reflect.String:
        // do something
    }
}

当你写出上面这样的代码时候就出现了一个问题:
你为了避免使用反射而选择了泛型,结果到头来又为了一些功能在在泛型中使用反射。当出现这种情况的时候你可能需要重新思考一下,自己的需求是不是真的需要用泛型。

泛型函数

泛型函数在第一篇介绍过写法

func Add[T int | float32 | float64](a T, b T) T {
    return a + b
}

func TestADD(t *testing.T) {

	//写法1
    fmt.Println(Add[](10, 20)
    //写法2
	fmt.Println(Add[int](100, 200))
	fmt.Println(Add[float64](10.5, 24.6))
}

和泛型类型一样,泛型函数也是不能直接调用的,要使用泛型函数的话必须传入类型实参之后才能调用。
匿名函数不支持泛型:匿名函数不能自己定义类型形参。

// 错误,匿名函数不能自己定义类型实参
fnGeneric := func[T int | float32](a, b T) T {
        return a + b
} 

fmt.Println(fnGeneric(1, 2))

泛型方法

go里面不止有函数还有方法。有泛型函数,有没有泛型方法呢?结果:目前Go的方法并不支持泛型。但是因为receiver支持泛型, 所以如果想在方法中使用泛型的话,我们可以这样。

type A[T int | float32 | float64] struct {
}

// 方法可以使用类型定义中的形参 T 
func (receiver A[T]) Add(a T, b T) T {
    return a + b
}

// 用法:
var a A[int]
a.Add(1, 2)

var aa A[float32]
aa.Add(1.0, 2.0)

个人理解:函数主要针对传参进行操作,方法可以针对receiver进行操作。如果传入一个泛型的参数赋值给一个非泛型receiver的属性,可能会报错( 如:int value in variable)。所以不支持泛型方法。

总结

Go的泛型(或者或类型形参)目前可使用在3个地方

  • 泛型类型 - 类型定义中带类型形参的类型
  • 泛型receiver - 泛型类型的receiver
  • 泛型函数 - 带类型形参的函数
  • 没有匿名的泛型类型(匿名结构体,匿名方法)
  • 没有泛型的方法,但是泛型方法可以通过泛型receiver+泛型参数来实现。
  • 泛型类型不能直接使用,要使用的话必须传入类型实参进行实例化
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Golang自V1.18版本开始正式支持泛型泛型Golang中可用于变量、函数、方法、接口、通道等多个语法实体。泛型的使用方法可以分为以下几个方面: 1. 泛型变量类型:泛型变量类型是针对类型变量的,可以用于声明包含不同类型的变量。例如,可以声明一个泛型切片变量,使其可以存储不同类型的元素。 2. 自定义泛型类型:在Golang中,可以通过声明自定义的泛型类型来扩展泛型的使用。这意味着可以定义适用于不同类型的自定义数据结构。 3. 调用带泛型的函数:通过使用泛型类型作为函数参数或返回值,可以调用带有泛型的函数。这样可以实现在不同类型上通用的函数逻辑。 4. 泛型与结构体:泛型可以与结构体一同使用,结构体中的字段和方法可以是泛型类型,从而实现更灵活和通用的数据结构和操作。 然而,需要注意的是,Golang泛型还存在一些限制和缺陷。例如,无法直接与switch语句配合使用。这是因为在泛型中无法判断具体的类型,而switch语句需要根据具体类型进行分支判断。 总的来说,Golang泛型提供了一种通用的类型机制,使得代码更具灵活性和可复用性。但是需要熟悉泛型的语法和使用限制,以避免在实际使用中遇到问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [全面解读!Golang泛型的使用](https://blog.csdn.net/QcloudCommunity/article/details/125401490)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [go泛型使用方法](https://blog.csdn.net/qq_42062052/article/details/123840525)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值