go语言入门之接口(附泛型详解)
创始人
2024-11-19 00:07:11
0

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 

相关内容

热门资讯

抖音如何投屏到电视? 在抖音视频播放界面,点击右侧的“投屏”图标,选择对应的电视设备,即可将视频投放到电视上观看。如何将抖...
shortgi什么意思 "shortgi"这个词在英语中并没有确切的意思。可能是拼写错误,或者是某个特定领域或网络用语的缩写...
WIFI光信号什么 WIFI光信号是指通过光波传输的无线网络信号,通常用于高速、稳定的网络连接。WIFI光信号详解什么是...
苹果13网络信号差怎么办(iP... 检查网络覆盖,重启手机,飞行模式切换,更新系统,重置网络设置。若问题持续,联系苹果客服或前往授权维修...
ecshop是什么意思 ECShop是一款开源的电商系统,主要用于搭建在线商城、网店等电子商务网站。ECShop 是一款基于...
怎么设置电脑开机自动开启的软件... 在Win7中,可通过"启动"文件夹或任务计划程序添加开机自启软件。将软件快捷方式拖入"启动"文件夹,...
nbns是什么协议 NB-IoT和NS-WAN都是通信协议。NB-IoT是窄带物联网的一种技术,主要用于低功耗、低带宽的...
qq音乐清除官方通知 如何删除QQ音乐心情步骤1:打开QQ音乐应用你需要在手机上找到并打开QQ音乐应用,如果你还没有安装,...
qq音乐怎么设置问候语音-启动... 在QQ音乐设置中,找到“启动问候语”选项,点击进入后选择喜欢的问候语音,保存即可。QQ音乐设置问候语...
双8pin显卡供电线的正确接法... 将电源线中的8pin接口分别插入显卡对应的8pin插槽,确保接口方向正确且完全插入。若显卡有两个8p...