go语言入门之接口(附泛型详解)
1.概念
接口(interface)
是一种类型,它是描述了一组方法
的集合
type Animal interface {
// Say 动物可以说话
Say()
// Move 动物可以移动
Move()
// Jump 动物可以跳起来
Jump()
}
func main() {
var animal Animal
// 这里打印的结果为nil,接口的零值为nil
// 也就是说我们想要使用接口,必须对齐初始化
fmt.Println(animal)
}
接口的方法并没有实现,接口只是一组抽象行为的定义,并不会和特定的实现细节所绑定
2.接口类型
接口在go1.18后被分为了两种类型
- 基本接口:只有方法的接口,用法与1.18版本之前一致,当然也可以用作类型约束
- 一般接口:不只有方法,还有类型的接口
基本接口
type MyError interface { // 接口中只有方法,所以是基本接口
Error() string
}
// 用法和 Go1.18之前保持一致
var err MyError = fmt.Errorf("hello world")
一般接口
type Uint interface { // 接口 Uint 中有类型,所以是一般接口
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}
type ReadWriter interface { // ReadWriter 接口既有方法也有类型,所以是一般接口
~string | ~[]rune
Read(p []byte) (n int, err error)
Write(p []byte) (n int, err error)
}
注意:一般接口类型不能用来定义变量,只能用于泛型的类型约束中
3.接口实现
需知:
- 接口实现规则:实现接口必须实现接口的所有方法,并且方法的名称,参数,返回值类型都相同
- 接口可以嵌套
type Animal interface {
//Say 动物可以说话
Say()
//Move 动物可以移动
Move()
//Jump 动物可以跳起来
Jump()
}
type Dog struct {
}
func (d *Dog) Move() {
fmt.Println("狗在跑")
}
func (d *Dog) Jump() {
fmt.Println("狗在跳")
}
func (d *Dog) Say() {
fmt.Println("汪汪汪")
}
type Cat struct {
}
func (c *Cat) Say() {
fmt.Println("喵喵喵")
}
func main() {
// 这里Dog实现了Animal接口,Cat没有实现
var dog Animal = &Dog{}
dog.Say()
}
4.空接口
需知:
- 空接口是没有方法的接口
- 因此任何类型都实现了
空接口
- 空接口类型的变量可以存储任意类型的变量
type Any interface{}
func main() {
var a Any
a = 10
fmt.Println(a)
}
// 为了方便空接口的使用,所以go提供了一个类型别名any
// 所以以后使用不需要定义空接口,可以直接用
func main() {
var a any
a = "张三"
fmt.Println(a)
}
5.类型断言
可以判断具体数据类型
语法为:x.(T)
- x:表示类型为interface{}的变量
- T:表示断言x可能是的类型
func main() {
var a interface{}
a = "张三"
justifyType(j)
}
// 输出结果:未知类型为:string,内容为: 张三
// 断言判断类型
func justifyType(x interface{}) {
switch v := x.(type) {
case string:
fmt.Printf("类型为:string,内容为: %v\n", v)
case int:
fmt.Printf("类型为:int,内容为: %v\n", v)
case bool:
fmt.Printf("类型为:bool,内容为: %v\n", v)
default:
fmt.Println("未定义类型!")
}
}
6.泛型
泛型这块我是基于其他文章做的自己的总结,想要了解更多请参考以下链接
泛型是一种编程语言的特性,允许在编写代码时不指定具体的数据类型,而在运行时动态确定。
泛型是
go1.18
新增的功能
(1)为什么用泛型
当我们要求一个函数既能接收int类型的参数,又能接收float类型的参数
这就需要写两个不同函数去接收,或者用接口+反射之类的
这就会造成代码的一个冗余,因此就推出了泛型这个概念
(2)泛型的参数
type Slice[T int|float32|float64 ] []T
T
就是类型形参,具体类型不定,类似占位符int|float32|float64
叫做类型约束T int|float32|float64
被称为类型形参列表,定义所有的类型形参Slice[T]
为类型名称- 我们将类型中带有类型形参的类型称为泛型类型,必须实例化后方可使用
例子:
// 首先定义一个结构体
type User[T int | float32, x []T] struct {
Data x
Age T
grade T
}
// 如果想给这个结构体传参
var u1 User[int, []int] // 这样是没问题的
var u2 User[int, []float32] // 但是这样就会出现问题
// 因为之前形参已经传了int,所以x实参自然应该是int而非float32
(3)泛型receiver
实例:
// 定义泛型切口
type Slice[T int | float32] []T
// 定义receiver
func (s Slice[T]) Sum() T {
var sum T
for _, value := range s {
sum += value
}
return sum
}
func main() {
// 实例化泛型切口 ,类型float32和int任选
var s Slice[int] = []int{1, 2, 3, 4, 5, 6}
// 或者这样写也行
// s := []int{1, 2, 3, 4, 5, 6}
fmt.Println(s.Sum())
}
基于队列的使用
队列,先进先出,类似排队
// 这里类型约束使用了空接口,代表的意思是所有类型都可以用来实例化泛型类型 Queue[T]
type Queue[T interface{}] struct {
elements []T
}
// 将数据放入队列尾部
func (q *Queue[T]) Put(value T) {
q.elements = append(q.elements, value)
}
// 从队列头部取出并从头部删除对应数据
func (q *Queue[T]) Pop() (T, bool) {
var value T
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 Queue[T]) Size() int {
return len(q.elements)
}
使用
var q1 Queue[string] // 可存放string类型数据的队列
q1.Put("A")
q1.Put("B")
q1.Put("C")
q1.Pop() // "A"
q1.Pop() // "B"
q1.Pop() // "C"
var q2 Queue[int] // 可存放int类型数据的队列
var q3 Queue[[]int] // 可存放[]int切片的队列
var q4 Queue[chan int] // 可存放int通道的队列
var q5 Queue[io.Reader] // 可存放接口的队列
var q6 Queue[struct{Name string}] //可存放结构体字段
// .......
动态判断变量的类型
如果想判断泛型的变量类型可以使用反射进行判断,但是一般不建议
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
}
// ...
}
(4)泛型函数
func Sum[T int | float32 | float64](a T, b T) T {
return a + b
}
// 调用 依然需要实例化
Sum[int](1,2)
// 当然go语言可以自行推到,所以下面这种写法也行
Sum(1,2)
(5)泛型接口
泛型接口嵌套的使用
type Int interface {
int | int8 | int16 | int32 | int64
}
type Uint interface {
uint | uint8 | uint16 | uint32
}
type Float interface {
float32 | float64
}
// 不同接口在类型约束中支持使用|组合,同时也会识别|
type SliceElement interface {
Int | Uint | Float | string // 组合了三个接口类型并额外增加了一个 string 类型
}
type Slice[T SliceElement] []T
任意类型泛型接口
// 空接口代表所有类型的集合。写入类型约束意味着所有类型都可拿来做类型实参
// any又是go语言的空接口
type Slice[T any] []T
var s1 Slice[int] // 正确
var s2 Slice[map[string]string] // 正确
var s3 Slice[chan int] // 正确
var s4 Slice[interface{}] // 正确
// 错误。因为 map 中键的类型必须是可进行 != 和 == 比较的类型
type MyMap[KEY any, VALUE any] map[KEY]VALUE
// 解决:用comparable
type MyMap[KEY comparable, VALUE any] map[KEY]VALUE
comparable(可比较):
Go直接内置了一个叫
comparable
的接口,它代表了所有可用!=
以及==
对比的类型不能比较大小
ordered(可排序):
可以比较大小,但并没有内置,所以想要的话需要自己来定义相关接口
// Ordered 代表所有可比大小排序的类型
type Ordered interface {
Integer | Float | ~string
}
type Integer interface {
Signed | Unsigned
}
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
type Float interface {
~float32 | ~float64
}
泛型接口的一些规则
- 1.用
|
连接多个类型的时候,类型之间不能有相交的部分(即必须是不交集) - 2.类型的并集中不能有类型形参
- 3.接口不能直接或间接地并入自己
- 4.接口的并集成员个数大于一的时候不能直接或间接并入
comparable
接口 - 5.带方法的接口(无论是基本接口还是一般接口),都不能写入接口的并集中
- 6.目前匿名结构体不支持泛型,成员方法也不支持泛型
具体每步的实现:
- 1.用
|
连接多个类型的时候,类型之间不能有相交的部分(即必须是不交集)
type MyInt int
// 错误,MyInt的底层类型是int,和 ~int 有相交的部分
type Ints interface {
~int | MyInt
}
// 但是相交的类型中是接口的话,则不受这一限制
// 正确
type Ints interface {
~int | interface{ MyInt }
}
有时我们会看到
~int
这样的表达式,~
标记表示的是底层类型为int的所有类型,包括int
- 2.类型的并集中不能有类型形参
// 错误。T是类型形参
type MyInf[T ~int | ~string] interface {
~float32 | T
}
- 3.接口不能直接或间接地并入自己
简单的来说接口嵌套不能包含自己
比如B实现了A,C实现了B,那么A就不能实现C
- 4.接口的并集成员个数大于一的时候不能直接或间接并入
comparable
接口
// 正确。只有一个类型的时候可以使用 comparable
type OK interface {
comparable
}
// 错误。类型并集不能直接并入 comparable 接口
type Bad interface {
[]int | comparable
}
- 5.带方法的接口(无论是基本接口还是一般接口),都不能写入接口的并集中
// 错误,error是带方法的接口(一般接口) 不能写入并集中
type _ interface {
~int | ~string | error
}
(6)泛型常见错误
a.类型形参错误
// 错误。类型形参不能单独使用
type CommonType[T int | string | float32] T
type Slice[T int | string | float32] []T
type MyInt int
// 错误。MyInt底层类型是int,但自身并不是不是int类型,泛型不接受
var s2 Slice[MyInt]
// 解决:通过~解决,他代表着所有以 int 为底层类型的类型也都可用于实例化
type Slice[T ~int | string | float32] []T
注意:~的使用限制
- ~后的类型不能为接口
- ~后的类型必须为基本类型
b.类型约束错误
// 错误。T *int会被编译器误认为是表达式 T乘以int,而不是int指针
// 同时 | 还会被认为是按位或操作
type NewType[T *int|*float64] []T
// 解法:通过类型约束包上 interface{}
// 解决:
type NewType[T interface{*int|*float64}] []T
c.类型定义错误
type NewType[T int | string] int
// 实例化案例
var a Wow[string] = 123 // 编译正确
var b Wow[string] = "hello" // 编译错误,因为"hello"不能赋值给底层类型int
d.类型嵌套错误
// 先定义个泛型类型 Slice[T]
type Slice[T int|string|float32|float64] []T
// 错误。泛型类型Slice[T]的类型约束中不包含uint, uint8
type UintSlice[T uint|uint8] Slice[T]
// 正确。基于泛型类型Slice[T]定义的新泛型类型 IntAndStringSlice[T]
type IntAndStringSlice[T int|string] Slice[T]
// 正确 基于IntAndStringSlice[T]套娃定义出的新泛型类型
type IntSlice[T int] IntAndStringSlice[T]
// 在map中套一个泛型类型Slice[T]
type NewMap[T int|string] map[string]Slice[T]
// 在map中套Slice[T]的另一种写法
type NewMap2[T Slice[int] | Slice[string]] map[string]T