5分钟学会interface(纯标题党)
Golang中的interface(接口)
接口的定义
在 Go 语言中,接口(interface) 是一种特殊的类型,它定义了一组方法,而不关心具体的实现。任何类型只要实现了这些方法,就可以被认为满足这个接口,无须显式声明实现关系。
为什么要用接口呢:
接口的主要作用是抽象行为,让不同的类型可以拥有相同的“能力”,从而实现多态和灵活的设计。
接口的核心特点:
- 接口定义了一组方法,但是不包含方法的具体实现
- 任何类型,只要实现了接口要求的所有方法,就自动被认为实现了该接口,无需额外声明。
- 接口可以作为参数传参,使代码更加灵活。
示例:
我们假设有两种动物,cat和dog,它们都会发出声音。
我们可以定义一个Animal接口,约定所有动物都必须实现MakeSound方法。
package mainimport "fmt"// 定义接口
type Animal interface {MakeSound() // 任何实现了这个方法的类型,都属于Animal接口
}// Dog 结构体
type Dog struct{}// Cat 结构体
type Cat struct{}// Dog 实现 MakeSound 方法
func (d Dog) MakeSound() {fmt.Println("汪汪汪!")
}// Cat 实现 MakeSound 方法
func (c Cat) MakeSound() {fmt.Println("喵喵喵!")
}// 让所有 Animal 类型的对象发出声音
// 此时interface作为函数参数
func Speak(animal Animal) {animal.MakeSound()
}func main() {dog := Dog{}cat := Cat{}Speak(dog) // 输出: 汪汪汪!Speak(cat) // 输出: 喵喵喵!
}
接口的使用
在 Go 语言中,接口变量可以存储实现该接口的任意类型的值。它实际上包含了 两部分:
- 动态类型(dynamic type):存储当前接口变量的具体类型。
- 动态值(dynamic value):存储该类型的具体值。
示例:
// 定义一个接口
type Speaker interface {Speak()
}// 定义两个结构体
type Dog struct{}
type Cat struct{}// Dog 实现 Speak 方法
func (d Dog) Speak() {fmt.Println("汪汪汪!")
}// Cat 实现 Speak 方法
func (c Cat) Speak() {fmt.Println("喵喵喵!")
}func main() {// 定义一个接口变量var animal Speaker// 将 Dog 赋值给接口变量animal = Dog{}fmt.Println("动态类型:", reflect.TypeOf(animal)) // 输出: 动态类型: main.Dogfmt.Printf("动态值: %v\n", animal) // 输出: 动态值: {}// 调用接口方法animal.Speak() // 输出: 汪汪汪!// 将 Cat 赋值给接口变量animal = Cat{}fmt.Println("动态类型:", reflect.TypeOf(animal)) // 输出: 动态类型: main.Catfmt.Printf("动态值: %v\n", animal) // 输出: 动态值: {}// 调用接口方法animal.Speak() // 输出: 喵喵喵!
}
可以看出,当 animal 是一个 Speaker 类型的接口变量,它可以存储任何实现 Speaker 接口的值,比如 Dog{} 和 Cat{}。
每次给 animal 赋值时,动态类型和动态值都会改变:
- 当
animal = Dog{}时:动态类型 是main.Dog - 当
animal = Cat{}时:动态类型 是main.Cat
空接口
接口的零值为nil,一个未初始化的接口变量其值为nil,其不包含任何动态类型或值。
我们可以定义一个空的接口interface{}可以表示任何类型。
空接口常用于需要存储任意类型数据的场景,如泛型容器、通用参数等。
例如:
func printValue(val interface{}) {fmt.Printf("Value: %v, Type: %T\n", val, val)
}func main() {printValue(42) // intprintValue("hello") // stringprintValue(3.14) // float64printValue([]int{1, 2}) // slice
}
实现了打印任意接口的类型和值。
接口的常见用法
- 多态:不同类型实现同一接口,实现多态
- 解耦:通过接口定义依赖关系,降低模块之间的耦合。
- 泛化:使用空接口 interface{} 表示任意类型。
类型断言
在 Go 语言中,接口变量可以存储不同类型的值,但如果我们想要从接口变量中取出原始类型的值,就需要使用类型断言(Type Assertion)。
类型断言的语法
value := iface.(Type)
iface是一个接口变量。Type是我们期望从 iface中取出的具体类型。如果 iface存储的值是 Type,那么断言成功,value变成 Type类型的值。如果 iface存储的值不是 Type,程序会panic(崩溃)。
示例:
func main() {// 定义一个空接口var data interface{}// 赋值为整数data = 100 // 类型断言,将接口变量 data 转换为 int 类型value := data.(int) fmt.Println("断言成功,值为:", value) // 输出: 断言成功,值为: 100
}
为了避免断言失败导致panic,可以使用ok语法:
value, ok := iface.(Type)其中ok是一个bool值,表示是否断言成功。如果成功,则value为断言的值,如果失败value为0,ok为false,不会触发panic。
使用方法:
// 定义一个接口
type Speaker interface {Speak()
}// 结构体 Dog
type Dog struct{}func (d Dog) Speak() {fmt.Println("汪汪汪!")
}// 结构体 Cat
type Cat struct{}func (c Cat) Speak() {fmt.Println("喵喵喵!")
}func main() {var animal Speakeranimal = Dog{} // 赋值一个 Dog 实例// 尝试断言 animal 是否是 Dog 类型dog, ok := animal.(Dog)if ok {fmt.Println("animal 是 Dog 类型")dog.Speak() // 输出: 汪汪汪!} else {fmt.Println("animal 不是 Dog 类型")}// 尝试断言 animal 是否是 Cat 类型cat, ok := animal.(Cat)if ok {fmt.Println("animal 是 Cat 类型")cat.Speak()} else {fmt.Println("animal 不是 Cat 类型") // 输出: animal 不是 Cat 类型}
}
反射
在 Go 语言中,接口变量并不是简单地存储值和类型的结构,它实际上是一个 二元结构,可以用 Go 语言伪代码描述接口的底层结构:
type interfaceStruct struct {dynamicType *Type // 存储类型信息dynamicValue *Value // 存储实际值的指针
}
我们不能直接访问 interfaceStruct.dynamicType 和 interfaceStruct.dynamicValue,因为 Go 不允许直接暴露接口的内部结构, 防止外部代码直接修改接口的底层数据,影响其行为。
假设 Go 允许我们直接访问 interfaceStruct 的 dynamicType 和 dynamicValue,我们就能手动修改接口的类型和值。这样就破坏了 Go 语言的类型系统,导致程序的行为变得不可预测。因此,Go 设计者禁止直接修改接口内部数据,以保证接口的安全性和一致性。
此外 Go 语言的接口在底层是存储指针的,如果程序员随意修改 dynamicValue,可能会导致指针指向无效地址,引发运行时错误(segmentation fault)。
因此我们需要 reflect 包来解析它。通过reflect.TypeOf(x) 获取 变量的动态类型。通过reflect.ValueOf(x) 获取 变量的动态值。
反射提供了一种在运行时获取和操作接口变量的方式,适用于不知道接口具体存储类型的情况。例如:
func main() {var x interface{} = 42 // 存储一个整数// 通过反射获取值和类型t := reflect.TypeOf(x) // 获取动态类型v := reflect.ValueOf(x) // 获取动态值fmt.Println("接口存储的类型:", t) // 输出: intfmt.Println("接口存储的值:", v) // 输出: 42
}
如果你知道接口存的是什么类型,比如 int,那你用 x.(int) 类型断言更高效。但如果你不确定接口的类型,比如你要写一个通用的工具函数(如 JSON 解析器、ORM 框架),那么 反射才是必要的。
反射最重要的应用场景之一是动态处理结构体,比如:
type User struct {Name stringAge int
}func main() {var x interface{} = User{Name: "Alice", Age: 25}// 获取反射类型和值t := reflect.TypeOf(x)v := reflect.ValueOf(x)fmt.Println("结构体类型:", t.Name()) // 输出: User// 遍历字段for i := 0; i < t.NumField(); i++ {field := t.Field(i)value := v.Field(i)fmt.Printf("字段名: %s, 类型: %s, 值: %v\n", field.Name, field.Type, value)}
}
在这个例子中,如果不使用 reflect,我们无法动态地获取 User 结构体的字段名和字段值。而 reflect 允许我们在运行时解析数据结构,这在写通用库时非常有用。
虽然反射很强大,但它有很对缺点
- 性能开销,此外其比普通方法调用慢,因为需要运行时解析类型信息。
- 代码可读性降低,使用 reflect 操作变量比直接调用变量的方法要复杂。
- 类型安全性降低,使用反射时,变量的类型转换依赖 interface{},容易引发 panic。
所以在实践中
- 如果能用 类型断言 (.(type)),就不要用反射
- 反射主要用于 写通用库、框架、工具函数,而不是日常业务逻辑。
- 避免滥用反射,否则会影响性能。
相关文章:
5分钟学会interface(纯标题党)
Golang中的interface(接口) 接口的定义 在 Go 语言中,接口(interface) 是一种特殊的类型,它定义了一组方法,而不关心具体的实现。任何类型只要实现了这些方法,就可以被认为满足这个…...
deepseek实战教程-第五篇支持deepseek的大模型应用安装及使用
目录 一.AnythingLLM 1.2 设置管理 1.3 关联知识库到对话 二.Cherrystudio 2.1 添加知识库文件 2.1.1 cherrystudio 2.1.2 anythingLLM 2.2 和知识库对话 三.AI产品落地之DIFY 3.1 安装Docker 3.2 下载dify压缩包 3.3 文件解压缩 3.4 文件重命名 3.5 设置模型 …...
嵌入式Linux RK3399启动模式及分区技术详解
嵌入式Linux RK3399启动模式及分区技术详解 一、RK3399启动模式分析 RK3399作为瑞芯微推出的高性能嵌入式处理器,其启动模式分为闭源与开源两种方案,核心区别在于前级Loader的实现方式。 1. 闭源启动流程 采用瑞芯微官方提供的闭源固件,流…...
python --face_recognition(人脸识别,检测,特征提取,绘制鼻子,眼睛,嘴巴,眉毛)/活体检测
dlib 安装方法 之前博文 https://blog.csdn.net/weixin_44634704/article/details/141332644 环境: python3.8 opencv-python4.11.0.86 face_recognition1.3.0 dlib19.24.6人脸检测 import cv2 import face_recognition# 读取人脸图片 img cv2.imread(r"C:\Users\123\…...
redis解决缓存穿透/击穿/雪崩
文章目录 1.缓存穿透1.1 概念1.2 解决方案1.2.1 缓存空对象1.2.2 布隆过滤 1.2 店铺查询使用缓存穿透解决方案1.2.1 流程 2.缓存雪崩2.1 什么是缓存雪崩?2.2 雪崩解决方案 3.缓存击穿3.1 什么是缓存击穿?3.2解决方案3.2.1 基于互斥锁解决缓存击穿问题&am…...
特征工程自动化(FeatureTools实战)
目录 特征工程自动化(FeatureTools实战)1. 引言2. 项目背景与意义2.1 特征工程的重要性2.2 自动化特征工程的优势2.3 工业级数据处理需求3. 数据集生成与介绍3.1 数据集构成3.2 数据生成方法4. 自动化特征工程理论基础4.1 特征工程的基本概念4.2 FeatureTools库简介4.3 关键公…...
哈希表简单例子
一、题意 给定一个整数数组,判断数组中是否存在重复的元素。如果存在一值在数组中出现至少两次,函数返回 True ;如果数组中每个元素都不相同,则返回 False 。 输入: [1, 2, 3, 1] 输出: True 输入: [1, 2, 3, 4] 输出: False …...
利用GitHub Pages快速部署前端框架静态网页
文章目录 前言GitHub Pages 来部署前端框架(Vue 3 Vite)项目1、配置 GitHub Pages 部署2、将项目推送到 GitHub3、部署到 GitHub Pages4、访问部署页面5、修改代码后的更新部署顺序 前言 可以先参考: 使用 GitHub Pages 快速部署静态网页: …...
《TCP/IP网络编程》学习笔记 | Chapter 22:重叠 I/O 模型
《TCP/IP网络编程》学习笔记 | Chapter 22:重叠 I/O 模型 《TCP/IP网络编程》学习笔记 | Chapter 22:重叠 I/O 模型理解重叠 I/O 模型重叠 I/O本章讨论的重叠 I/O 的重点不在于 I/O 创建重叠 I/O 套接字执行重叠 I/O 的 WSASend 函数进行重叠 I/O 的 WSA…...
Skynet 中 snlua 服务启动整体流程分析
前言: 在 Skynet 中,Lua 扮演了极其重要的角色。Skynet 大多数业务逻辑都跑在一个个 Lua 服务里,而能够将 Lua 环境嵌入到 Skynet 框架下,并与 Skynet 消息调度机制完美结合,正是 snlua 服务所承担的核心功能。 本文将…...
python每日十题(10)
在Python语言中,源文件的扩展名(后缀名)一般使用.py。 保留字,也称关键字,是指被编程语言内部定义并保留使用的标识符。Python 3.x有35个关键字,分别为:and,as,assert&am…...
基于大模型预测的初治菌阳肺结核诊疗方案研究报告
目录 一、引言 1.1 研究背景与意义 1.2 研究目的 二、初治菌阳肺结核概述 2.1 疾病定义与病理机制 2.2 流行病学特征 2.3 传统诊疗方法与局限性 三、大模型在初治菌阳肺结核预测中的应用原理 3.1 大模型技术简介 3.2 数据收集与预处理 3.3 模型构建与训练 3.4 模型…...
嵌入式系统应用-音乐播放器-按键版本
音乐播放器-按键版本 1 背景介绍1.1 导入音乐播放器1.2 导入按键扫描按键包 2 功能设计2.1 需求分析2.2 程序架构设计2.3 相关知识储备 3 代码编写3.1 led代码实现3.2 按键扫描3.3 音乐播放线程 4 低功耗设计4.1 睡眠模式4.2 停止模式4.3 待机模式 1 背景介绍 这个音乐播放器分…...
LabVIEW液压振动锤控制系统
在现代工程机械领域,液压振动锤的高效与精准控制日益显得重要。本文通过LabVIEW软件,展开液压振动锤启停共振控制技术的研究与应用,探讨如何通过改进控制系统来优化液压振动锤的工作性能,确保其在复杂工况下的稳定性与效率。 …...
简单介绍My—Batis
1.什么是My—Batis? My—Batis是一个持久层框架,提供了sql映射功能,能方便的将数据库表和java对象进行映射,通过My—Batis可以将项目中的数据存储在数据库中,以便我们进行调用。值得注意的是My—Batis和spring不是一回…...
ALTER TABLE SHRINK SPACE及MOVE的区别与适用场景
以下是 Oracle 数据库中三个收缩表空间命令的对比: 1. ALTER TABLE table_name SHRINK SPACE; 作用:直接重组表数据并移动高水位线(HWM),释放未使用的空间到表空间。 影响: 会锁表&#…...
车载通信方案为何选择CAN/CANFD?
摘要 随着汽车电子技术的飞速发展,车载通信系统在车辆的智能化、网联化进程中扮演着至关重要的角色。控制器局域网络(CAN)及其扩展版本CANFD凭借其卓越的可靠性、高效的数据传输能力和强大的抗干扰特性,成为现代汽车通信架构的核心…...
docker远程debug
1. 修改 Java 启动命令 在 Docker 容器中启动 Java 程序时,需要添加 JVM 调试参数,jdk8以上版本 java -agentlib:jdwptransportdt_socket,servery,suspendn,address*:5005 -jar your-app.jar jdk8及以下版本: java -Xdebug -Xrunjdwp:tra…...
rosbag|ROS中.bag数据包转换为matlab中.mat数据类型
代码见代码 msg_dict中设置自定义消息类型 test_config中设置需要记录的具体的值 test_config中topic_name以及message_type照搬plotjuggler打开时的参数 最后生成.mat文件在matlab中进行使用...
Java编程思想:为何有时要将子类对象赋值给父类引用
为何有时要将子类对象赋值给父类引用,用父类来进行实例化? 这就要说多态的优势: 代码的扩展性和降低耦合度,而不是完全避免修改代码。 TuXing t new Changfangxing(); Changfangxing k (Changfangxing)t;原因1: 代码可拓展性 …...
pytest-xdist 进行高效并行自动化测试
pytest-xdist 的核心功能是通过多进程分发测试任务,每个进程独立运行测试,确保测试隔离。2025 年 3 月 25 日,pytest-xdist 在 GitHub 上已有超过 1,200,000 次下载,表明其在测试社区中的广泛接受。 在自动化测试中,随…...
位置编码再思考
最近在做多模态,发现基于 transformer 的多模态,position embedding 是一个非常重要的内容,而且还没有统一方案,先暂做记录,几篇还不错的博客: Transformer学习笔记一:Positional Encoding&…...
Deepseek API+Python 测试用例一键生成与导出 V1.0.3
** 功能详解** 随着软件测试复杂度的不断提升,测试工程师需要更高效的方法来设计高覆盖率的测试用例。Deepseek API+Python 测试用例生成工具在 V1.0.3 版本中,新增了多个功能点,优化了提示词模板,并增强了对文档和接口测试用例的支持,极大提升了测试用例设计的智能化和易…...
[c语言日寄MAX]深度解析:大小端字节序
【作者主页】siy2333 【专栏介绍】⌈c语言日寄MAX⌋:这是一个专注于C语言刷题的专栏,精选题目,搭配详细题解、拓展算法。从基础语法到复杂算法,题目涉及的知识点全面覆盖,助力你系统提升。无论你是初学者,还…...
Android ADB工具使用教程(从安装到使用)
目录 ADB工具介绍 什么是ADB? 组成 主要功能 ADB工具安装与连接设备 WIFI连接,提示计算机积极拒绝10061 WIFI成功连接后,拔掉数据线显示offline 提示adb版本不一致编辑 ADB工具使用 ★日志操作命令 adb logcat:抓取日志 日志格式…...
开个坑记录一下树莓派4B部署yolo的一些问题
问题一:操作系统与内核信息 这个问题困扰了我一天半,下载的时候显示的信息是aar64的系统,但是这并无意味着一个问题,那就是你的操作系统是64位的。需要采用如下的指令查看: getconf LONG_BIT 我在树莓派得出来的操作…...
基于SSM框架的线上甜品销售系统(源码+lw+部署文档+讲解),源码可白嫖!
摘要 网络技术和计算机技术发展至今,已经拥有了深厚的理论基础,并在现实中进行了充分运用,尤其是基于计算机运行的软件更是受到各界的关注。加上现在人们已经步入信息时代,所以对于信息的宣传和管理就很关键。因此网上销售信息的…...
基于 FPGA的HLS技术与应用
1、hls简介 HLS ( high level synthesis )即高层次综合,主要是利用高级编程语言实现算法。 2、循环优化 约束语法: #pragma HLS unroll #pragma HLS PIPELINE II1 绝大多数循环都以串行的方式执行,这种执行方…...
Redis原理:Monitor 实现
在调用 Redis 的 MONITOR 命令后,可以在对应的客户端上实时查看服务器的执行情况。今天,我们将从源码的角度来深入探讨 MONITOR 机制是如何处理这些请求以及如何将数据反馈给用户的。 MONITOR 命令的实现 Redis 中所有命令的具体实现细节都可以在其源代…...
计算机工具基础(七)——Git
Git 本系列博客为《Missing in CS Class(2020)》课程笔记 Git是一种分布式版本控制系统,被其跟踪的文件可被查询精细到行的修改记录、回退版本、建立分支等 模型 一般流程:工作区 → \to →暂存区 → \to →仓库(本地 → \to →远端) 工作区࿱…...
