go语言中的接口

接口简介

  1. 现实生活中的接口

现实生活中手机、相机、U 盘都可以和电脑的 USB 接口建立连接。我们不需要关注 usb 卡槽大小是否一样,因为所有的 USB 接口都是按照统一的标准来设计的。

在这里插入图片描述

  1. Golang 中的接口(interface)

Golang 中的接口是一种抽象数据类型,Golang 中接口定义了对象的行为规范,只定义规范不实现。接口中定义的规范由具体的对象来实现。

通俗的讲接口就一个标准,它是对一个对象的行为和规范进行约定,约定实现接口的对象必须得按照接口的规范。

Golang 接口的定义

在 Golang 中接口(interface)是一种类型,一种抽象的类型。接口(interface)是一组函数 method 的集合,Golang 中的接口不能包含任何变量。

在 Golang 中接口中的所有方法都没有方法体,接口定义了一个对象的行为规范,只定义规范不实现。接口体现了程序设计的多态和高内聚低耦合的思想。

Golang 中的接口也是一种数据类型,不需要显示实现。只需要一个变量含有接口类型中的所有方法,那么这个变量就实现了这个接口。

Golang 中每个接口由数个方法组成,接口的定义格式如下:

type 接口名 interface{
方法名 1( 参数列表 1 ) 返回值列表 1
方法名 2( 参数列表 2 ) 返回值列表 2}

其中:

  • 接口名:使用 type 将接口定义为自定义的类型名。Go 语言的接口在命名时,一般会在
    单词后面添加 er,如有写操作的接口叫 Writer,有字符串功能的接口叫 Stringer 等。接
    口名最好要能突出该接口的类型含义。
  • 方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被
    接口所在的包(package)之外的代码访问。
  • 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。

案例1:定义一个 Usber 接口让 Phone 和 Camera 结构体实现这个接口

package main

import (
	"fmt"
)

type Usber interface {
	Start()
	Stop()
}

type Phone struct {
	Name string
}

func (p Phone) Start() {
	fmt.Println(p.Name, "开始工作")
}
func (p Phone) Stop() {
	fmt.Println("phone 停止")
}

type Camera struct {
}

func (c Camera) Start() {
	fmt.Println("相机 开始工作")
}
func (c Camera) Stop() {
	fmt.Println("相机 停止工作")
}

func main() {
	phone := Phone{Name: "小米手机"}
	var p Usber = phone //phone 实现了 Usb 接口
	p.Start()
	camera := Camera{}
	var c Usber = camera //camera 实现了 Usb 接口
	c.Start()
}

案例2:Computer 结构体中的 Work 方法必须传入一个Usb的接口

package main

import (
	"fmt"
)

type Usber interface {
	Start()
	Stop()
}

type Phone struct {
	Name string
}

func (p Phone) Start() {
	fmt.Println(p.Name, "开始工作")
}
func (p Phone) Stop() {
	fmt.Println("phone 停止")
}

type Camera struct {
}

func (c Camera) Start() {
	fmt.Println("相机 开始工作")
}
func (c Camera) Stop() {
	fmt.Println("相机 停止工作")
}

// 电脑的结构体
type Computer struct {
	Name string
}

// 电脑的 Work 方法要求必须传入 Usb 接口类型数据
func (c Computer) Work(usb Usber) {
	usb.Start()
	usb.Stop()
}

func main() {
	phone := Phone{Name: "小米手机"}
	camera := Camera{}
	computer := Computer{}
	//把手机插入电脑的 Usb 接口开始工作
	computer.Work(phone)
	//把相机插入电脑的 Usb 接口开始工作
	computer.Work(camera)
}

空接口

Golang 中的接口可以不定义任何方法,没有定义任何方法的接口就是空接口。空接口表示没有任何约束,因此任何类型变量都可以实现空接口。
空接口在实际项目中用的是非常多的,用空接口可以表示任意数据类型。

代码示例:

package main

import "fmt"

func main() {
	// 定义一个空接口 x, x 变量可以接收任意的数据类型
	var x interface{}
	s := "你好 golang"
	x = s
	fmt.Printf("type:%T value:%v\n", x, x)
	i := 100
	x = i
	fmt.Printf("type:%T value:%v\n", x, x)
	b := true
	x = b
	fmt.Printf("type:%T value:%v\n", x, x)
}

运行结果:

type:string value:你好 golang
type:int value:100
type:bool value:true
  1. 空接口作为函数的参数

使用空接口实现可以接收任意类型的函数参数。

package main

import "fmt"

// 空接口作为函数参数
func show(a interface{}) {
	fmt.Printf("type:%T value:%v\n", a, a)
}
func main() {
	show("hello world")
}
  1. map 的值实现空接口

使用空接口实现可以保存任意值的字典。

代码示例:

package main

import "fmt"

func main() {
	// 空接口作为 map 值
	var studentInfo = make(map[string]interface{})
	studentInfo["name"] = "张三"
	studentInfo["age"] = 18
	studentInfo["married"] = false
	fmt.Println(studentInfo)
}

运行结果:

map[age:18 married:false name:张三]
  1. 切片实现空接口
package main

import "fmt"

func main() {
	var slice = []interface{}{"张三", 20, true, 32.2}
	fmt.Println(slice) // [张三 20 true 32.2]
}

类型断言

一个接口的值(简称接口值)是由一个具体类型和具体类型的值两部分组成的。这两部分分别称为接口的动态类型和动态值。

如果我们想要判断空接口中值的类型,那么这个时候就可以使用类型断言,其语法格式:

x.(T)

其中:

  • x : 表示类型为 interface{}的变量
  • T : 表示断言 x 可能是的类型

该语法返回两个参数,第一个参数是 x 转化为 T 类型后的变量,第二个值是一个布尔值,若为 true 则表示断言成功,为 false 则表示断言失败。
举个例子:

package main

import "fmt"

func main() {
	var x interface{}
	x = "Hello golang"
	v, ok := x.(string)
	if ok {
		fmt.Println(v) // Hello golang
	} else {
		fmt.Println("类型断言失败")
	}
}

上面的示例中如果要断言多次就需要写多个 if 判断,这个时候我们可以使用 switch 语句来
实现:
注意:类型.(type)只能结合 switch 语句使用

package main

import "fmt"

func justifyType(x interface{}) {
	switch v := x.(type) {
	case string:
		fmt.Printf("x is a string,value is %v\n", v)
	case int:
		fmt.Printf("x is a int is %v\n", v)
	case bool:
		fmt.Printf("x is a bool is %v\n", v)
	default:
		fmt.Println("unsupport type!")
	}
}
func main() {
	justifyType("Hello golang")
}

因为空接口可以存储任意类型值的特点,所以空接口在 Go 语言中的使用十分广泛。
关于接口需要注意的是:只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要定义接口。不要为了接口而写接口,那样只会增加不必要的抽象,导致不必要的运行时损耗。

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

  1. 值接收者

如果结构体中的方法是值接收者,那么实例化后的结构体值类型和结构体指针类型都可以赋值给接口变量。

package main

import "fmt"

type Usb interface {
	Start()
	Stop()
}
type Phone struct {
	Name string
}

func (p Phone) Start() {
	fmt.Println(p.Name, "开始工作")
}
func (p Phone) Stop() {
	fmt.Println("phone 停止")
}
func main() {
	phone1 := Phone{Name: "小米手机"}
	var p1 Usb = phone1 //phone1 实现了 Usb 接口 phone1 是 Phone 类型
	p1.Start()          //小米手机 开始工作
	phone2 := &Phone{Name: "苹果手机"}
	var p2 Usb = phone2 //phone2 实现了 Usb 接口 phone2 是 *Phone 类型
	p2.Start()          //苹果手机 开始工作
}
  1. 指针接收者:

如果结构体中的方法是指针接收者,那么实例化后结构体指针类型都可以赋值给接口变量,结构体值类型没法赋值给接口变量。

示例代码:

package main

import "fmt"

type Usb interface {
	Start()
	Stop()
}
type Phone struct {
	Name string
}

func (p *Phone) Start() {
	fmt.Println(p.Name, "开始工作")
}
func (p *Phone) Stop() {
	fmt.Println("phone 停止")
}
func main() {
	/* 错误写法
	phone1 := Phone{
	Name: "小米手机", }
	var p1 Usb = phone1
	p1.Start() */
	// 将 phone1 转换为 *Phone 类型
	phone1 := Phone{Name: "小米手机"}
	var p1 Usb = &phone1
	p1.Start()
	// phone2 实现了 Usb 接口
	phone2 := &Phone{Name: "苹果手机"}
	var p2 Usb = phone2 //phone2 实现了 Usb 接口 phone2 是 *Phone 类型
	p2.Start()          //苹果手机 开始工作
}

运行结果:

小米手机 开始工作
苹果手机 开始工作

一个结构体实现多个接口

Golang 中一个结构体也可以实现多个接口

示例代码:

package main

import "fmt"

type AInterface interface {
	GetInfo() string
}
type BInterface interface {
	SetInfo(string, int)
}
type People struct {
	Name string
	Age  int
}

func (p People) GetInfo() string {
	return fmt.Sprintf("姓名:%v 年龄:%d", p.Name, p.Age)
}
func (p *People) SetInfo(name string, age int) {
	p.Name = name
	p.Age = age
}
func main() {
	var people = &People{
		Name: "张三", Age: 20}
	// people 实现了 AInterface 和 BInterface
	var p1 AInterface = people
	var p2 BInterface = people
	fmt.Println(p1.GetInfo())
	p2.SetInfo("李四", 30)
	fmt.Println(p1.GetInfo())
}

运行结果:

姓名:张三 年龄:20
姓名:李四 年龄:30

接口嵌套

接口与接口间可以通过嵌套创造出新的接口。

package main

import "fmt"

type SayInterface interface {
	say()
}
type MoveInterface interface {
	move()
}

// 接口嵌套
type Animal interface {
	SayInterface
	MoveInterface
}
type Cat struct {
	name string
}

func (c Cat) say() {
	fmt.Println("喵喵喵")
}
func (c Cat) move() {
	fmt.Println("猫会动")
}
func main() {
	var x Animal
	x = Cat{name: "花花"}
	x.move()
	x.say()
}

总结

在Go语言中,接口(interface)是一种非常强大的特性,它允许我们定义一组方法签名,并且可以由任何类型实现,只要该类型包含与接口定义的方法相匹配的方法集。

什么是接口?

接口是Go语言中的一种抽象类型,它规定了一组方法签名,但不提供这些方法的具体实现。一个类型如果实现了某个接口定义的所有方法,则称该类型实现了这个接口。这意味着,即使两个类型没有任何继承关系,只要它们实现了相同的方法集,就可以认为它们实现了相同的接口。

定义接口

在Go中,接口通过type关键字后跟接口名称和interface{}来定义。接口内部是一系列方法签名的集合:

type Speaker interface {
    Speak() string
}

这里定义了一个名为Speaker的接口,它要求所有实现此接口的类型必须拥有一个返回字符串的Speak方法。

实现接口

在Go中,实现接口不需要显式的声明或继承关系。只要一个类型提供了接口所需的所有方法,那么这个类型就自动实现了该接口。例如:

type Dog struct{}

func (d Dog) Speak() string {
    return "Woof!"
}

type Cat struct{}

func (c Cat) Speak() string {
    return "Meow!"
}

在这个例子中,DogCat都实现了Speaker接口,因为它们都提供了Speak方法。

使用接口

一旦有了接口定义和实现了该接口的类型,你就可以使用接口来编写更加通用和灵活的代码。下面是一个简单的例子,展示了如何使用Speaker接口:

func SaySomething(s Speaker) {
    fmt.Println(s.Speak())
}

func main() {
    dog := Dog{}
    cat := Cat{}
    
    SaySomething(dog)
    SaySomething(cat)
}

这段代码会输出:

Woof!
Meow!
空接口

Go还支持一种特殊的接口——空接口(interface{}),它可以表示任何类型的值。因为空接口没有指定任何方法,所以所有的类型都实现了空接口。这使得空接口非常适合用于需要处理未知类型的场景。

func Println(a interface{}) {
    fmt.Println(a)
}

在这里,Println函数接受一个interface{}类型的参数,因此它可以接收任何类型的输入。

接口组合

Go语言允许通过组合现有的接口来创建新的接口。这对于组织相关的方法集特别有用:

type Animal interface {
    Speaker
    Eat()
}

这里的Animal接口不仅包含了Speaker接口的所有方法,还额外添加了一个Eat方法。

最佳实践
  • 保持接口简小:理想的接口应该只包含必要的方法。较大的接口可能意味着设计上的问题。
  • 使用接口进行解耦:通过依赖于接口而不是具体类型,可以使你的代码更加模块化和易于测试。
  • 避免不必要的类型断言:虽然Go允许你在运行时检查类型是否实现了某个接口,但过度使用会导致代码难以维护。

Go语言的接口提供了一种简洁而强大的方式来定义行为规范,而不强制实现细节。通过合理地应用接口,不仅可以提高代码的灵活性和可复用性,还能促进更好的软件设计。希望这篇博客能够帮助你更好地理解和利用Go语言中的接口。开始尝试吧,你会发现它们能为你的项目带来意想不到的好处!

参考文献

https://gobyexample.com/

https://www.w3schools.com/go/

https://go.dev/doc/tutorial/

https://www.geeksforgeeks.org/golang-tutorial-learn-go-programming-language/

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

网络安全威胁框架与入侵分析模型概述

引言 “网络安全攻防的本质是人与人之间的对抗,每一次入侵背后都有一个实体(个人或组织)”。这一经典观点概括了网络攻防的深层本质。无论是APT(高级持续性威胁)攻击、零日漏洞利用,还是简单的钓鱼攻击&am…

Redis企业开发实战(三)——点评项目之优惠券秒杀

目录 一、全局唯一ID (一)概述 (二)全局ID生成器 (三)全局唯一ID生成策略 1. UUID (Universally Unique Identifier) 2. 雪花算法(Snowflake) 3. 数据库自增 4. Redis INCR/INCRBY 5.总结 (四)Redis实现全局唯一ID 1.工具类 2.测试类 3…

Verilog代码实例

Verilog语言学习! 文章目录 目录 文章目录 前言 一、基本逻辑门代码设计和仿真 1.1 反相器 1.2 与非门 1.3 四位与非门 二、组合逻辑代码设计和仿真 2.1 二选一逻辑 2.2 case语句实现多路选择逻辑 2.3 补码转换 2.4 7段数码管译码器 三、时序逻辑代码设计和仿真 3.1…

排序算法--基数排序

核心思想是按位排序(低位到高位)。适用于定长的整数或字符串,如例如:手机号、身份证号排序。按数据的每一位从低位到高位(或相反)依次排序,每次排序使用稳定的算法(如计数排序&#…

图形化界面MySQL(MySQL)(超级详细)

目录 1.官网地址 1.1在Linux直接点击NO thanks…? 1.2任何远端登录,再把jj数据库给授权 1.3建立新用户 优点和好处 示例代码(MySQL Workbench) 示例代码(phpMyAdmin) 总结 图形化界面 MySQL 工具大全及其功能…

C++ 使用CURL开源库实现Http/Https的get/post请求进行字串和文件传输

CURL开源库介绍 CURL 是一个功能强大的开源库,用于在各种平台上进行网络数据传输。它支持众多的网络协议,像 HTTP、HTTPS、FTP、SMTP 等,能让开发者方便地在程序里实现与远程服务器的通信。 CURL 可以在 Windows、Linux、macOS 等多种操作系…

mapbox进阶,添加绘图扩展插件,绘制圆形

👨‍⚕️ 主页: gis分享者 👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.2 ☘️mapboxgl.Map style属性1.3 ☘️MapboxDraw 绘图控件二、🍀添加绘图扩…

网络工程师 (24)数据封装与解封装

一、数据封装 数据封装是指将协议数据单元(PDU)封装在一组协议头和尾中的过程。在OSI 7层参考模型中,数据从应用层开始,逐层向下封装,直到物理层。每一层都会为其PDU添加相应的协议头和尾,以包含必要的通信…

OSPF基础(3):区域划分

OSPF的区域划分 1、区域产生背景 路由器在同一个区域中泛洪LSA。为了确保每台路由器都拥有对网络拓扑的一致认知,LSDB需要在区域内进行同步。OSPF域如果仅有一个区域,随着网络规模越来越大,OSPF路由器的数量越来越多,这将导致诸…

C++----继承

一、继承的基本概念 本质:代码复用类关系建模(是多态的基础) class Person { /*...*/ }; class Student : public Person { /*...*/ }; // public继承 派生类继承基类成员(数据方法),可以通过监视窗口检…

【DeepSeek】DeepSeek小模型蒸馏与本地部署深度解析DeepSeek小模型蒸馏与本地部署深度解析

一、引言与背景 在人工智能领域,大型语言模型(LLM)如DeepSeek以其卓越的自然语言理解和生成能力,推动了众多应用场景的发展。然而,大型模型的高昂计算和存储成本,以及潜在的数据隐私风险,限制了…

ZZNUOJ(C/C++)基础练习1081——1090(详解版)

目录 1081 : n个数求和 (多实例测试) C C 1082 : 敲7(多实例测试) C C 1083 : 数值统计(多实例测试) C C 1084 : 计算两点间的距离(多实例测试) C C 1085 : 求奇数的乘积(多实例测试…

axios 发起 post请求 json 需要传入数据格式

• 1. axios 发起 post请求 json 传入数据格式 • 2. axios get请求 1. axios 发起 post请求 json 传入数据格式 使用 axios 发起 POST 请求并以 JSON 格式传递数据是前端开发中常见的操作。 下面是一个简单的示例,展示如何使用 axios 向服务器发送包含 JSON 数…

硬盘接入电脑提示格式化?是什么原因?怎么解决?

有时候,当你将硬盘接入电脑时,看到系统弹出“使用驱动器中的光盘之前需要将其格式化”的提示,肯定会感到十分困惑和焦虑。这种情况不仅让人担心数据丢失,也可能影响正常使用。为什么硬盘会突然要求格式化?是硬盘出了问…

使用Python实现PDF与SVG相互转换

目录 使用工具 使用Python将SVG转换为PDF 使用Python将SVG添加到现有PDF中 使用Python将PDF转换为SVG 使用Python将PDF的特定页面转换为SVG SVG(可缩放矢量图形)和PDF(便携式文档格式)是两种常见且广泛使用的文件格式。SVG是…

【大数据技术】搭建完全分布式高可用大数据集群(Kafka)

搭建完全分布式高可用大数据集群(Kafka) kafka_2.13-3.9.0.tgz注:请在阅读本篇文章前,将以上资源下载下来。 写在前面 本文主要介绍搭建完全分布式高可用集群 Kafka 的详细步骤。 注意: 统一约定将软件安装包存放于虚拟机的/software目录下,软件安装至/opt目录下。 安…

【C++篇】C++11新特性总结1

目录 1,C11的发展历史 2,列表初始化 2.1C98传统的{} 2.2,C11中的{} 2.3,C11中的std::initializer_list 3,右值引用和移动语义 3.1,左值和右值 3.2,左值引用和右值引用 3.3,…

Redis --- 使用HyperLogLog实现UV(访客量)

UV 和 PV 是网站或应用数据分析中的常用指标,用于衡量用户活跃度和页面访问量。 UV (Unique Visitor 独立访客): 指的是在一定时间内访问过网站或应用的独立用户数量。通常根据用户的 IP 地址、Cookies 或用户 ID 等来唯一标识一个用户。示例&#xff1…

【机器学习案列】糖尿病风险可视化及预测

🧑 博主简介:曾任某智慧城市类企业算法总监,目前在美国市场的物流公司从事高级算法工程师一职,深耕人工智能领域,精通python数据挖掘、可视化、机器学习等,发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…

单片机之基本元器件的工作原理

一、二极管 二极管的工作原理 二极管是一种由P型半导体和N型半导体结合形成的PN结器件,具有单向导电性。 1. PN结形成 P型半导体:掺入三价元素,形成空穴作为多数载流子。N型半导体:掺入五价元素,形成自由电子作为多…