Go 泛型

一、什么是泛型

泛型的英文是Generics,就是函数的参数,或者容器元素的类型,支持更广泛的类型,不再是特定的类型。

下面只搬运一下对泛型的简单介绍

  • 函数和类型声明的语法接受类型参数
  • 可以通过方括号中的类型参数列表来实例化参数化函数和类型
  • 接口类型的语法现在允许嵌入任意类型以及Union和〜T类型元素。这些接口只能用作类型约束。接口现在可以定义一组类型和一组方法
  • 新的预声明标识符any是空接口的别名,可以使用any代替interface{}
  • 新的预声明标识符comparable表示可以使用==或!=做比较的所有类型的一个接口,它可以被用作类型约束
  • 有三个实验性的package在使用泛型.golang.org/x/exp下的所有package都属于试验性质或者被废除的package,不倡议应用。

    golang.org/x/exp/constraints
    golang.org/x/exp/slices
    golang.org/x/exp/maps

在Golang、Java、C++等这类静态语言中,是需要严格定义传入变量的类型的,斌不是随心所欲,例如在golang中:

1
2
3
func Sum(a, b int) int {
return a + b
}

在函数Sum中,不仅要严格定义传入参数a和b的变量类型,而且返回值的类型也需要严格定义,所有你只能传入int类型进行调用:

1
Sum(1, 2) // 3

如果传入其它类型的变量就会报错:

1
2
3
4
fmt.Println(Sum(1.23, 2.54)) 

./main.go:33:18: cannot use 1.23 (untyped float constant) as int value in argument to Sum (truncated)
./main.go:33:24: cannot use 2.54 (untyped float constant) as int value in argument to Sum (truncated)

因此,如果当golang开发者想开发类似实现两个float类型变量相加的功能,只能另写一个函数:

1
2
3
func SumFloat(a, b float) float {
return a + b
}

或者写一个通用的Sum函数使用interface反射来判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func Sum(a, b interface{}) interface{} {
switch a.(type) {
case int:
a1 := a.(int)
b1 := b.(int)
return a1 + b1
case float64:
a1 := a.(float64)
b1 := b.(float64)
return a1 + b1
default:
return nil
}
}

这样的话,不仅重复很多代码,而且类型频繁转换导致不仅性能低效,安全性上也不高。

所以泛型诞生了。

然而泛型是一把双刃剑,在给开发者带来便利的同时,同样会带来编译和效率的问题,因为泛型需要系统去推倒和计算变量的类型的,这在无形中会增加编译的时间和降低运行效率。

二、Golang中的泛型

首先来看一下,在Golang 1.18版本中是如何利用泛型来实现Sum函数的

1
2
3
func Sum[T int|float64](a,b T) T {
return a + b
}

然后再调用一下:

1
2
fmt.Println(Sum[int](1, 2))  //3
fmt.Println(Sum[float64](1.23, 2.54)) //3.77

先不去理解函数中各组件的含义,仅仅看代码就简洁了不少,函数也实现了多个类型的功能。

三、泛型语法

3.1 泛型的语法

MyType[T1 constraint1 | constraint2, T2 constraint3…] …

泛型的语法非常简单, 就类似于上面这样, 其中:

1
2
3
4
MyType 可以是函数名, 结构体名, 类型名…
T1, T2… 是泛型名, 可以随便取
constraint 的意思是约束, 也是泛型中最重要的概念, 接下来会详解constraint
使用 | 可以分隔多个constraint, T满足其中之一即可(如T1可以是constraint1和constraint2中的任何一个)

3.2 Constraint(约束)

约束的意思是限定范围, constraint的作用就是限定范围, 将T限定在某种范围内

而常用的范围, 我们自然会想到的有:

1
2
3
4
5
any(interface{}, 任何类型都能接收, 多方便啊!)
Interger(所有int, 多方便啊, int64 int32…一网打尽)
Float(同上)
comparable(所有可以比较的类型, 我们可以给所有可以比较的类型定制一些方法)

这些约束, 不是被官方定义为内置类型, 就是被涵盖在了constraints包内!!!

下面是builtin.go的部分官方源码:

1
2
3
4
5
6
7
8
9
// any is an alias for interface{} and is equivalent to interface{} in all ways.
type any = interface{}

// comparable is an interface that is implemented by all comparable types
// (booleans, numbers, strings, pointers, channels, interfaces,
// arrays of comparable types, structs whose fields are all comparable types).
// The comparable interface may only be used as a type parameter constraint,
// not as the type of a variable.
type comparable comparable

下面是constraints.go的部分官方源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Integer is a constraint that permits any integer type.
// If future releases of Go add new predeclared integer types,
// this constraint will be modified to include them.
type Integer interface {
Signed | Unsigned
}

// Float is a constraint that permits any floating-point type.
// If future releases of Go add new predeclared floating-point types,
// this constraint will be modified to include them.
type Float interface {
~float32 | ~float64
}
//......

3.3 自定义constraint(约束)

下面是constraints包中的官方源码:

1
2
3
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}

Signed约束就是这样被写出来的, 其中需要我们掌握的点有如下几个:

1
2
3
4
5
6
7
8
使用interface{}就可以自定义约束
使用 | 就可以在该约束中包含不同的类型, 例如int, int8, int64均满足Signed约束
你可能会有疑问, ~是什么??? int我认识, ~int我可不认识呀??? 没关系, 实际上~非常简单, 它的意思就是模糊匹配, 例如:
type MyInt int64
此时 MyInt并不等同于int64类型(Go语言特性)
若我们使用int64来约束MyInt, 则Myint不满足该约束
若我们使用~int64来约束MyInt, 则Myint满足该约束(也就是说, ~int64只要求该类型的底层是int64, 也就是模糊匹配了)
官方为了鲁棒性, 自然把所有的类型前面都加上了~

例如:

1
2
3
type My_constraint_Num interface {
~int64 | ~float64
}

3.3.1 声明一个泛型函数

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
53
54
55
56
57
58
59
60
61
package main

import "fmt"

func printSlice[T int | int64 | float64 | string](data []T) {
for _, v := range data {
fmt.Println(v)
}
}
func printSliceAny[T any](data []T) {
for _, v := range data {
fmt.Println(v)
}
}

//多个泛型参数语法:
func printSliceDemo01[T, M any](data01 []T, data02 []M) {
fmt.Println("printSliceDemo01================")
fmt.Println(data01)
fmt.Println("printSliceDemo01================")
fmt.Println(data02)
}

// []里写一个类型,传入的data01和data02 必须是同一种数据类型
func printSliceDemo02[T any](data01 []T, data02 []T) {
fmt.Println("printSliceDemo02================")
fmt.Println(data01)
fmt.Println("printSliceDemo02================")
fmt.Println(data02)
}

// []里写一个类型,传入的data01和data02 必须是同一种数据类型
func printSliceDemo03[T any](data01, data02 []T) {
fmt.Println("printSliceDemo03================")
fmt.Println(data01)
fmt.Println("printSliceDemo03================")
fmt.Println(data02)
}

func printSliceDemo04[T any, M any](data01 []T, data02 []M) {
fmt.Println("printSliceDemo04================")
fmt.Println(data01)
fmt.Println("printSliceDemo04================")
fmt.Println(data02)
}

func main() {
// 显示类型调用
printSlice[int]([]int{66, 77, 88, 99, 100})
printSlice[float64]([]float64{1.1, 2.2, 5.5})
printSlice[string]([]string{"烤鸡", "烤鸭", "烤鱼", "烤面筋"})

// 省略显示类型调用
printSlice([]int64{55, 44, 33, 22, 11})
printSliceAny([]int64{55, 44, 33, 22, 11})

printSliceDemo01([]int64{55, 44, 33, 22, 11}, []string{"烤鸡", "烤鸭", "烤鱼", "烤面筋"})
printSliceDemo02([]int64{55, 44, 33, 22, 11}, []int64{55, 44, 33, 22, 11})
printSliceDemo03([]int64{55, 44, 33, 22, 11}, []int64{55, 44, 33, 22, 11})
printSliceDemo04([]int64{55, 44, 33, 22, 11}, []string{"烤鸡", "烤鸭", "烤鱼", "烤面筋"})
}

[T any]参数的类型,意思是该函数支持任何T类型; 底层是 type any = interface{}

多个泛型参数语法:

[T, M any]
[T any, M any]
[T any, M comparable]

在调用这个泛型函数的时候

可以显示指定类型参数

如: printSlice[int]([]int{66, 77, 88, 99, 100})

也可以省略显示类型 自动推断类型

printSlice([]int64{55, 44, 33, 22, 11})

3.3.2 声明一个泛型切片

带有类型参数的类型被叫做泛型类型。下面定义一个底层类型为切片类型的新类型 vector。它是可以存储任何类型的的切片。要使用泛型类型,要先对其进行实例化,就是给类型参数指定一个实参。

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package main

import (
"fmt"
"sort"
"strings"

"golang.org/x/exp/constraints"
)

type vector[T any] []T

func printSlice[T any](data []T) {
fmt.Println(data)
}

func main() {
//dome01()
//sortSliceDome()
//ContainsSliceDome()
findFuncDemo()
//filterSliceDome()
//Contains[comparable]([int]{58, 1881},58)
//testMinMax()
}

func dome01() {
v := vector[int]{58, 1881}
printSlice(v)
v2 := vector[string]{"烤鸡", "烤鸭", "烤鱼", "烤面筋"}
printSlice(v2)
v3 := vector[float64]{10.2, 2.5}
printSlice(v3)

var v4 vector[int] = []int{1, 2, 3}
v4[2] = 4
printSlice(v4)

}

func sortSliceDome() {
floatSlice := []float64{2.3, 1.2, 0.2, 51.2}
sortSlice(floatSlice, "asc")
fmt.Println(floatSlice)

stringSlice := []string{"z", "a", "b"}
sortSlice(stringSlice, "asc")
fmt.Println(stringSlice)

intSlice := []int{0, 3, 2, 1, 6}
sortSlice(intSlice, "desc")
fmt.Println(intSlice)
}

// 切片排序 order asc|desc
func sortSlice[T constraints.Ordered](s []T, order string) {
if strings.ToUpper(order) == "ASC" || order == "" {
sort.Slice(s, func(i, j int) bool {
return s[i] < s[j]
})
} else {
sort.Slice(s, func(i, j int) bool {
return s[i] > s[j]
})
}
}

func ContainsSliceDome() {
floatSlice := []float64{2.3, 1.2, 0.2, 51.2}
fmt.Println(ContainsSlice(floatSlice, 2.3))

stringSlice := []string{"z", "a", "b"}
fmt.Println(ContainsSlice(stringSlice, "c"))

intSlice := []int{0, 3, 2, 1, 6}
fmt.Println(ContainsSlice(intSlice, 0))
}

// ContainsSlice 是否包涵
func ContainsSlice[E constraints.Ordered](s []E, v E) bool {
for _, vs := range s {
if v == vs {
return true
}
}
return false
}

func findFuncDemo() {
fmt.Println(FindFunc([]int{1, 2, 3, 4, 5, 6}, 2)) //1
}

//FindFunc 查找元素
//该方法应用于在已知切片中查找给定元素是否存在,若存在则返回元素所在切片下标,不存在则返回 -1。
//支持泛型类型:comparable。即属于相同泛型类型的不同元素之间必须可以比较是否相等。
func FindFunc[T comparable](a []T, v T) int {
for i, e := range a {
if e == v {
return i
}
}
return -1
}

func filterSliceDome() {
websites := []string{"http://foo.com", "https://bar.com", "https://gosamples.dev"}
httpsWebsites := FilterSlice(websites, func(v string) bool {
return !strings.HasPrefix(v, "https://")
})
fmt.Println(httpsWebsites)

httpsWebsites2 := FilterSlice(websites, func(v string) bool {
return strings.HasPrefix(v, "https://")
})
fmt.Println(httpsWebsites2)

numbers := []int{1, 2, 3, 4, 5, 6}
divisibleBy2 := FilterSlice(numbers, func(v int) bool {
return v%2 == 0
})
fmt.Println(divisibleBy2)

//输出:
//[https://bar.com https://gosamples.dev]
//[2 4 6]

}

//FilterSlice 过滤出符合传入方法的数据
func FilterSlice[T any](slice []T, f func(T) bool) []T {
var n []T
for _, e := range slice {
if f(e) {
n = append(n, e)
}
}
return n
}


func testMinMax() {
vi := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
result := Max(vi)
fmt.Println(result)

vi = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
result = Min(vi)
fmt.Println(result)

//输出
//10
//1
}

type minmax interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64
}

func Max[T minmax](a []T) T {
m := a[0]
for _, v := range a {
if m < v {
m = v
}
}
return m
}

func Min[T minmax](a []T) T {
m := a[0]
for _, v := range a {
if m > v {
m = v
}
}
return m
}

golang.org/x/exp
文档地址:https://pkg.go.dev/golang.org/x/exp

注意:
不建议使用这个包:
详细参考下面文档:
关于golang: 重磅Go-118将移除用于泛型的constraints包

golang.org/x下所有package的源码独立于Go源码的骨干分支,也不在Go的二进制安装包里。如果须要应用golang.org/x下的package,能够应用go get来装置。
golang.org/x/exp下的所有package都属于试验性质或者被废除的package,不倡议应用。
因为泛型的存在,相同的功能对于不同类型的slice可以少写一份代码,如果想使用slice泛型的相关操作,建议复制golang.org/x/exp中的函数进行使用或修改

constraints包里定义了Signed,Unsigned, Integer, Float, Complex和Ordered共6个interface类型,能够用于泛型里的类型束缚(type constraint)。

官方也引入了一些官方包来方面泛型的使用,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// constraints 定义了一组与类型参数一起使用的约束
package constraints

// Signed是允许任何有符号整数类型的约束。
type Signed interface { ... }

// Unsigned是允许任何无符号整数类型的约束。
type Unsigned interface { ... }

// Integer是允许任何整数类型的约束。
type Integer interface { ... }

// Float是一个允许任何浮点类型的约束。
type Float interface { ... }

// Complex是允许任何复杂数值类型的约束。
type Complex interface { ... }

// Ordered是一个约束,允许任何有序类型:任何支持操作符< <= >= >的类型。
type Ordered interface { ... }

使用方式示例如下:

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package main

import (
"fmt"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
"sort"
)

func main() {
EqualDome()
//ContainsDome()
//ContainsFuncDome()
//InsertDome()
}

// EqualDome 是否等于
func EqualDome() {
var m1 = map[int]int{1: 2, 2: 4, 4: 8, 8: 16}
wantKeys := []int{1, 2, 4, 8}
gotKeys := maps.Keys(m1)
sort.Ints(gotKeys)
// gotKeys 是否等于 wantKeys
fmt.Println(slices.Equal(gotKeys, wantKeys))

var m2 = map[int]string{1: "a", 2: "b", 4: "c", 8: "d"}
wantValsAsc := []string{"a", "b", "c", "d"}
wantValsDesc := []string{"d", "c", "b", "a"}

gotVals := maps.Values(m2)

sort.Strings(gotVals) // 升序
// gotKeys 是否等于 wantKeys
fmt.Println(slices.Equal(gotVals, wantValsAsc))

sort.Sort(sort.Reverse(sort.StringSlice(gotVals))) //降序
// gotKeys 是否等于 wantValsDesc
fmt.Println(slices.Equal(gotVals, wantValsDesc))

// 打印结果
//true
//true
//true

}

func ContainsDome() {
floatSlice := []float64{2.3, 1.2, 0.2, 51.2}
// floatSlice 是否包涵 2.3
fmt.Println(slices.Contains(floatSlice, 2.3))

stringSlice := []string{"z", "a", "b"}
fmt.Println(slices.Contains(stringSlice, "c"))

intSlice := []int{0, 3, 2, 1, 6}
fmt.Println(slices.Contains(intSlice, 0))

// 打印结果
//true
//false
//true

}

func ContainsFuncDome() {
floatSlice := []float64{2.3, 1.2, 0.2, 51.2}
// floatSlice 是否包涵 > 1.0
fmt.Println(slices.ContainsFunc(floatSlice, func(v float64) bool {
return v > 1.0
}))

stringSlice := []string{"z", "a", "b"}
// stringSlice 是否包涵 == "c"
fmt.Println(slices.ContainsFunc(stringSlice, func(v string) bool {
return v == "c"
}))

intSlice := []int{0, 3, 2, 1, 6}
// intSlice 是否包涵 v%2 == 0
fmt.Println(slices.ContainsFunc(intSlice, func(v int) bool {
return v%2 == 0
}))
// 打印结果
//true
//false
//true

}

func InsertDome() {
i := []int{1, 2, 3}
// i中第1个角标位置插入4 5
gotInt := slices.Insert(i, 1, []int{4, 5}...)
fmt.Println(gotInt)

// f中第1个角标位置插入4.1, 5.6
f := []float64{1.2, 2.2, 3.3}
gotFloat64 := slices.Insert(f, 1, []float64{4.1, 5.6}...)
fmt.Println(gotFloat64)

s := []string{"a", "b", "c"}
// s中第1个角标位置插入"e", "f"
gotString := slices.Insert(s, 1, []string{"e", "f"}...)
fmt.Println(gotString)

// 打印结果
//[1 4 5 2 3]
//[1.2 4.1 5.6 2.2 3.3]
//[a e f b c]
}

更多介绍:
Go1.18 新特性–泛型
javascript:void(0)

3.3.3 声明一个泛型map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func main() {
testDemo01()
}

func testDemo01() {
type M[K string, V any] map[K]V //这里的K不支持any,由于底层map不支持,所以使用string
m1 := M[string, int]{"key": 1}
m1["key"] = 2

m2 := M[string, string]{"key": "value"}
m2["key"] = "new value"
fmt.Println(m1, m2)
//打印
//map[key:2] map[key:new value]
}

3.3.4 声明一个泛型通道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

type C[T any] chan T

func main() {
c1 := make(C[int], 10)
c1 <- 1
c1 <- 2

c2 := make(C[string], 10)
c2 <- "hello"
c2 <- "world"

fmt.Println(<-c1, <-c2)

//打印
//1 hello
}

3.3.5 声明一个泛型struct

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
package main

import (
"fmt"
"golang.org/x/exp/constraints"
)


type Vector[T constraints.Ordered] struct {
x, y T
}

func (v *Vector[T]) Add(x, y T) {
v.x += x
v.y += y
}

func (v *Vector[T]) String() string {
return fmt.Sprintf("{x: %v, y: %v}", v.x, v.y)
}

func NewVector[T constraints.Ordered](x, y T) *Vector[T] {
return &Vector[T]{x: x, y: y}
}

func main() {
v := NewVector[float64](1, 2)
v.Add(2, 3)
fmt.Println(v)

v2 := NewVector[string]("a", "b")
v2.Add("1", "2")
fmt.Println(v2)

//打印:
//{x: 3, y: 5}
//{x: a1, y: b2}
}

3.3.6 泛型约束

3.3.6.1 使用interface中规定的类型约束泛型函数的参数

NumStr,新增了类型列表表达式,它是对类型参数进行约束。
使用 | 表示取并集
如果传入参数不在集合限制范围内,就会报错。

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
package main

import "fmt"

type NumStr interface {
Num | Str
}
type Num interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~complex64 | ~complex128
}
type Str interface {
~string
}

func add[T NumStr](a, b T) T {
return a + b
}

//使用interface中规定的类型约束泛型函数的参数
func main() {
fmt.Println(add(3, 4))
fmt.Println(add("hello", "world"))

//打印
//7
//helloworld
}

3.3.6.2 使用interface中规定的方法来约束泛型的参数

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
package main

import (
"fmt"
"strconv"
)

type ShowPrice interface {
String() string
}

type Price int

func (i Price) String() string {
return strconv.Itoa(int(i))
}

type Price2 string

func (i Price2) String() string {
return string(i)
}

func ShowPriceList[T ShowPrice](s []T) (ret []string) {
for _, v := range s {
ret = append(ret, v.String())
}
return
}

//使用interface中规定的方法来约束泛型的参数
func main() {
fmt.Printf("%T %+v \n", ShowPriceList([]Price{1, 2}), ShowPriceList([]Price{1, 2}))
fmt.Printf("%T %+v \n", ShowPriceList([]Price2{"a", "b"}), ShowPriceList([]Price2{"a", "b"}))

//打印
//[]string [1 2]
//[]string [a b]
}

3.3.6.3 使用interface中规定的方法和类型来双重约束泛型的参数

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"
"strconv"
)

type ShowPrice interface {
String() string
int | string
}
type Price int

func (i Price) String() string {
return strconv.Itoa(int(i))
}

func ShowPriceList[T ShowPrice](s []T) (ret []string) {
for _, v := range s {
ret = append(ret, v.String())
}
return
}

//使用interface中规定的方法和类型来双重约束泛型的参数
func main() {
fmt.Printf("%T %+v", ShowPriceList([]Price{1, 2}), ShowPriceList([]Price{1, 2}))

}

//传入浮点参数,就会因为不是约束类型而报错
// .\main.go:27:36: Price does not implement ShowPrice (possibly missing ~ for int in constraint ShowPrice)

3.3.6.4 使用泛型自带comparable约束,判断比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
)

func findFunc[T comparable](a []T, v T) int {
for i, e := range a {
if e == v {
return i
}
}
return -1
}

func main() {
fmt.Println(findFunc([]int{1, 2, 3, 4, 5, 6}, 5))
fmt.Println(findFunc([]string{"烤鸡", "烤鸭", "烤鱼", "烤面筋"}, "烤面筋"))
// 打印
// 4
// 3
}

参考文档:
http://www.golang.ren/article/193584

https://cdn.modb.pro/db/528594

-------------本文结束感谢您的阅读-------------