脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - Go 泛型和非泛型代码详解

Go 泛型和非泛型代码详解

2021-11-18 10:23crazstom Golang

Go 在 1.17 中支持泛型,但是默认未开启;1.18 中会正式支持泛型,下面文章内容小编将给大家讲解Go 语言中的泛型和非泛型并且附上代码详解,刚兴趣的小伙伴请参考下面文章的具体内容

1. 开启泛型

在 Go1.17 版本中,可以通过:

?
1
export GOFLAGS="-gcflags=-G=3"

或者在编译运行程序时加上:

?
1
go run -gcflags=-G=3 main.go

2.无泛型代码和泛型代码

2.1. AddSlice

首先看现在没有泛型的代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main
 
 import (
   "fmt"
 )
 
 func AddIntSlice(input []int, diff int) []int {
   output := make([]int, 0, len(input))
   for _, item := range input {
     output = append(output, item+diff)
   }
   return output
 }
 
 func AddStrSlice(input []string, diff string) []string {
   output := make([]string, 0, len(input))
   for _, item := range input {
     output = append(output, item+diff)
   }
   return output
 }
 
 func main() {
   intSlice := []int{1, 2, 3, 4, 5, 6}
   fmt.Printf("intSlice [%+v] + 2 = [%+v]\n", intSlice, AddIntSlice(intSlice, 2))
 
   strSlice := []string{"hi,", "hello,", "bye,"}
   fmt.Printf("strSlice [%+v] + man = [%+v]\n", strSlice, AddStrSlice(strSlice, "man"))
 }
 //output
 //intSlice [[1 2 3 4 5 6]] + 2 = [[3 4 5 6 7 8]]
 //strSlice [[hi, hello, bye,]] + man = [[hi,man hello,man bye,man]]

上面没有使用泛型的代码中,对 intSlice strSlice,需要构造两个函数对它们进行处理;而如果后续还有 float64uint32 等类型就需要更多地 Add...Slice 函数。

而如果使用泛型之后,这些 Add...Slice 函数就可以合并为一个函数了,在这个函数中,对那些可以使用 + 操作符的类型进行加操作(无论是数学的加还是字符串的连接)。

泛型代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main
import (
  "fmt"
)
type PlusConstraint interface {
  type int, string
}
func AddSlice[T PlusConstraint](input []T, diff T) []T {
  output := make([]T, 0, len(input))
  for _, item := range input {
    output = append(output, item+diff)
  }
  return output
}
func main() {
  intSlice := []int{1, 2, 3, 4, 5}
  fmt.Printf("intSlice [%+v] + 2 = [%v]\n", intSlice, AddSlice(intSlice, 2))
  strSlice := []string{"hi,", "hello,", "bye,"}
  fmt.Printf("strSlice [%v] + man = [%v]\n", strSlice, AddSlice(strSlice, "man"))
}
//output
//intSlice [[1 2 3 4 5]] + 2 = [[3 4 5 6 7]]
//strSlice [[hi, hello, bye,]] + man = [[hi,man hello,man bye,man]]

是不是超级简单,但是 AddSlice 函数中引入了约束的概念,即 PlusConstraintAddSlice 的方括号中是类型参数,T 就是这个类型参数的形参,后面的 PlusConstraint 就是 T 的约束条件,意思是只有满足约束条件的 T 类型才可以在这个函数中使用。

AddSlice 后面圆括号中的参数是常规参数也称为非类型参数,它们可以不制定具体类型(int、string 等),可以使用 T 来代替。

而在 AddSlice 中,对于 T 类型的值 item,它会将 item 和 diff 进行 + 操作,可能是数学上的累加,也可能是字符串的连接。

那现在你可能要问了,T 类型就一定是支持 + 操作符的吗,有没有可能是一个 struct 呢?

答案是:不可能。

前面说过,只有满足约束条件的 T 才可以在 AddSlice 中使用,而约束条件就是上面的 PlusConstraint

PlusConstraint 定义的方式和接口类型的定义是一样的,只不过内部多了一行:

?
1
type int, string

这句话就是说,只有 intstring 这两个类型才满足这个约束,这里涉及到类型集的概念,后续会提到。

因此,有了这个约束条件,传入到 AddSlice 的参数 input diff 都是可以使用 + 操作符的。如果你的 AddSlice 函数中想传入 float46uint64 等类型,就在 PlusConstraint 中加上这两个类型即可。

上面的代码中,只是对 int 和 string 两种基础类型进行约束。实际开发中,我们可能会定义自己的类型:

?
1
2
type MyInt int
type MyStr string

那如果在 AddSlice 中使用这两种类型可以编译通过吗?答案是可以的。在泛型草案中,这种情况是无法编译通过的,需要在约束条件中添加~int | ~string,表示底层类型是 int 或 string 的类型。而在 Go1.17 中,上面的 PlusConstraint 就包括了 intstring、以及以这两者为底层类型的类型。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main
import (
  "fmt"
)
type MyInt int
type MyStr string
type PlusConstraint interface {
  type int, string
}
func AddSlice[T PlusConstraint](input []T, diff T) []T {
  output := make([]T, 0, len(input))
  for _, item := range input {
    output = append(output, item+diff)
  }
  return output
}
func main() {
  intSlice := []MyInt{1, 2, 3, 4, 5}
  fmt.Printf("intSlice [%+v] + 2 = [%v]\n", intSlice, AddSlice(intSlice, 2))
  strSlice := []MyStr{"hi,", "hello,", "bye,"}
  fmt.Printf("strSlice [%v] + man = [%v]\n", strSlice, AddSlice(strSlice, "man"))
}
//output
//intSlice [[1 2 3 4 5]] + 2 = [[3 4 5 6 7]]
//strSlice [[hi, hello, bye,]] + man = [[hi,man hello,man bye,man]]

2.2. 带方法的约束 StringConstraint

前面说到,约束的定义和接口很像,那如果约束中有方法呢,那不就是妥妥的接口吗?

两者还是有区别的:

  • 接口的成员只有方法和内嵌的接口类型
  • 约束的成员有方法、内嵌约束类型、类型(int、string等)
  •  

看下面一个没有使用泛型的例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package main
import (
  "fmt"
)
func ConvertSliceToStrSlice(input []fmt.Stringer) []string {
  output := make([]string, 0, len(input))
  for _, item := range input {
    output = append(output, item.String())
  }
  return output
}
type MyInt int
func (mi MyInt) String() string {
  return fmt.Sprintf("[%d]th", mi)
}
func ConvertIntSliceToStrSlice(input []MyInt) []string {
  output := make([]string, 0, len(input))
  for _, item := range input {
    output = append(output, item.String())
  }
  return output
}
type MyStr string
func (ms MyStr) String() string {
  return string(ms) + "!!!"
}
func ConvertStrSliceToStrSlice(input []MyStr) []string {
  output := make([]string, 0, len(input))
  for _, item := range input {
    output = append(output, item.String())
  }
  return output
}
func main() {
  intSlice := []MyInt{1, 2, 3, 4}
  // compile error, []MyInt not match []fmt.Stringer
  //fmt.Printf("%v convert %v", intSlice, ConvertSliceToStrSlice(intSlice))
  fmt.Printf("%v convertIntToStr %v \n", intSlice, ConvertIntSliceToStrSlice(intSlice))
  strSlice := []MyStr{"111", "222", "333"}
  fmt.Printf("%v convertStrToStr %v \n", strSlice, ConvertStrSliceToStrSlice(strSlice))
  // output
  //[[1]th [2]th [3]th [4]th] convertIntToStr [[1]th [2]th [3]th [4]th]
  //[111!!! 222!!! 333!!!] convertStrToStr [111!!! 222!!! 333!!!]
}

上面代码中,MyInt MyStr 都实现了 fmt.Stringer 接口,但是两个都无法调用 ConvertSliceToStrSlice 函数,因为它的入参是 []fmt.Stringer 类型,[]MyInt 和它不匹配,这在编译的时候就是会报错的,而如果我们想要把[]MyInt 转换为 []string,就需要定义一个入参为[]MyInt 的函数,如 ConvertIntSliceToStrSlice;对于 []MyStr,则需要另一个函数。。。那明明两者都实现了 fmt.Stringer,理论上应该都可以通过 ConvertSliceToStrSlice 啊,这也太反人类了。

哈哈,泛型实现了这个功能。

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main
 
 import (
   "fmt"
 )
 
 type StringConstraint interface {
   String() string
 }
 
 func ConvertSliceToStrSlice[T StringConstraint](input []T) []string {
   output := make([]string, 0, len(input))
   for _, item := range input {
     output = append(output, item.String())
   }
   return output
 }
 
 type MyInt int
 
 func (mi MyInt) String() string {
   return fmt.Sprintf("[%d]th", mi)
 }
 
 type MyStr string
 
 func (ms MyStr) String() string {
   return string(ms) + "!!!"
 }
 func main() {
   intSlice := []MyInt{1, 2, 3, 4}
   // compile error, []MyInt not match []fmt.Stringer
   fmt.Printf("%v convert %v\n", intSlice, ConvertSliceToStrSlice(intSlice))
 
 
   strSlice := []MyStr{"111", "222", "333"}
   fmt.Printf("%v convert %v\n", strSlice, ConvertSliceToStrSlice(strSlice))
   // output
   //[[1]th [2]th [3]th [4]th] convert [[1]th [2]th [3]th [4]th]
   //[111!!! 222!!! 333!!!] convert [111!!! 222!!! 333!!!]
 }

简单吧,在 StringConstraint 约束中定义一个 String() string,这样只要有这个方法的类型都可以作为 T 在 ConvertSliceToStrSlice 使用。在这个约束条件下,所有具有 String() string 方法的类型都可以进行转换,但是我们如果想把约束条件定的更加苛刻,例如只有底层类型为 int 或者 string 的类型才可以调用这个函数。 那么我们可以进一步在 StringConstraint 中添加约束条件:

?
1
2
3
4
type StringConstraint interface {
  type int, string
  String() string
}

这样满足这个约束的类型集合就是底层类型是 int 或者 string,并且,具有 String() string 方法的类型。而这个类型集合就是 type int, string 的类型集合与 String() string 的类型集合的交集。具体的概念后续介绍。

这样,MyFloatMyUint 就无法调用 ConvertSliceToStrSlice 这个函数了。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package main
import (
  "fmt"
)
type StringConstraint interface {
  type int, string
  String() string
}
func ConvertSliceToStrSlice[T StringConstraint](input []T) []string {
  output := make([]string, 0, len(input))
  for _, item := range input {
    output = append(output, item.String())
  }
  return output
}
type MyFloat float64
func (mf MyFloat) String() string {
  return fmt.Sprintf("%fth", mf)
}
type MyInt int
func (mi MyInt) String() string {
  return fmt.Sprintf("[%d]th", mi)
}
type MyStr string
func (ms MyStr) String() string {
  return string(ms) + "!!!"
}
func main() {
  intSlice := []MyInt{1, 2, 3, 4}
  // compile error, []MyInt not match []fmt.Stringer
  fmt.Printf("%v convert %v\n", intSlice, ConvertSliceToStrSlice(intSlice))
  strSlice := []MyStr{"111", "222", "333"}
  fmt.Printf("%v convert %v\n", strSlice, ConvertSliceToStrSlice(strSlice))
  // output
  //[[1]th [2]th [3]th [4]th] convert [[1]th [2]th [3]th [4]th]
  //[111!!! 222!!! 333!!!] convert [111!!! 222!!! 333!!!]
  floatSlice := []MyFloat{1.1, 2.2, 3.3}
  //type checking failed for main
  //prog.go2:48:44: MyFloat does not satisfy StringConstraint (MyFloat or float64 not found in int, string)
  fmt.Printf("%v convert %v\n", floatSlice, ConvertSliceToStrSlice(floatSlice))
}

小结:

总的来说,泛型可以简化代码的编写,同时在编译时进行类型检查,如果类型不满足约束,就会在编译时报错;这样就避免了运行时不可控的错误了。

到此这篇关于Go 泛型和非泛型代码详解的文章就介绍到这了,更多相关Go 泛型和非泛型代码内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://zhuanlan.zhihu.com/p/414728272

延伸 · 阅读

精彩推荐
  • Golanggolang 通过ssh代理连接mysql的操作

    golang 通过ssh代理连接mysql的操作

    这篇文章主要介绍了golang 通过ssh代理连接mysql的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    a165861639710342021-03-08
  • GolangGolang通脉之数据类型详情

    Golang通脉之数据类型详情

    这篇文章主要介绍了Golang通脉之数据类型,在编程语言中标识符就是定义的具有某种意义的词,比如变量名、常量名、函数名等等,Go语言中标识符允许由...

    4272021-11-24
  • Golanggolang的httpserver优雅重启方法详解

    golang的httpserver优雅重启方法详解

    这篇文章主要给大家介绍了关于golang的httpserver优雅重启的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,...

    helight2992020-05-14
  • Golanggolang如何使用struct的tag属性的详细介绍

    golang如何使用struct的tag属性的详细介绍

    这篇文章主要介绍了golang如何使用struct的tag属性的详细介绍,从例子说起,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看...

    Go语言中文网11352020-05-21
  • Golanggo语言制作端口扫描器

    go语言制作端口扫描器

    本文给大家分享的是使用go语言编写的TCP端口扫描器,可以选择IP范围,扫描的端口,以及多线程,有需要的小伙伴可以参考下。 ...

    脚本之家3642020-04-25
  • GolangGolang中Bit数组的实现方式

    Golang中Bit数组的实现方式

    这篇文章主要介绍了Golang中Bit数组的实现方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    天易独尊11682021-06-09
  • Golanggo日志系统logrus显示文件和行号的操作

    go日志系统logrus显示文件和行号的操作

    这篇文章主要介绍了go日志系统logrus显示文件和行号的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    SmallQinYan12302021-02-02
  • Golanggolang json.Marshal 特殊html字符被转义的解决方法

    golang json.Marshal 特殊html字符被转义的解决方法

    今天小编就为大家分享一篇golang json.Marshal 特殊html字符被转义的解决方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧 ...

    李浩的life12792020-05-27