一文搞懂Golang中的接口

news2024/11/18 16:31:30

目录

接口

接口介绍

接口定义

实现接口

空接口

实现空接口

类型断言

使用类型断言

结构体实现多接口

接口嵌套

结构体值接收者和指针接收者实现接口的区别

代码案例


接口

接口介绍

Go语言中的接口(interface)是一组方法签名的集合,是一种抽象类型。接口定义了方法,但没有实现,而是由具体的类型(struct)实现这些方法,因此接口是一种实现多态的机制。

接口定义

Go语言中的接口定义语法如下:

type 接口名 interface {
    方法名1(参数1 类型1, 参数2 类型2) 返回值类型1
    方法名2(参数3 类型3) 返回值类型2
    ...
}

其中,接口名是一个标识符,方法名是一个标识符,参数和返回值都是类型。一个接口可以包含多个方法,每个方法的返回值类型可以是单个值、多个值、无返回值(void)或者是另一个接口类型。

实现接口

package main

import "fmt"

type animal interface {
    sound() string
}

type cat struct {
    name string
}

func (c cat) sound() string {
    return "meow"
}

type dog struct {
    name string
}

func (d dog) sound() string {
    return "woof"
}

func makeSound(a animal) {
    fmt.Println(a.sound())
}

func main() {
    c := cat{"Tom"}
    d := dog{"Fido"}

    makeSound(c)
    makeSound(d)
}

定义了一个 animal 接口,它包含了一个 sound() 方法,没有任何参数,返回一个字符串。然后我们定义了两个类型,一个是 cat,一个是 dog,它们都实现了 sound() 方法。最后我们定义了一个 makeSound() 函数,它接收一个实现了 animal 接口的对象,然后调用它的 sound() 方法打印出声音。在 main() 函数中,我们创建了一个 cat 和一个 dog 对象,然后分别传递给 makeSound() 函数进行测试。

实现接口的关键在于方法名和参数列表的匹配。只有当一个类型实现了一个接口中所有的方法,才能称之为这个接口的实现类型。在上面的例子中,catdog 都实现了 animal 接口中的 sound() 方法,因此它们都是 animal 接口的实现类型。

需要注意的是,Go语言中的接口是隐式实现的,也就是说,只要一个类型实现了接口中定义的所有方法,它就自动成为这个接口的实现类型,不需要显式地声明实现了哪个接口。这使得Go语言中的接口更加灵活,也更加容易使用。

空接口

在 Go 语言中,空接口是指一个没有任何方法的接口。空接口不仅没有方法,也没有任何需要实现的约束条件,因此可以被任何类型实现。因此,空接口也被称为万能接口。

空接口在 Go 语言中的作用非常广泛,可以用来表示任何类型的数据。在许多场景中,我们可能需要处理不同类型的数据,但是又不想为每个类型都定义一个新的接口,这时候就可以使用空接口。

实现空接口

package main

import (
	"fmt"
)

// 空接口
type EmptyInterface interface {
}

func main() {
	// 可以用空接口表示任何类型的数据
	var data1 EmptyInterface = 10
	var data2 EmptyInterface = "hello"
	var data3 EmptyInterface = []int{1, 2, 3}

	fmt.Println(data1)
	fmt.Println(data2)
	fmt.Println(data3)
}

在上面的示例中,定义了一个名为 EmptyInterface 的空接口。接着,在 main 函数中定义了三个变量 data1data2data3,它们都是 EmptyInterface 类型的变量,并分别赋值为整型、字符串和整型切片。可以看到,在输出这三个变量时,都直接使用了空接口类型的变量。

类型断言

在Go语言中,可以使用类型断言(type assertion)来判断一个接口实例的底层值是什么类型,并将其转换成对应的类型。类型断言的语法如下:

value, ok := interfaceVar.(Type)

其中,interfaceVar 是一个接口变量,Type 是一个具体的类型。如果 interfaceVar 的底层值是 Type 类型,则类型断言返回 interfaceVar 的底层值和 true;否则返回零值和 false

使用类型断言

package main

import (
	"fmt"
)

func main() {
	var i interface{}
	i = "hello"

	// 使用类型断言判断 i 的底层值是否为字符串类型
	if s, ok := i.(string); ok {
		fmt.Printf("i is a string: %s\n", s)
	} else {
		fmt.Println("i is not a string")
	}

	// 使用类型断言判断 i 的底层值是否为整数类型
	if n, ok := i.(int); ok {
		fmt.Printf("i is an integer: %d\n", n)
	} else {
		fmt.Println("i is not an integer")
	}
}

在上面的示例程序中,首先定义了一个空接口变量 i,并将其赋值为字符串 "hello"。然后,通过两次类型断言分别判断 i 的底层值是否为字符串类型和整数类型,最终输出判断结果。

输出结果为:

i is a string: hello
i is not an integer

需要注意的是,在使用类型断言时,如果底层值不是指定类型,则会触发运行时错误。因此,在使用类型断言时,通常会将其与条件语句配合使用,以避免出现运行时错误。而且类型断言一般使用在switch语句中。

结构体实现多接口

在Go语言中,结构体可以实现一个或多个接口,这使得结构体可以具备多个不同的行为。

首先定义一个结构体类型:

type Person struct {
    name string
    age  int
}

接着定义两个接口类型:

type Talker interface {
    Talk() string
}

type Runner interface {
    Run() string
}

Person结构体要实现这两个接口,需要实现它们的方法:

func (p Person) Talk() string {
    return "Hi, my name is " + p.name
}

func (p Person) Run() string {
    return "I can run " + strconv.Itoa(p.age) + " miles per hour"
}

这里的Talk()方法返回一个字符串,表示Person的自我介绍。Run()方法返回一个字符串,表示Person的奔跑速度。

最后可以创建一个Person实例,并将其赋值给Talker和Runner类型的变量:

func main() {
    p := Person{"Tom", 20}
    var t Talker = p
    var r Runner = p
    fmt.Println(t.Talk())
    fmt.Println(r.Run())
}

在这个示例中,我们将p赋值给t和r。t是Talker类型,因此它只能访问Talk()方法;r是Runner类型,因此它只能访问Run()方法。但是,由于Person实现了这两个接口,t和r都可以访问Talk()和Run()方法。

接口嵌套

Go语言中的接口嵌套是一种将多个接口组合成一个新接口的方法。它可以让程序员更灵活地组织和复用代码。

接口嵌套的语法格式为:一个接口类型可以嵌套多个接口类型,也可以嵌套一个包含多个接口类型的接口。

嵌套接口的语法格式为:

type Embed interface {
    I1
    I2
}

其中,I1I2是已经定义好的接口类型。Embed接口嵌套了I1I2两个接口类型,也就是说,Embed接口继承了I1I2两个接口的所有方法。这样,实现了Embed接口的类型也必须实现I1I2接口的所有方法。

接口嵌套可以实现代码的复用和组合。通过嵌套多个接口类型,可以定义一个新的接口类型,这个新的接口类型拥有多个接口类型的方法集合,可以更方便地调用这些方法。同时,由于接口类型的动态特性,我们可以在运行时动态地组合不同的接口类型,进一步实现代码的复用和扩展。

结构体值接收者和指针接收者实现接口的区别

1.在 Go 语言中,结构体可以通过实现接口来满足某个接口的约束条件。在实现接口时,可以使用值接收者或指针接收者。它们的主要区别在于如何处理结构体的拷贝和指针。

2.值接收者方法接收一个结构体值的副本作为接收者,而指针接收者方法接收一个结构体指针作为接收者。具体来说,值接收者方法会将结构体值拷贝一份,然后对副本进行操作,而指针接收者方法则直接操作原始结构体指针所指向的对象。

3.在实现接口时,使用值接收者方法和指针接收者方法的区别在于,使用值接收者方法实现接口时,只有结构体的值可以被传递给接口,而指针接收者方法实现接口时,可以传递结构体的指针或者任何实现了该结构体指针类型的类型。这是因为在 Go 语言中,可以通过对指针类型进行间接引用来访问结构体的字段。

4.指针接收者方法还具有一些其他的优点。例如,使用指针接收者可以避免在方法中对结构体进行拷贝,从而提高程序的性能。此外,指针接收者还可以用于修改结构体中的字段值。

5.在实现接口时,应根据实际情况选择值接收者方法或指针接收者方法。如果不需要修改结构体的字段,并且希望方法能够被传递给值类型的变量,那么使用值接收者方法就可以了。如果需要修改结构体的字段,或者希望方法能够被传递给指针类型的变量,那么就应该使用指针接收者方法。

代码案例

当使用值接收者(value receiver)实现接口时,接口会拷贝值接收者的值。而当使用指针接收者(pointer receiver)实现接口时,接口会拷贝指向值接收者的指针。这两种实现方式的主要区别在于对结构体的修改是否会影响接口中对应的值。

定义了一个接口 Geometry,并分别使用值接收者和指针接收者实现了两个结构体 RectangleCircle

package main

import (
	"fmt"
	"math"
)

type Geometry interface {
	area() float64
	perimeter() float64
}

type Rectangle struct {
	width, height float64
}

func (r Rectangle) area() float64 {
	return r.width * r.height
}

func (r Rectangle) perimeter() float64 {
	return 2*r.width + 2*r.height
}

type Circle struct {
	radius float64
}

func (c *Circle) area() float64 {
	return math.Pi * c.radius * c.radius
}

func (c *Circle) perimeter() float64 {
	return 2 * math.Pi * c.radius
}

func main() {
	r := Rectangle{width: 3, height: 4}
	c := Circle{radius: 5}

	fmt.Println("Rectangle")
	fmt.Println("Area: ", r.area())
	fmt.Println("Perimeter: ", r.perimeter())

	fmt.Println("\nCircle")
	fmt.Println("Area: ", c.area())
	fmt.Println("Perimeter: ", c.perimeter())

	// 值接收者实现的结构体修改不影响接口中对应的值
	r.width = 6
	fmt.Println("\nRectangle after modification")
	fmt.Println("Area: ", r.area())
	fmt.Println("Perimeter: ", r.perimeter())

	// 指针接收者实现的结构体修改会影响接口中对应的值
	c.radius = 7
	fmt.Println("\nCircle after modification")
	fmt.Println("Area: ", c.area())
	fmt.Println("Perimeter: ", c.perimeter())
}

输出结果为:

Rectangle
Area:  12
Perimeter:  14

Circle
Area:  78.53981633974483
Perimeter:  31.41592653589793

Rectangle after modification
Area:  24
Perimeter:  14

Circle after modification
Area:  153.93804002589985
Perimeter:  43.982297150257104

可以看到,当使用值接收者实现 Rectangle 结构体时,结构体中的修改不会影响接口中对应的值;而使用指针接收者实现 Circle 结构体时,结构体中的修改会影响接口中对应的值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/545207.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

单片机--STM中断系统

目录 【1】中断系统 1.基本概念 2.中断的意义 中断提高了CPU效率? 3.中断处理过程 中断处理过程 4. 中断体系结构 5.NVIC NVIC主要功能 6.EXTI 【2】IIC IIC主要特点 【3】485 【1】中断系统 1.基本概念 在处理器中,中断相当于对于突发事件…

【Scala---02】Scala 类与对象 『 类 | 属性 | 访问权限 | 方法 | 继承 | 伴生对象伴生类』

文章目录 1. 定义类2. 属性3. 访问权限4. 方法4.1 方法 vs 函数4.2 方法重写4.3 方法重载4.4 构造方法(1) 构造器定义(2) 构造器的参数列表(3) 构造器私有化 5. 继承6. 伴生对象 & 伴生类6.1 伴生对象的由来6.2 伴生对象 & 伴生类 7. 后续 1. 定义类 Java文件&#xf…

JavaScript全解析——ES6函数中参数的默认值和解构赋值

本文为千锋资深前端教学老师带来的【JavaScript全解析】系列,文章内含丰富的代码案例及配图,从0到1讲解JavaScript相关知识点,致力于教会每一个人学会JS! 文末有本文重点总结,可以收藏慢慢看~ 更多技术类内容&#xf…

AI向百万薪资 高级原画师开刀?!爆Midjourney入局3D模型生成

来源 SoulofArt | ID Soul_Art 现在AI向高级原画师和3D开刀了? 网传爆料AI已入局3D模型生成... 这进化速度放在整个行业都十分炸裂 4月,Midjourney进一步宣布推出Niji-journey V5 这是MJ针对二次元动漫风格预训练好的模型 可在其中添加提示词直接调…

Android 广播使用详解(Broadcast Receivers)

Android 广播接收器(Broadcast Receivers) 广播接收器用于响应来自其他应用程序或者系统的广播消息。这些消息有时被称为事件或者意图。例如,应用程序可以初始化广播来让其他的应用程序知道一些数据已经被下载到设备,并可以为他们所用。这样广播接收器可…

Kubernetes配置管理 ConfigMap

Kubernetes配置管理 ConfigMap 一、ConfigMap 1.1 什么是configmap kubernetes集群可以使用ConfigMap来实现对容器中应用的配置管理。可以把ConfigMap看作是一个挂载到pod中的存储卷 1.2 创建ConfigMap的4种方式 1.2.1 在命令行指定参数创建 通过直接在命令行中指定config…

新手玩转腾讯云轻量应用服务器搭建网站(保姆级教程)

腾讯云轻量应用服务器镜像选择宝塔Linux面板,然后在宝塔面板上安装LNMP网站所需的Web环境,在宝塔面板上新建站点,上床网站程序安装包到根目录,并安装网站全流程。腾讯云百科来详细说下腾讯云轻量应用服务器搭建网站全流程&#xf…

opencv显示图片

要使用OpenCV在Python中显示图像,您需要遵循以下步骤: 1. 首先,确保已经安装了OpenCV。您可以使用以下命令安装OpenCV: pip install opencv-python 2. 然后,编写一个简单的程序来读取并显示图像。以下是一个示例代码…

数据可视化之雷达图(Radar Chart)

什么是雷达图? 雷达图(Radar Chart)也称为蜘蛛图、网络图或极坐标图,是一种用于可视化多变量数据的图表。该图表由一系列从中心点向外辐射的辐条组成,每个辐条代表一个不同的变量。数据使用线或区域绘制在每个辐条上&#xff0c…

面试的水太深,年轻人你把握不住.....

前言 去阿里面试测试工程师,这里面水太深,什么未来规划,职业发展的东西都是虚拟的,作者还太年轻,没有那个经历,把握不住。项目只有几个,开心快乐就行,不PK,文明PK。 很…

Partial convolution Gated convolution

组会讨论帖 1. 图像修复 图像修复(Image Inpainting),顾名思义,就是将图像中损坏的部分修复起来,是一种图像编辑技术,可以应用在移除物体、修复老照片、图像补全(eg,地震插值)等等。…

【初始scrapy框架上】——全栈开发——如桃花来

目录索引 scrapy框架的介绍:scrapy第三方包的下载:常见的换源网址: scrapy的图示原理:或者:或者: scrapy原理流程:详细介绍:流程描述:scrapy中的三个内置对象&#xff1a…

open cv 4.6.0 导入maven库以及依赖包 安装教程

windows: 1.官网下载windows安装包 2.安装完成后,复制opencv\build\java\x64\opencv_java451.dll 到 C:\Windows 下 centos: 参考https://www.cnblogs.com/huizhipeng/p/12732019.html 1.先安装cmake3 参考https://www.jianshu.com/p/20…

详谈大模型训练和推理优化技术

详谈大模型训练和推理优化技术 作者:王嘉宁,转载请注明出处:https://wjn1996.blog.csdn.net/article/details/130764843 ChatGPT于2022年12月初发布,震惊轰动了全世界,发布后的这段时间里,一系列国内外的…

莫言用 GPT 写颁奖辞,那如果他自己写会是什么效果呢?

在《收获》杂志 65 周年庆典上,莫言在为余华颁奖时表示,余华是自己的好朋友,但给他的颁奖词写了好几天也想不出来,后来找了 ChatGPT 帮忙写。最后,莫言让 ChatGPT 写了一篇莎士比亚风格 1000 多字的颁奖词,…

「硬核分享」Docker+jenkinsPipeline 运行实现 python 自动化

一、实现思路 在 Linux 服务器安装 docker 创建 jenkins 容器 jenkins 中创建 pipeline 项目 根据自动化项目依赖包构建 python 镜像(构建自动化 python 环境) 运行新的 python 容器,执行 jenkins 从仓库中拉下来的自动化项目 执行完成之后删除容器 二、环境准…

Defining Timing Constraints and Exceptions(UG945)

在本实验中,将学习为设计创建约束的两种方法。将使用Vivado2019.2 IDE中包含的Kintex-7 CPU网表示例设计。 步骤1:打开示例项目 1.打开Vivado IDE。 Vivado IDE的“Getting Started page”包含打开或创建项目以及查看文档的链接。 2.在“Getting Started…

泛型编程与模板简单介绍,函数模板及其原理与template,typename,模板参数T,函数模板隐式实例化等

泛型编程 比方说现在要去实现一个通用的交换函数,以我们现在的思路,肯定是通过函数重载去解决。但是这样子的话有几个缺点:重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己…

水溶性花菁染料Sulfo-CY7 NHS este标记活性脂Cyanine7-NHS

Sulfo-CY7 NHS ester是一种荧光标记试剂,化学结构为C38H42KN3O10S2,分子量约为803.98 。此外,它的分子中还含有一个N-羟基琥珀酰亚胺酯基团,这个基团可以与生物分子中的氨基结合,从而实现荧光标记。Sulfo-CY7 NHS este…

Moonbeam联合Multichain和AWS Startups正式推出Bear Necessities Hackathon黑客松

我们很高兴宣布Bear Necessities Hackathon正式启动。本次黑客松包含7个挑战,超过7万美金的奖池等你来领!我们欢迎所有的BUILDers参加,这是开发者们探索Moonbeam并构建跨链用例的机会! 本次黑客松由Moonbeam、Multichain和AWS St…