iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > GO >goalng 结构体 方法集 接口实例详解
  • 123
分享到

goalng 结构体 方法集 接口实例详解

2024-04-02 19:04:59 123人浏览 八月长安
摘要

目录一 前序二 事出有因errors.As 方法签名三 结构体与实例的数据结构1. 结构体类型2. 实例3 方法调用3.1 方法表达式3.2 值实例调用所有方法3.3 指针实例调用所

一 前序

很多时候我们以为自己懂了,但内心深处却偶有困惑,知识是严谨的,偶有困惑就是不懂,很幸运通过大量代码的磨练,终于看清困惑,并弄懂了。

本篇包括结构体,类型, 及 接口相关知识,希望对大家有所启发。

二 事出有因

golang也有三四个年头了,大小项目不少,各种Golang书籍资料也阅无数,今天突然被一个报错搞懵了。演示代码如下:

type MyErr struct{}
func(this MyErr)Error()string{
    return "myerr"
}
func main(){
    var Err *MyErr
    errors.As(MyErr{},Err) //这一句
}

errors.As是标准库里的判断错误类型的一个简单函数,按照如上写法他运行报错,报错内容如下:

panic: errors: target must be a non-nil pointer
goroutine 1 [running]:
errors.As({0x107e280, 0x11523f8}, {0x104f3e0, 0x11523f8})
        D:/GO/src/errors/wrap.go:84 +0x3e5
GitHub.com/pkg/errors.As(...)
        D:/GO/gopath/pkg/mod/github.com/pkg/errors@v0.9.1/go113.go:31
main.main()
        H:/infORMation/demo1/main.go:19 +0x31
exit status 2

errors.As 方法签名

func As(err error, target interface{}) bool

起初我没有太关心报错结果,我第一感觉是指针类型实现接口有问题,于是又改实现方法,又折腾变量,有时候ide提示方法未实现,有时候运行报错,偶有成功,为啥成功我也不知道。

突然我发现我对接口一直都停留在会用的基础上,所有结构体方法接受者都用指针,所有结构体实例都用指针,一方面保证接口方法都能实现,另一方面减少对象拷贝,减少内存用量。

于是带着这个问题开始了刨根问题。在查阅资料中又发现了新的问题。

  • 指针方法集包括结构体所有方法,值方法集不包括指针方法集,为啥一个指针或者一个值实例可以调用所有方法。方法集的本质是啥?
type T struct{}
func (t T) Get() {
	fmt.Println("this is Get")
}
func (t *T) Set() {
	fmt.Println("this is set")
}
func main() {
	var a T
	a.Set()
	a.Get()
	(&a).Get()
	(&a).Set()
}
  • 为啥有时候指针对象无法调用非指针方法?如开始的err例子。
  • 嵌入类型的结构体,面对指针和值实例,方法集规律是啥?
  • 接口到底是啥?nil又是啥?
  • 结构体体结构到底是怎么样的?
  • 实例结构又如何?怎么通过实例找到相应的方法?
  • 。。。

三 结构体与实例的数据结构

1. 结构体类型

结构体就是一个模板,用于生成实例用的,包括最基本的属性集,值的方法集,指针方法集。

type T struct{
    Num int
}
func (t T) Get() int{
	fmt.Println("this is Get")
        return t.Num
}
func (t *T) Set(i int) {
	fmt.Println("this is set")
        t.Num = i
}

这就是一个定义的结构体。

func (t T) Get() 该方法的接受者 t是一个实例值,所以该方法称为值方法。

func (t *T) Set() 该方法的接受者 t 是一个指针,所以该方法成为指针方法。

2. 实例

实例就是结构体实例化后的变量,用T类型说明。

    var a T
    var b *T
    var c = T{1}
    var d = &T{1}

这四种实例定义发生了什么?数据结构如何?

实例数据结构主要包括三部分。

  • 头部信息,说明实例大小,实例是指针还是非指针等
  • 值,指针时候是指向实例的地址,非指针时候是具体的属性值
  • 类型

实例a是一个空结构体实例,其特点是a虽然没有显示赋值,但是会默认创建一个a实例,其中的属性都是"类型零值"。

实例b是一个指针类型,特点是没有被初始化,指针未任何实例。

实例c是一个显示赋值的实例,和a区别就是Num初始化值不再是"类型零值",而是1。

实例d就有点复杂了,他会有个实例及指针两种数据,指针指向实例。实例初始化非"类型零值"。

关于图中地址的说明,所有数据结构最终都是内存中的一段连续代码,都有开始地址,其他需要使用该数据的地方都是通过该地址找到这段内存信息的。当然要说到代码,内存,虚拟地址,堆栈,程序运行,会有很多内容,这里只要知道通过地址能找到该数据信息即可。

注意,上图也仅仅只是示意图,帮助理解。其中类型指针实现并不是一个真的指针而是一个关于类型元信息的偏移地址。

3 方法调用

结合上面的图,说一下方法调用问题。为啥值方法和指针方法都可以调用所有方法,并且都能成功,并且修改都可以成功。

	a.Get()
	a.Set(2)
	// b.Get() 编译器通过 运行不通过
	b.Set(3)
	c.Get()
	c.Set(4)
	d.Get()
	d.Set(5)

3.1 方法表达式

实例的方法调用的本质是函数,类似python,编译器调用该函数时候默认的第一个参数是实例值或者实例指针。

T.Get(a)

(*T).Set(b,2)

通过类型直接调用类型中的函数,这就是方法表达式调用。真实的实例调用,也是通过找到类型并调用类型的方法。关于"方法表达式"这个词出自《go语言核心编程》第三章,类型系统,有兴趣的可以看看。

方法表达式有个特点,就是不会被自动转换,通过方法表达式可以清楚知道值方法集或指针方法集是否有该方法。

在没有说到接口之前,判断一个方法是否属于方法集用这个方法表达式是比较方便的。

3.2 值实例调用所有方法

a和c本质是一样的,只是初始值不一样。拿c做例子进行讲解。

c.Get() == T.Get(a)

上边代码这个不用解释太多,就是c实例通过类型信息找到相关的值方法进行调用。

c.Set() == (&c).Set(4) == (*T).Set(c,4)

上边代码 c中对应的T中方法并不包含Set方法。

T.Set() 你会发现编译器会报错 T中没有Set方法

但*T中有方法Set,这时候编译器会生成一个*c,指针对象,在通过该对象调用Set方法。虽然通过指针对象调用Set但确实把c对象中的Num修改成功了,因为指针指向的正是c实例。如下图:

这就是为啥实例方法集中没有Set方法,也可以调用Set方法,编译器进行了自动转换,而这样设计是合理的,通过Set操作,c实例中的Num确实变成4,符合预期。

3.3 指针实例调用所有方法

b和d都是指针实例,看看上图关于b和d的数据结构示意图,这两个图里最大的区别就是有没有匿名实例,b因为是空指针没有指向任何实例,所以只有类型信息。

编译器知道你是个指针,查看类型中的所有方法,包括值方法和指针方法,有Set和Get所以编译通过,但是在运行的时候,因为是空指针,无法找到值的方法Get,所以运行时候报错 panic: errors: target must be a non-nil pointer

d因为指向一个实例,所以顺着这个实例找到Get方法进行调用,这都是编译器自动进行的。

d.Get() == (&d).Get() == (T).Get(*d)

通用使用方法表达式,也可以知道指针方法集中是没有Get方法的。

(*T).Get() 编译器不会通过 说明指针方法集中确实没有Get函数 所以只能通过转化成实例来调动Get方法

这种自动转化及操作的结果也是符合预期的,拿到了d指针指向的实例的数据。

3.4 空指针无法调用值方法

在回过头看最初的err问题,原因就出在给了一个空指针,要通过一个空指针找到一个值方法,但是运行时候无法找到,所以panic了

四 接口

正常情况下,值实例还是指针实例都可以调用所有方法,并且修改都可以成功,那为什么要区分值的方法集和指针的方法集,这就不得不提接口。

方法集是给接口准备。

方法集是"符合预期"的。

可以说因为接口的需要才会有方法集概念,只有接口中的方法与方法集中的方法相匹配时候,该方法集的实例才是该接口的实现实例。

可是问题又来了,明明一个实例对象不管是指针还是非指针实例都可以执行全部的方法,技术上完全可以实现,为什么还要区分指针非指针方法?这是因为"不符合预期",为什么,为什么"不符合预期",看下边解释。

1 接口数据结构

要说明白接口和方法集的关系不是一件容易的事,先从接口结构说起。

接口类型跟struct类型不同,字面上看,接口只有方法头,没有属性。

接口实例跟一般的struct实例也不一样,它是一种动态的实例,只有接口实例被具体实例(值或指针)赋值的时候,接口实例才能确定。如下图。

接口实例跟结构体实例类似,也包括两部分,值和类型。

接口中的值是动态的,当被具体结构体实例赋值时候才能确定该值。该值就是结构体实例的值的拷贝,当实例是非指针时候会把数据都拷贝过来,当是实例是指针时候会把指针拷贝过来。golang中一切赋值都是拷贝,包括接口赋值,也是因为拷贝才会有很多"不符合预期的"结果。

接口中的类型包括动态类型和自身的接口类型,自身类型没啥好说的,看上图就明白了,主要是动态类型,这个是存储了当前赋值的结构体实例的类型。

2 接口赋值

以下面的接口赋值代码进行说明解释。

package main
type I interface {
	Get() int
	Set(i int)
}
type T struct {
	Num int
}
func (t T) Get() int {
	return t.Num
}
func (t *T) Set(num int) {
	t.Num = num
}
func main() {
	var a T
	var b *T
	var c = T{}
	var d = &T{}
	var ia I = a //编译不通过 方法集不匹配
	var ib I = b //编译通过 运行会报错 panic: runtime error: invalid memory address or nil pointer dereference
	var ic I = c //编译不通过 方法集不匹配
	var id I = d
}

例子代码很简单,就是一个接口类型I,一个struct类型T,其实现了值Get方法,指针Set方法。

上边代码中a,b,c,d已经在上部分进行过讲解了。

ia,ib,ic,id赋值过程如下图:

值方法集

ia,ic接口对象其实在编辑阶段IDE就会给出报错提示,实例和接口不匹配,因为a和c实例方法集中只有一个Get函数,可以通过前边提到的"表达式方法"进行验证,这里通过IDE提示也知道缺少Set函数。

那么问题来了,在第一部分单独a,c对象是可以调用所有方法,这里接口实现为啥要弄出个方法集进行限制?因为"拷贝"和"不符合预期"。

假设a,c可以成功赋值给接口ia,ic,赋值后a,c中的数据会拷贝到接口的动态值区域,要是成功执行了Set函数,将接口动态值区域的数据进行了修改,那原来的a,c中的数据并未改变,这个是"不符合预期的"。所以干脆就不允许这么操作。

更常用的"不符合预期"解释代码是当接口是参数值时候。如下代码。

func DoT(a I) {
	a.Set(11)
}
func main(){
    ...
    DoT(ic)
    fmt.Println(ic.Get())
}

DoT函数用I做参数,内部对I进行了操作,用ic或者ia做参数,如果可以成功,最后打印ic或者ia中的值,并未改变,这不符合预期,很令人困惑。这段原理可参考<<go核心编程>>第三章类型系统相关描述。

指针方法集

ib和id都是指针类型,其方法集包括所有方法,即Get和Set,其中Get是通过编译器自动转化进行间接调用,值实例不允许调用指针实例的方法集是因为"不符合预期",那指针实例就允许调用值实例的方法了?是的,允许,因为"符合预期"。

还用下面的代码做解释。

func DoT(a I) {
    a.Set(a.Get()++)
}
func main(){
    ...
    DoT(id)
    fmt.Println(id.Get())
}

这里用id做参数,最终执行完,结果id确实增加了1,符合预期。

结合前边接口赋值的图进行分析,接口动态值区域拷贝了一份id的指针值,这个指针指向一个具体的实例。如下图。

从这里可以看出对id的任何操作其实都是对具体的实例进行的操作,所以无论读写都是符合预期的,所以当使用指针调用Get方法时候就会进行自动转化调用值的Get方法。

至于ib为啥编译通过,运行时候就报错,也是因为指针是个nil值,无法自动转化找到Get方法。

总结

翻了好几天资料,本来想把嵌入类型和反射都写进来,但是时间有点仓促,大家可以结合上边的讲解,自行对嵌入类型和反射进行研究,基础原理都一样。

这里总结一下:

实例都包括两部分,值和类型,编译器正是通过实例类型所以才知道了其方法集。

单独实例使用时候,是允许调用所有方法的,调用非自身方法集时候编译器会自动进行转换,并且都会调用成功,符合预期。

实例赋值给接口时候,是把实例信息拷贝到接口中的,其数据结构和原来实例完全不一样了,同时接口会严格检查方法集,以防止不符合预期行为发生。

实例是指针时候,并且为空的时候,并且包含非指针方法时候,无论是该实例的接口还是该实例,都不能进行任何方法调用,否则会有运行时panic发生。未指向任何具体数据变量,无论读写肯定报错。

接口断言知道为啥一定要是接口才能进行断言吧,因为接口的动态值和动态类型要进行动态填充,接口断言也可以判断一个实例的方法集,而且是安全的判断

_,ok:=interface{}(a).(I)

判断一个实例是否有哪个方法,方法集中的方法有哪些,目前看可以通过三种方法"方法表达式"","接口赋值","接口断言"。

其实还有好多知识点比如nil类型,空接口,空指针,相互比较时候真假结果,嵌入结构体方法集,反射操作,等等,只要把原理搞清了都很容易理解的。

以上就是goalng 结构体 方法集 接口实例详解的详细内容,更多关于goalng 结构体 方法集 接口的资料请关注编程网其它相关文章!

您可能感兴趣的文档:

--结束END--

本文标题: goalng 结构体 方法集 接口实例详解

本文链接: https://www.lsjlt.com/news/121191.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • goalng 结构体 方法集 接口实例详解
    目录一 前序二 事出有因errors.As 方法签名三 结构体与实例的数据结构1. 结构体类型2. 实例3 方法调用3.1 方法表达式3.2 值实例调用所有方法3.3 指针实例调用所...
    99+
    2022-11-11
  • SpringBoot接口返回结果封装方法实例详解
    rest接口会返回各种各样的数据,如果对接口的格式不加约束,很容易造成混乱。 在实际项目中,一般会把结果放在一个封装类中,封装类中包含http状态值,状态消息,以及实际的数据。这里主...
    99+
    2022-11-12
  • Go语言学习函数+结构体+方法+接口
    目录1. 函数1.1 函数返回值同一种类型返回值带变量名的返回值函数中的参数传递函数变量1.2 匿名函数——没有函数名字的函数在定义时调用匿名函数将匿名函数赋...
    99+
    2022-11-13
  • Java数据结构算法Collection接口迭代器示例详解
    目录Java合集框架Collection接口迭代器Java合集框架 数据结构是以某种形式将数据组织在一起的合集(collection)。数据结构不仅存储数据,还支持访问和处理数据的操...
    99+
    2022-11-13
  • 实例详解Java调用第三方接口方法
    目录一、 通过JDK网络类Java.net.HttpURLConnection1.java.net包下的原生java api提供的http请求2.HttpClientUtil工具类3...
    99+
    2022-11-13
  • Golang实现Json转结构体的示例详解
    解决实际需求,案例分享。 1.请求Zabbix API,通过itemid获取到AppName(应用集名称) package main import (  "encoding/jso...
    99+
    2023-02-19
    Golang Json转结构体 Golang Json 结构体
  • Go语言函数、结构体、方法和接口怎么用
    本篇内容介绍了“Go语言函数、结构体、方法和接口怎么用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1. 函数Go语言的函数属于“一等公民”...
    99+
    2023-06-30
  • java中抽象类、抽象方法、接口与实现接口实例详解
    前言对于java中的抽象类,抽象方法,接口,实现接口等具体的概念就不在这里详细的说明了,网上书本都有很多解释,主要是我懒,下面通过一个例子来说明其中的精髓要点,能不能练成绝世武功,踏上封王之路,就看自己的的啦(不要误会,我指的只是我自己啦啦...
    99+
    2023-05-30
    java 抽象类 接口
  • Go语言基础结构体用法及示例详解
    目录概述语法结构体定义的三种形式第一种【基本的实例化】第二种【指针类型的结构体】第三种【取结构体的地址实例化,通过&的操作】初始化结构体键值对初始化结构体值列表填充结构体匿名...
    99+
    2022-11-12
  • Java集合和数据结构排序实例详解
    目录概念插入排序直接插入排序代码实现性能分析希尔排序代码实现性能分析选择排序直接选择排序代码实现性能分析堆排序代码实现性能分析交换排序冒泡排序代码实现性能分析快速排序代码实现性能分析...
    99+
    2022-11-12
  • Go语言学习函数+结构体+方法+接口
    目录1. 函数1.1 函数返回值同一种类型返回值带变量名的返回值函数中的参数传递函数变量1.2 匿名函数——没有函数名字的函数在定义时调用匿名函数将匿名...
    99+
    2022-06-07
    GO 结构体 方法 学习 函数 go语言 接口
  • Golang打印复杂结构体两种方法详解
    目录fmt结构体占位符打印复杂结构体方案一方案二fmt结构体占位符 在Golang中有原生的 fmt 格式化工具去打印结构体,可以通过占位符%v、%+v、%#v去实现,这3种的区别如...
    99+
    2022-11-11
  • Rust结构体的定义与实例化详细讲解
    结构体和我们在“元组类型”部分论过的元组类似,它们都包含多个相关的值。和元组一样,结构体的每一部分可以是不同类型。但不同于元组,结构体需要命名各部分数据以便能...
    99+
    2022-12-12
    Rust结构体定义 Rust结构体实例化
  • Go语言基础语法之结构体及方法详解
    结构体类型可以用来保存不同类型的数据,也可以通过方法的形式来声明它的行为。本文将介绍go语言中的结构体和方法,以及“继承”的实现方法。 结构体类型 结构体类型(struct)在go语...
    99+
    2022-11-12
  • 数据结构TypeScript之邻接表实现示例详解
    目录图的结构特点图的分类图的表示面向对象方法封装邻接表构造函数增加顶点和边删除顶点和边图的遍历颜色标记广度优先搜索(队列)深度优先搜索(栈)图的结构特点 图由顶点和顶点之间的边构成...
    99+
    2023-01-30
    TypeScript数据结构邻接表 TypeScript数据结构
  • Go语言学习之结构体和方法使用详解
    目录1. 结构体别名定义2. 工厂模式3. Tag 原信息4. 匿名字段5. 方法1. 结构体别名定义 变量别名定义 package main import "fmt" type...
    99+
    2022-11-13
  • C语言详解如何实现堆及堆的结构与接口
    目录一、堆的结构及实现(重要)1.1 二叉树的顺序结构1.2 堆的概念及结构1.3 堆的实现1.3.1 堆的向下调整算法1.3.2 向下调整算法的时间复杂度1.3.3 堆的创建(向下...
    99+
    2022-11-13
  • Go语言同步等待组sync.WaitGroup结构体对象方法详解
    目录sync.WaitGroup结构体对象WaitGroup的结构体Add()方法Done()方法Wait()方法Add()、Done()、Wait()三者对比sync.WaitGr...
    99+
    2022-11-11
  • Spring Security实现接口放通的方法详解
    目录1.SpringBoot版本2.实现思路3.实现过程3.1新建注解3.2新建请求枚举类3.3判断Controller方法上是否存在该注解3.4在SecurityConfig上进行...
    99+
    2022-11-13
  • C语言示例讲解结构体的声明与初始化方法
    目录一、结构体声明的结构1.直接声明2.使用typedef声明一个新的类型3.不完全声明二.结构体初始化1.声明(同时定义)时直接赋值2.定义时直接赋值3.定义后赋值4.指定初始化一...
    99+
    2022-11-13
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作