关于 Golang 接口的一些总结

Golang interface 接口详解 前言 之前写过,golang 是通过 结构体(struct)-方法(method)-接口(interface) 的组合使用来实现面向对象的思想。在之前的文章 Golang 复合类型 和 Golang method 方法详解 已经详细介绍过 struct 和 method,本文将介绍 golang 面向对象的另一个重要组成部分:接口(interface)。 接口概念 接口是一种抽象的类型,描述了一系列方法的集合,作用是对一系列具有联系的方法做出抽象和概括,类似于其他语言中类的成员变量。不同的是,golang 阅读详情

在使用 Golang 构建后端服务时,接口是常用的强大工具。下面是我个人在使用 Golang 接口进行编程的一些总结。

基本定义和用法

在 Go 语言中,接口类型是函数签名的集合,也就是方法的集合。定义一个接口就表示定义了一堆方法:

// define an interface and its abstract methods
type I interface {
  PrintVal()
  setVal(float64)
}

接口所定义的只是函数的签名(抽象方法),函数的具体实现肯定要由其他具体的类型来实现。和 Java 中的接口一样,实现接口的类型必须要实现接口中的所有方法:

// type Number implements I's methods
type Number struct {
  val float64
}
func (num *Number) PrintVal() {
  fmt.Printf("%f\n", num.val)
}
func (num *Number) setVal(newNum float64) {
  num.val = newNum
}

// build-in type float64 implements I's methods
type F float64
func (num F) PrintVal() {
    fmt.Printf("%f\n", num)
}
func (num F) setVal(newNum float64) {
    num = F(newNum)
}

当一个类型实现了接口中的方法时,就可以把这个类型的实例赋值给该接口类型的变量:

func main() {
  // i is type of interface I
  var i I

  i = &Number{72.6}
  i.PrintVal()
  i.setVal(89.6)
  i.PrintVal()

  num := F(12.8)
  i = num
  i.PrintVal()
  i.setVal(16.9)
  i.PrintVal()
}

output:
72.600000
89.600000
12.800000
12.800000

对以上代码的相关解释如下:

  • Number 和 F 类型都实现了接口 I 的两个方法,因此这两个类型的实例都可以赋值给接口 I 的变量 i ,并通过 i 来调用两个类型的方法,由此可见,Go 语言利用接口实现了像泛型这样的概念。

  • 由于 F 类型实现 setVal 方法时传递的不是指针类型 *F ,因此调用 setVal 方法时并不能改变变量 num 的值;而 Number 类型实现 setVal 方法时传递的是指针类型 *Number ,因此可以改变 num.val 的值。

  • 要想使用内建类型(如图中的 float64)去实现某个接口的方法,必须像上面的代码所示把内建类型定义为另外一种类型 type F float64 ,然后使用类型 F 去实现接口的方法。如果直接用内建类型去实现接口的方法将会报错:

    func (num *float64) PrintVal() {
    fmt.Printf("%f\n", num)
    }
    func (num *float64) setVal(newNum float64) {
    *num = newNum
    }
    
    output:
    cannot define new methods on non-local type float64
    cannot define new methods on non-local type float64

如果一个类型实现了接口定义的抽象方法之外的方法,则在用接口的变量指向该类型的实例是不能调用这个额外的方法,比如 Number 类型还定义了一个在接口中没有定义的方法addOne ,此时在 main 函数中调用 i.addOne() 时将会报错:

func (num *Number) addOne() {
  num.val += 1
}

func main() {
  ...
  i.addOne()
  ...
}

output:
i.addOne undefined (type I has no field or method addOne)

假设把 F 类型实现的 setVal 函数改为传递指针类型,而 PrintVal 函数仍然是传递值类型:

func (num F) PrintVal() {
    fmt.Printf("%f\n", num)
}
func (num *F) setVal(newNum float64) {
    *num = F(newNum)
}

此时在 main 函数中执行如下代码将报错:

num := F(12.8)
// num is of type F
i = num
i.PrintVal()
i.setVal(16.9)
i.PrintVal()

output:
cannot use num (type F) as type I in assignment:
F does not implement I (setVal method has pointer receiver)

表示 F 类型并没有实现接口 I 的 setVal 函数,因为 setVal 函数接收的是指针类型 *F 。然而有趣的是,当把 num 的地址传递给 i 时,程序却能够正常运行:

num := F(12.8)
// num is of type *F
i = &num
i.PrintVal()
i.setVal(16.9)
i.PrintVal()

output:
12.800000
16.900000

*F 类型应该没有实现接口 I 的 PrintVal 函数,因为 PrintVal 函数接收的是值类型 F 。但程序的正确运行说明 func (num F) PrintVal() 的实现包含了 func (num *F) PrintVal() 的实现。而从上面报错的程序来看,func (num *F) setVal(newNum float64) 却不包含 func (num F) setVal(newNum float64) 的实现。由此我们可以得出这样的结论:当一个类型的值类型实现了接口的某个方法时,这个类型的指针类型也实现了这个方法;但当一个类型的指针类型实现了接口的某个方法时,并不意味着这个类型的值类型也实现了这个方法!

但是这种问题只是在实现接口的方法时出现,如果不是实现接口的方法,则在调用方法时指针类型和值类型是可以相互进行隐式转换的:

func (num Number) addOne() {
    num.val += 1
}
num := &Number{72.6}
num.addOne()               // 等价于 (*num).addOne(),只是执行后 num.val 的值不会改变,仍为 72.6

func (num *Number) addOne() {
    num.val += 1
}
num := Number{72.6}
num.addOne()               // 等价于 (&num).addOne(),只是执行后 num.val 的值改变了,变为 73.6

接口的值

在 Go 语言中,一个接口的值可以视为一个元组 (value, type) ,其中 type 表示是哪个类型的实例赋值给了这个接口,而 value 则是这个类型的值。紧接着第一部分中接口 I 和类型 Number 、F 的定义,运行如下代码:

var i I
fmt.Printf("%v, %T\n", i, i)

i = &Number{72.6}
fmt.Printf("%v, %T\n", i, i)

num := F(12.8)
i = num
fmt.Printf("%v, %T\n", i, i)

output:
<nil>, <nil>
&{72.6}, *main.Number
12.8, main.F

从输出结果中可以看到,i 的类型不是 main.I !当 i 没有被赋值时,i 的类型为 nil ,而当 i 被赋值后,i 的类型就变成了赋值给 i 的那个实例的类型,而 i 值则是对应实例的值。这个特性使得 Go 语言的接口使用起来非常灵活。

接口用于函数参数

接口也可以用作函数参数,比如定义一个函数 func display(i I) ,则这个函数可以既可以接收 *Number 类型的参数也可以接收 F 类型的参数:

j := &Number{15.6}
display(j)

k := F(17.1)
display(k)

output:
15.600000
17.100000

判断接口类型

当函数接收一个接口时,由于该接口可以由不同的类型来实现,有时候需要判断这个接口属于哪种类型再进行下一步的操作,判断接口类型可以用以下两种方法:

  • 使用类型断言 t, ok = i.(T) 。其中,i 是接口,T 是某个类型。如果接口 i 的值 <value, type> 中的 type == T ,则返回 ok == true ,并且 t 将会变成类型 T 的一个实例,而不再是一个接口类型!这个实例的值与接口 i 的 value 相同;如果接口 i 的值 <value, type> 中的 type != T ,则返回 ok == false ,但是 t 仍然会变成类型 T 的一个实例 !只是 t 的值为 nil 。

  • 使用类型判断 switch t := i.(type) :

    switch t := i.(type) {
    case T:
        // t is type of T here
    case S:
        // t is type of S here
    default:
        // no match; t's type and value is the same as i
    }

    分析上述代码,类型判断语句中的 case 语句后面不再是值,而是不同的类型 T 和 S 。当 t 的类型是 T 时,在 case T 语句后面 t 就变成了一个类型为 T 的实例 ,而不再是一个接口类型!这个实例的值与接口 i 的 value 相同,当 t 的类型为 S 时,也同理,t 变成了类型 S 的一个实例。当没有匹配时,进入 default 语句,此时 t 仍然是和 i 相同的一个接口类型,其 type 和 value 都与 i 的 type 和 value 相同。

下面再用代码说明进行类型断言或判断后相应类型和值的改变:

func display1(i I) {
    switch t := i.(type) {
    case *Number:
        fmt.Printf("%f\n", t.val)  // 可以直接使用 t 来获取 *Number 类型的字段 val 以及调用接口 I
        t.addOne()                 // 没有定义的函数 addOne,这充分说明了 t 已经不再是一个接口,
        fmt.Printf("%f\n", t.val)  // 而是 *Number 类型的一个实例!
    default:
        fmt.Printf("%v, %T\n", t, t)
    }
}

func display2(i I) {
    t, ok := i.(*Number)
    if ok {
        fmt.Printf("%f\n", t.val)  // 同理,t 已经变成 *Number 类型的一个实例。
        t.addOne()
        fmt.Printf("%f\n", t.val)
    } else {
        fmt.Printf("%v, %T\n", t, t)
    }
}

func main() {
    var i I
    i = &Number{72.6}
    display1(i)
    display2(i)

    var j I
    display1(j)
    display2(j)
}

output:
72.600000
73.600000
73.600000
74.600000
<nil>, <nil>          // switch type 没有匹配时,t 的类型和值仍然与 j 的类型和值相同,为空接口
<nil>, *main.Number   // 但是在使用类型断言时,如果没有匹配,t 会变成 *Number 的一个实例,值为空。

空接口

在第一部分中已经可以看到了,当 Number 类型定义了一个在接口 I 中没有定义的函数 addOne 时,接口变量 i 不能调用这个 addOne 方法。对于空接口 interface{} 来说,它没有定义任何的抽象方法。这就意味着,任何一个类型,无论有什么方法,都可以实现空接口;换言之,任何类型的实例都可以赋值给空接口类型的变量。但是,在一个类型的实例赋值给一个空接口的变量 i 后,i 将不能调用这个类型的任何方法!必须要进行类型断言或者类型判断后,才能把空接口 i 变成相应类型的实例,从而调用这个类型的方法或访问其字段!

var k interface{}
k = "hello"
fmt.Printf("%v, %T\n", k, k)

output:
hello, string

参考资料

  1. 《学习 Go 语言》Miek Gieben, 邢星译
  2. A Tour of Go
【Golang】Go语言接口与多态 在Go语言中,接口(Interface)是一个重要的特性,它允许我们定义一组方法但不实现它们,任何类型只要实现了这些方法,就被认为是实现了该接口。 接口体现了程序设计的多态、高内聚、低耦合的思想,是实现面向对象编程中多态性的关键工具。 阅读详情

相关推荐

Golang接口最佳实践:写出更优雅的Go代码

本文旨在帮助Go语言开发者深入理解接口的概念和用法,掌握接口设计的最佳实践,从而编写出更优雅、更健壮的Go代码。我们将覆盖从基础到进阶的接口知识,包括接口定义、实现、组合以及在实际项目中的应用技巧。核心概念与联系:解释接口的基本概念和设计哲学接口设计原则:介绍接口设计的最佳实践和模式实际应用场景:展示接口在不同场景下的应用高级技巧:探讨接口组合、空接口等进阶话题项目实战:通过完整示例演示接口的实际应用总结与思考:回顾关键知识点并提出进一步思考的问题接口(Interface)

AI Agent Harness Engineering 1194

Go语言报错cannot define new methods on non-local type

今天在写一段Golang代码时报错:package main import( "fmt" ) func Add(a ,b int){ //函数 fmt.Println(a+b) } func (a int) Add (b int){ //方法 fmt.Println(a+b) }报错信息如下:cannot define new methods on non-local

xiangxianghehe的博客 1万+

【Go 语言编程实战】用 Golang 实现一个 Go Thrift RPC 接口的泛化调用代码实例

本文详细讲解一下 go rpc 泛化调用的原理和过程和如何使用 golang 实现的 go thrift RPC 接口的泛化调用代码。

AI天才研究院 1万+

golang语法基础--type关键字

Go语言type关键字(类型别名) 注意:本节内容涉及Go语言新版本的功能,内容上会涉及后续章节讲解的类型定义及结构体嵌入等特性。另外,本节内容适用于对Go语言很熟悉且正在关注工程升级、代码重构等问题的读者阅读。 类型别名是 Go 1.9 版本添加的新功能,主要用于解决代码升级、迁移中存在的类型兼容性问题。在 C/C++ 语言中,代码重构升级可以使用宏快速定义一段新的代码,Go语言中没有选择加入宏,而是解决了重构中最麻烦的类型名变更问题。 在 Go 1.9 版本之前定义内建类型的代码...

我不生产数据,只是数据的搬运工 899

S32DS使用手把手教程:从零实现CAN通信配置

深入讲解S32DS使用过程中如何从零开始配置CAN通信,涵盖关键步骤与实用技巧,帮助开发者快速上手S32DS使用中的常见问题与调试方法。

weixin_42405592的博客 1127

GenomeSurvyIndex设计--随笔017

摘要: GenomeSurvyIndex是一款自动化多倍体基因组调查工具,整合FastK、Smudgeplot和GenomeScope2.0三大核心组件。该工具通过k-mer频谱特征自动识别基因组倍性类型,包括二倍体(1:2:4)和同源四倍体(1:2:3:4),并采用3a1b>2a2b算法判定同源/异源多倍体。工具提供完整的conda环境配置方案,封装为Python脚本实现全流程自动化分析,包含k-mer计数、倍性判定、基因组特征计算等功能,最终输出标准化报告和可视化图表。该方案可直接部署使用,适用于

weixin_44874487的博客 475

Go语言--数据类型

一 数据类型 Go语言中有丰富的数据类型。Go语言按类别有以下几种数据类型: 布尔型(bool) 布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。 <注意> 布尔类型变量的默认值为false。 Go 语言中不允许将整型强制转换为布尔型。 布尔型无法参与数值运算,也无法与其他类型进行转换。 数字类型 数字类型又分为整型、浮点型和其他数字类型。 1、整型 整型分为有符号整型和无符号整型。 2、浮点型 floa..

yunfan 629

微机原理课程设计-模拟十字路口交通信号灯

一、设计任务描述 1.1设计目的 通过课程设计使学生更进一步掌握微机原理及应用课程的有关知识,提高应用微机解决问题的能力,加深对微机应用的理解。通过查阅资料,结合所学知识进行软、硬件的设计,使学生初步掌握应用微机解决问题的步骤及方法。为以后学生结合专业从事微机应用设计奠定基础。 1.2 设计要求 控制LED灯,按照十字路口红绿灯的原理亮灭显示: (1) 东西方向车辆放行x秒...

Learning_is_endless的博客 2万+

Golang中的type关键字

文章目录定义结构体定义接口自定义类型类型定义类型开关 原文链接 type有几种用法:定义结构体,定义接口, 类型别名, 类型定义, 类型开关 定义结构体 结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。类似Java 的类,我们可以把Go中的struct看作是不支持继承行为的轻量级的“类”。 我们来看看使用type怎么定义结构体: //定义一个 Books结构体 type Books struct { title string author string subject str

jianming21的博客 2272

小目标检测之改进的YOLOV11算法

MASF-YOLO通过多尺度特征聚合注意力增强自适应融合三大核心创新,显著提升了无人机视角下的小目标检测性能。

hasakie的博客 1719

Vue - 最新详细实现滑块拖动拼图验证,网站登录验证滑块验证功能,拖动滑块到图片缺口的位置即为成功,滑块验证安全检测(详细示例代码,一键复制运行)

您一定见过很多系统都“配备”了滑块验证、拼图验证、找不同等,本质上都是为了验证登录系统的是人还是机器(代码),如下图所示效果: 您打开项目根目录,终端执行如下命令:拼图式拖动滑块验证 验证拖动滑块验证,滑动拼图/图片点选验证 接下来,在中加入以下代码: 如下图所示: 更多配置示例和参数详见官方文档,以下便是如上效果图的代码:.........

🏆 前端领域优质创作者 1万+

非本地类型不能定义方法 cannot define new methods on non-local type time.Duration

能够随意地为各种类型起名字,是否意味着可以在自己包里为这些类型任意添加方法 ? 参见下面的代码演示 : package main import "time" type MyDuration = time.Duration func (m MyDuration) Set() { } func main() { } 编译后报错: ./temp.go:7:6: cannot define new methods on non-local type time.Duration 编译器提示:不能在一个非本

wohu1104的专栏 2092

Android Input——输入系统介绍(一)

Input 系统对于提供流畅、灵敏和一致的用户交互体验至关重要,它需要在不同的设备、场景和应用中保证输入事件的正确性、及时性和安全性。Android 输入系统是一个复杂但高效的设计,旨在处理来自各种输入设备(如触摸屏、键盘等)的用户交互,并将这些交互转换为应用程序可以理解和响应的形式。EventHub:输入系统的最底层,直接与硬件通信,监听和读取输入设备的原始事件。:从 EventHub 获取原始输入事件,对其进行解析,转化为更高层次的事件,以便上层应用或服务能够理解。

小旭的专栏 1712

GoLang - Go中接口的用法

Go-接口 接口是定义了合约但并没有实现的类型。举个例子: type Logger interface { Log(message string) } 那这样做有什么作用呢?其实,接口有助于将代码与特定的实现进行分离。例如,我们可能有各种类型的日志记录器: type SqlLogger struct { … } type ConsoleLogger struct { … } type FileLogger struct { … } 针对接口而不是具体实现的编程会使我们很轻松的修改(或者测试)任何代码都不会产

xiabiao1974的博客 1万+

基于深度学习的垃圾邮件识别

垃圾邮件的大量存在严重威胁着电子邮件系统的安全和效率。基于深度学习的垃圾邮件识别技术已经成为目前最有效的解决方案之一。本文将详细介绍使用深度学习技术进行垃圾邮件识别的方法,并提供相应的代码实现。 随着电子邮件的广泛应用,垃圾邮件的数量不断增加,传统的规则和统计方法已经无法满足对垃圾邮件进行准确识别的需求。深度学习技术以其强大的模式识别能力和自动特征提取能力成为解决垃圾邮件识别难题的有效工具。

一键难忘的博客 1094

2026华为OD面试手撕代码真题目录

一般面试的时候每一轮技术面都会出一到两个手撕代码题,这些题的特点就是,非常短小,易于理解。不可能会再出阅读理解一样的机试题的。但是这些题目也非常注意基本功,很注意细节的处理,一般都是easy+medium题目。但是大家一定要注意,因为大部分面试官上面一开始就会让你做题,直接看你所有的做题过程(防止有人水平一般,机试就网上找的答案的蒙混过关),手撕代码题会给你后面的面试定一个基调。

MISAYAONE的博客 9万+

golang接口的使用场景_如何在Go中使用接口

golang接口的使用场景 介绍 (Introduction) Writing flexible, reusable, and modular code is vital for developing versatile programs. Working in this way ensures code is easier to maintain by avoiding the need to ...

1906

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

【Golang】在Go语言中如何实现接口

接口是Go语言中的一个核心概念,它为类型定义了行为。接口提供了一种方式,使得不同的类型可以被同样对待,只要它们实现了相同的接口。在这篇博客中,我们将讨论如何在Go语言中实现接口。接口是Go语言中的一个强大特性,它提供了一种强大的抽象机制,可以帮助我们编写更加灵活和可扩展的代码。通过定义接口,我们可以定义对象的行为,而不是它们具体的实现。这使得我们的代码更加灵活,更易于测试和维护。希望这篇博客能帮助你理解如何在Go中实现接口。如果你有任何问题或建议,欢迎在评论区留言。

tr6666的博客 1628

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

【Golang】Go语言编程思想(一):接口

对 Golang 的接口 interface 进行学习和记录。

YGGP的博客 1072

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Golang笔记 4.2 go 接口

接口是使用方法的抽象,使当前程序可以更聚焦在方法的应用,不关心该方法的具体实现。后续的应用程序在做具体实现时,再去完善具体实现,不会影响之前程序已经定义好的逻辑。 更通俗地来讲,接口的定义,就像是设置了一个岗位,描述了岗位的职责;,一些规章可以先根据岗位职责来制定,不关心具体是谁来任职这个岗位。当应用程序开始执行的时候,需要给接口传入一个实例,相当于企业开始运营时再把一个人放到这个岗位。

IoT小能手 834

golang 切片 接口_Go 中接口值的复制

我一直在思考 Go 语言它是如何工作的。直到最近我才发现 Go 中一切都是基于值的。当我们向函数传递参数、迭代切片、执行类型断言时我们都可以看到这一现象。在这些例子中,这些数据结构所存储的值的拷贝会被返回。当我刚开始学习 Go 的时候,我对于这种实现方式很失望,但渐渐地我开始意识到这样做对于我们的代码来说有它的合理性。我开始在想,如果我创建了一个所存储的是值而非指针的接口类型的拷贝会发生什么。那么...

weixin_39905725的博客 280

Go 学习笔记 11 | Golang 接口详解

Go 学习笔记 11 | Golang 接口详解

Wonz 1562

【Golang】go语言面向接口

Go语言的接口是一种抽象数据类型,是一系列接口的集合,接口把所有的具有共性的方法定义在一起,任何其它类型只要实现了接口定义的方法就是实现了接口。接口是duck-type编程的一种体现,不关心属性(数据),只关心行为(方法)。 type 接口类型名 interface{ 方法名1(参数列表)(返回值) 方法名2(参数列表)(返回值) ... } type Fruit i...

smx_dd的博客 506

GoLang之Go 接口与 C++ 接口有何异同(10)

GoLang之Go 接口与 C++ 接口有何异同(10)

weixin_52690231的博客 460

【Golang从零开始的修炼之路】Go接口的那些事~

Go 接口是一种类型,它定义了一组方法签名。接口类型的变量可以存储任何实现了该接口的具体类型的值,实现接口需要通过实现接口签名中的所有方法来完成。

技术知识分享 327

【Golang 基础系列十六】Go 语言接口

概述 Go 语言中的接口就是方法签名的集合,接口只有声明,没有实现,不包含变量。 语法 定义接口 type [接口名] interface { 方法名1(参数列表) 返回值列表 方法名2(参数列表) 返回值列表 ... } 例子 type Isay interface{ sayHi() } 实现接口 例子 //定义接口的实现类 type Chinese...

郭枫的博客 731
上一篇: 详细设计和框架映射
下一篇: Git本地版本管理
Jack_CJZ
博客等级 码龄10年 10粉丝 38原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值