Go基础篇:接口

news2024/12/31 4:35:01

目录

  • 前言✨
  • 一、什么是接口?
  • 二、空接口 interface{}
    • 1、eface的定义
    • 2、需要注意的问题
  • 三、非空接口
    • 1、iface的定义
    • 2、itab的定义
    • 3、itab缓存

前言✨

前段时间忙着春招面试,现在也算告一段落,找到一家比较心仪的公司实习,开始慢慢回归状态,这后面几章我会学习go1.19版本的语言特性或者机制:类型系统、接口、断言以及反射的内容,也算是补上之前没有深入底层的内容。

一、什么是接口?

Go语言中的接口(interface)是一种类型,它定义了一组方法的集合,但没有具体的实现。接口可以被任何类型实现,只要该类型实现了接口中定义的所有方法。这种设计方式使得Go语言具有很高的灵活性和可扩展性。

// 定义一个接口,包含一个String方法
type Stringer interface {
	String() string
}

// 定义一个结构体,实现Stringer接口
type Person struct {
	Name string
	Age  int
}

func (p Person) String() string {
	return fmt.Sprintf("%s (%d)", p.Name, p.Age)
}

func TestInterface(t *testing.T) {
	// 定义一个接口变量,只能存储实现了Stringer接口的值
	var str Stringer
	// str = 42 // 编译错误,int类型没有实现Stringer接口
	// str = "hello" // 编译错误,string类型没有实现Stringer接口
	str = Person{"Bob", 30}
	fmt.Println(str) // Bob (30)
}

在这里插入图片描述

Go语言接口的特点是:

  • 接口是一种抽象的类型,它只定义了一组方法,而不指定具体的实现。
  • 接口是隐式实现的,也就是说,任何类型只要实现了接口的所有方法,就可以被认为是该接口的实例,而不需要显式地声明。
  • 接口可以组合,也就是说,一个接口可以包含另一个接口的所有方法,一个接口可以由多个接口的方法组成,从而实现接口的多态。

Go语言接口的优点是:

  • 接口可以提高代码的复用性和可维护性,因为它可以将不同类型的对象抽象为统一的接口,从而降低了代码之间的耦合度。
  • 接口可以提高代码的灵活性和扩展性,因为它可以支持多种实现方式,从而增加了代码的可变性和可选择性。
  • 接口可以提高代码的测试性和可测性,因为它可以方便地使用模拟对象或桩对象来替代真实对象,从而简化了单元测试和集成测试。

二、空接口 interface{}

Go语言中的接口是一种类型,它定义了一组方法的集合,接口可以分为空接口和非空接口。空接口(interface{})是一种特殊的接口类型,它没有任何方法,因此可以表示任何类型的值。非空接口则是指至少有一个方法的接口类型。

Jordan Oreilli 对空接口的一个很好的定义:

接口是两件事物:它是一组方法,但它也是一种类型。
空接口 interface{} 类型是没有方法的接口。由于 Go 语言没有 implements 关键字,所有类型都至少实现零个方法,并且接口是隐式实现的,所有类型都满足空接口。

在这里插入图片描述

1、eface的定义

在 Go 的泛型未敲定前,空接口和断言广泛用于实现泛型,空接口的底层数据结构是 eface,它是一个结构体,包含两个字段:_type和data。

eface结构体定义如下:

type eface struct {
	_type *_type
	data  unsafe.Pointer
}

其中,_type是一个指向类型信息的指针,它包含了类型的名称、大小、对齐方式等信息。(不了解的请看上一节篇《Go基础篇:类型系统》),data是一个指向实际值的指针,它可以存储任何类型的值。

举个简单的例子:

type MyString string

// 输出val的值和类型,这里只是稍微用到了反射reflect的TypeOf方法获取类型,后面我会详细讲的
func Print(val interface{}) {
	fmt.Println(val, reflect.TypeOf(val))
}

func TestPrint(t *testing.T) {
	name := "linzy1"
	Print(name)
	var Myname MyString = "linzy2"
	Print(Myname)
}

在这里插入图片描述

例子中在空接口被赋值string类型和自定义类型时的值虽然相同,但是类型却不同,且都不是空接口 interface{} 类型。这是为什么呢?

当我们使用空接口来存储一个值时,Go语言会将该值的类型信息和实际值分别存储在eface结构体的_type和data字段中。在需要使用该值时,Go语言会根据_type字段中的类型信息,将data字段中的值转换为相应的类型,并进行相应的操作。

在这里插入图片描述

具体来说,当一个空接口变量被赋值为string类型的值时,Go语言会将该值的类型描述信息存储在该变量的_type字段中。这个类型描述信息包含了string类型的名称、大小、对齐方式等信息,可以通过反射包中的TypeOf()函数获取。

需要注意的是,由于空接口可以存储任何类型的值,因此在使用空接口时需要进行类型断言或类型转换,以确保程序的正确性。

2、需要注意的问题

空接口可以存储任何类型的值,但是 []interface{} 空接口切片类型不一样,[]interface{} 可以存储任何类型的值,但是不能直接赋值切片或者切片转换。

举个简单的例子:

func PrintAll(vals []interface{}) {
	fmt.Println("vals Type is:", reflect.TypeOf(vals))
	for _, val := range vals {
		fmt.Println(val, reflect.TypeOf(val))
	}
}

func TestPrint(t *testing.T) {
	names := []string{"stanley", "david", "oscar"}
	// ./interface_test.go:144:11: cannot use names (variable of type []string) as type []interface{} in argument to PrintAll
	// PrintAll(names)
	ns := make([]interface{}, len(names))
	for i, name := range names {
		ns[i] = name
	}
	PrintAll(ns)

	ns = []interface{}{"linzy", 32, 1.68}
	PrintAll(ns)
}

在这里插入图片描述

在Go语言中,[]interface{}和[]string是两种不同的类型,它们之间不能直接赋值或转换。虽然[]interface{}可以存储任何类型的值,包括string类型,但是它本身并不是string类型的切片,因此不能直接赋值给[]string类型的变量。

最核心的点在于 slicetype 中的 elem 类型描述不同,所以他们是两种不同的类型。

type slicetype struct {
	typ  _type
	elem *_type
}

三、非空接口

非空接口是指具有实际类型的接口,想要赋值给非空接口类型必须要实现该接口的所有方法,也就是说,它不是空接口。

1、iface的定义

在Go语言中,非空接口的底层数据结构是iface,它是一个结构体,定义如下:

type iface struct {
	tab  *itab
	data unsafe.Pointer
}

其中,tab是一个指向方法表的指针,data是一个指向实际对象的指针。

2、itab的定义

itab是一个结构体,它定义了接口类型和实现类型之间的关系,包括接口类型的方法集合、实现类型的方法集合和方法表等信息。itab的定义如下:

type itab struct {
    inter *interfacetype
    _type *_type
    hash  uint32
    _     [4]byte
    fun   [1]uintptr
}

其中,inter是一个指向接口类型的指针,_type是一个指向动态类型的指针,hash是实现类型的哈希值,fun是一个指向方法表的指针数组,指向的是动态类型实现接口方法的地址

interfacetype和_type都是类型对象,它们包含了类型的名称、大小、对齐方式、方法集合等信息。uintptr是一个无符号整数类型,它可以存储指针类型的值。

itab结构体的大小是可变的,因为它的最后一个字段fun是一个指针数组,它的长度取决于实现类型的方法集合的大小。当实现类型的方法集合发生变化时,Go语言会重新生成一个新的itab结构体,并将其添加到缓存中以便后续可以复用。

举个简单的例子:

func TestReadWriter(t *testing.T) {
	// 定义io.ReadWriter接口类型的变量rw,需要实现Read和Write两个方法
	var rw io.ReadWriter
	f, _ := os.Open("linzy.txt")
	defer f.Close()
	rw = f

	// os.Stat函数获取文件的大小
	Stat, _ := f.Stat()

	tmp := make([]byte, Stat.Size())
	rw.Read(tmp)
	fmt.Println(string(tmp))
}

rw 赋值后
赋值的变化过程:

  • 当你将f \*os.File类型变量赋值给rw变量时,Go语言会生成一个新的itab结构体。它的inter字段指向io.ReadWriter接口类型的描述信息,_type字段指向*os.File类型的描述信息,fun字段指向一个方法表,其中包含了io.ReadWriter接口类型的需要的所有方法,拷贝的是方法地址。
  • Go语言会使用一个全局的哈希表来缓存itab结构体,这个itab结构体会被缓存起来以便后续可以复用。
  • 当你调用rw.Read(tmp)方法时,Go语言会根据rw变量对应的itab指针找到对应的方法表,并调用其中的Read方法。

总结:
当你将一个实现了接口类型的值赋给接口类型的变量时,Go语言会根据实际对象的类型和接口类型的方法集合生成一个新的itab结构体,并将其赋值给接口类型变量对应的itab指针。
这个itab结构体包含了接口类型的方法集合、实现类型的方法集合和方法表等信息,通过这些信息可以实现接口方法的调用。

3、itab缓存

一个非空接口类型和一个动态类型就可以确定一个itab的内容,容易出现重复的itab结构体。Go为了避免重复生成itab结构体的开销,并且可以减少内存的使用,提高程序的性能。
itab可复用

Go语言会使用一个全局的哈希表来缓存itab结构体,这个哈希表的键是一个由<接口类型, 动态类型>组成的key,值是对应的*itab结构体的指针。当我们创建一个非空接口变量时,Go语言会先在缓存中查找对应的itab结构体,如果找到了就直接使用,否则就动态生成一个新的itab结构体,并将其添加到缓存中以便后续可以复用。

在这里插入图片描述
但是这里说的哈希表跟go的map哈希表不相同,itab设计的哈希表结构更简单:

type itabTableType struct {
	size    uintptr             // length of entries array. Always a power of 2.
	count   uintptr             // current number of filled entries.
	entries [itabInitSize]*itab // really [size] large
}

需要一个itab时,会首先去itabTable里查找,计算哈希值时会用到接口类型(itab.inter)和动态类型(itab._type)的类型:

func itabHashFunc(inter *interfacetype, typ *_type) uintptr {
	// compiler has provided some good hash codes for us.
	return uintptr(inter.typ.hash ^ typ.hash)
}

如果能查询到对应的itab指针,就直接拿来使用。若没有就要再创建,然后添加到itabTable中。

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

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

相关文章

Linux防火墙之iptables(上)

目录 一、iptables防火墙的相关知识 1&#xff09;防火墙的概念 2&#xff09;iptables的简介 3&#xff09;netfilter/iptables 的关系 netfilter iptables 二、iptables中的四表五链 1 &#xff09;四表五链的关系 2&#xff09;iptables中的四表 3&#xff0…

0基础要怎么开始学习c++?

先放一张学习线路图&#xff1a; 对于初学者看到这张线路图可能有点晕。实际上耐心一点&#xff0c;有足够的毅力还是可以自学攻克C这门语言的。 一、C语言 基础内容就是两块&#xff1a;基础语法和一个小游戏案例 进阶一点也是两块内容&#xff1a;C高级和数据结构。包括一…

怎么画骑鹅旅行记思维导图?办公常备工具

骑鹅旅行记不仅是一本神奇的奇幻童话&#xff0c;更是一本寓教于乐的启蒙读物&#xff0c;引导着孩子们学习正确的价值观和道德观。在开始制作骑鹅旅行记思维导图之前&#xff0c;我们需要先整理好故事情节&#xff1a;小男孩尼尔斯因捉弄动物而被一个小精灵变成了拇指大小&…

中国社科院与美国杜兰大学金融管理硕士——与时间赛跑,充分利用每一分钟

不管你愿不愿意&#xff0c;时间总是在不经意间流去。林清玄在《和时间赛跑》中写道&#xff1a;“虽然我知道人永远跑不过时间&#xff0c;但是可以比原来快跑几步。那几步虽然很小很小&#xff0c;但作用却很大很大”。是的&#xff0c;我们需要与时间赛跑&#xff0c;充分利…

就业内推 | 上市公司招网工,最高25k*14薪,六险一金

01 锐捷网络 招聘岗位&#xff1a;网络工程师 职责描述&#xff1a; 1、承接本产品线&#xff08;无线或数通&#xff09;所有咨询、故障、网络变更等业务&#xff0c;响应内外部客户的业务响应需求&#xff0c;需要值班。 2、同时作为产品线技术力的核心&#xff0c;需要负责…

购票API接口商品详情信息API

购票网是一个在线购票平台&#xff0c;为音乐会、演唱会、话剧、体育比赛等各类娱乐活动提供门票销售服务。通过大麦网&#xff0c;用户可以轻松购买心仪的演出门票&#xff0c;并享受到良好的购票体验。 为了让更多用户了解到大麦网的商品详情&#xff0c;并能够方便地获取相…

内防泄密重要,还是外防窃密重要?

内防泄密是组织为防止内部敏感信息未经授权泄露所采取的各种管理与技术措施的总称。它主要针对内部人员的信息访问与操作行为进行管控,减少故意或疏忽泄密事件的发生几率。 内防泄密的工作&#xff0c;通常包括员工管理、权限管控、监控检查、分级保护、离岗管控、技术防护、事…

高赞热门,这是B站值得一看的商单作品

3月30日、5月11日&#xff0c;第十届中国网络视听大会和首届上海网络视听内容创作者大会先后开幕。 在现场&#xff0c;陈睿共发表了《科技与青年的双向奔赴》、《B站&#xff0c;高质量内容的沃土》两次主题演讲。陈睿分享道&#xff0c;“优质的内容和创作者&#xff0c;是能…

主动防御更安全 混沌工程助力企业系统稳定

数字经济时代背景下&#xff0c;各个行业迎来数字化转型浪潮&#xff0c;随着企业对于云计算的理解和实践不断深入&#xff0c;数据作为生产要素参与到市场分配当中&#xff0c;全面渗透金融、政务、医疗、教育、能源等各个领域&#xff0c;已成为企业发展乃至国家发展的重要战…

GPU机器docker环境离线安装

秋风阁(https://focus-wind.com/) 文章目录 docker 环境离线二进制安装下载二进制包解压.tgz包迁移文件到/usr/bin/目录下启动docker手动启动dockersystemctl启动dockerdocker.servicedocker.socketcontainerd.service设置开机重启启动docker服务 离线安装nvidia-docker&#x…

GPT逆向:高效解读小红书文案生成器的内部逻辑

文章目录 前言一、什么是小红书文案生成器二、具体步骤总结 前言 关注我的很多同学都会写爬虫。但如果想把爬虫写得好&#xff0c;那一定要掌握一些逆向技术&#xff0c;对网页的JavaScript和安卓App进行逆向&#xff0c;从而突破签名或者绕过反爬虫限制。 最近半年&#xff…

密码学基本原理和发展——古典密码学

密码技术最早起源于公元前404年的希腊&#xff0c;此后密码大致经历了古典密码、近代密码和现代密码三个阶段。 1.古典密码 古典密码&#xff08;公元前五世纪&#xff5e;19世纪末&#xff09;代表性的是滚桶密码、棋盘密码和凯撒密码。 1.1滚筒密码 滚筒密码原理为信息发送…

5月17号软件资讯更新合集....

Erlang/OTP 26 正式发布 Erlang/OTP 26.0 已正式发布。 Erlang 是一种通用的并发函数式程序设计语言。Erlang 也可以指 Erlang/OTP 的通称&#xff0c;开源电信平台 (OTP) 是 Erlang 的常用执行环境及一系列标准组件。 这是一个重要版本更新&#xff0c;包含许多新特性、改进…

Python带你实现批量自动点赞小程序

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 所用知识点: 动态数据抓包 requests发送请求 json数据解析 开发环境: python 3.8 运行代码 pycharm 2022.3 辅助敲代码 requests 请求模块 &#xff0c;第三方&#xff0c;需安装 win R 输入cmd 输入安装命令 pip inst…

oa是什么意思?oa系统哪个好用?

一、oa是什么意思 oa&#xff08;Office Automation办公自动化&#xff09;是一种将智能化科技应用于企业管理中的应用系统。它可以通过电脑网络、互联网等技术手段&#xff0c;将企业的各种业务流程、各种业务数据进行集成和处理&#xff0c;将各种业务流程和各种业务数据统一…

centos安装kurento

前沿 本文章使用docker安装&#xff0c;为个人安装记录 一、安装kurento 1.拉取镜像 # 搜索kurento docker search kurento# 拉取 docker pull kurento/kurento-media-server# 查看镜像是否拉取成功 docker images2.运行kurento服务 # 运行 docker run -d --name kms -p 8…

【Python Mayavi】零基础也能轻松掌握的学习路线与参考资料

Mayavi是Python的一个科学可视化库&#xff0c;主要用于三维&#xff08;3D&#xff09;科学数据的可视化。Mayavi具有优秀的可视化效果、交互性良好、易于使用、能够输出高质量的图像和动画等特点。对于需要展示科学数据的学者和研究人员来说&#xff0c;Mayavi是一个非常好的…

手撕代码带你领略Spring如何集成Mybatis

引言 有关于Spring整合Mybatis其实一直是一个很具有典型代表性的Spring实际应用&#xff0c;今天就带着大家由浅入深手撸一遍整合的代码 手撕代码 准备工作 首先准备两个Mapper作为今天演示的操作对象 import org.apache.ibatis.annotations.Select;public interface User…

动态规划——第N个泰波那契数

题目链接 leetcode在线oj题——第N个泰波那契数 题目描述 泰波那契序列 Tn 定义如下&#xff1a; T0 0, T1 1, T2 1, 且在 n > 0 的条件下 Tn3 Tn Tn1 Tn2 给你整数 n&#xff0c;请返回第 n 个泰波那契数 Tn 的值。 题目示例 示例1 输入&#xff1a;n 4 输出…

❤ cannot read properties of null(reading appendChild)解决办法

❤ 操作元素报&#xff1a;cannot read properties of null(reading appendChild)解决办法 1、场景&#xff1a; 写的一个js渲染&#xff0c;但是出了个小问题&#xff0c;cannot read properties of null(reading appendChild)报错。 <div id"divps" class&qu…