【Golang - 90天从新手到大师】Day14 - 方法和接口

一.  go方法

go方法:在函数的func和函数名间增加一个特殊的接收器类型,接收器可以是结构体类型或非结构体类型。接收器可以在方法内部访问。创建一个接收器类型为Type的methodName方法。

func (t Type) methodName(parameter list) {}

go引入方法的原因:

1)go不是纯粹的面向对象编程语言,而且Go不支持类。因此,基于类型的方法是一种实现和类相似行为的途径。

2)相同名字的方法可以定义在不同的类型上,而相同名字的函数不被允许。

方法调用

t.methodName(parameter list)

指针接收器与值接收器

区别:指针接收器的方法内部的改变对外可见,而值接收器不会改变方法外部的变量。

对于指针接收器&T Type而言,(&T).methodName与T.methodName等价。

匿名字段的方法

属于结构体的匿名字段的方法可以被直接调用,就好像这些方法是属于定义了匿名字段的结构体一样。

在方法中使用值接收器 与 在函数中使用值参数

当一个函数有一个值参数,它只能接受一个值参数。

当一个方法有一个值接收器,它可以接受值接收器和指针接收器。

package main
import "fmt"
type rectangle struct {        length int        width int}
func area(r rectangle){        fmt.Printf("Area Function result: %d\n", (r.length * r.width))}
func (r rectangle)area(){        fmt.Printf("Area method result: %d\n", (r.length * r.width))}
func main(){        r := rectangle{                length: 10,                width: 5,        }area(r)        r.area()p := &r//      area(p) // cannot use p (type *rectangle) as type rectangle in argument to area        p.area() //通过指针调用接收器}

在方法中使用指针接收器 与 在函数中使用指针参数

函数使用指针参数只接受指针,而使用指针接收器的方法可以使用值接收器和指针接收器。

在非结构体上的方法

为了在一个类型上定义一个方法,方法的接收器类型定义和方法的定义应该在同一个包中。

对于内建类型,如int,应该在文件中创建一个类型别名,然后创建一个以该类型别名为接收器的方法。

二.  go接口

接口是方法(方法签名,method signature)的集合。当一个类型定义了接口中的所有方法,就称它实现了该接口。与OOP类似,接口定义了一个类型应该具有的方法,由该类型决定如何实现这些方法。

type myInterface interface{    method1()    method2()}

接口调用

永远不要使用一个指针指向一个接口类型,因为它已经是一个指针。 函数参数为interface{}时可以接收任何类型参数,即使是指针类型,也应该是interface{},而不是*interface{}。

//interface definitiontype VowelsFinder interface {      FindVowels() []rune}
type MyString string
//MyString implements VowelsFinderfunc (ms MyString) FindVowels() []rune {      var vowels []rune    for _, rune := range ms {        if rune == 'a' || rune == 'e' || rune == 'i' || rune == 'o' || rune == 'u' {            vowels = append(vowels, rune)        }    }    return vowels}name := MyString("Sam Anderson")    var v VowelsFinder    v = name // possible since MyString implements VowelsFinder    fmt.Printf("Vowels are %c", v.FindVowels())

如果一个类型包含了接口中声明的所有方法,那么它就隐式地实现了 Go 接口

接口的内部表示

可以把接口看作内部的一个元组 (type, value)。 type 是接口底层的具体类型(Concrete Type),而 value 是具体类型的值。

图片图片

(接口值是一个两个字长度的数据结构,第一个字包含一个指向内部表的指针,内部表iTable存储值类型信息以及方法集。第二个字是一个指向所存储值的指针。)

一个接口的值,接口值,由两个部分组成,一个具体的类型和那个类型的值。它们被称为接口的动态类型和动态值。

对于Go语言这种静态类型的语言,类型是编译期的概念:因此一个类型不是一个值。

从概念上讲,不论接口值多大,动态值总是可以容下它。

接口值可以使用和!=来进行比较。两个接口值相等仅当他们是nil值或者它们的动态类型相同并且动态值也根据这个动态类型的操作相等。因为接口值是可比较的,所以它们可以用在map的键或者作为switch语句的操作数。

然而,如果两个接口值的动态类型相同,但是这个动态类型是不可比较的(比如切片),将它们比较就会失败并且panic。

var x interface{} = []int{1, 2, 3}fmt.Println(x == x) // panic: comparing uncomparable
 type []int

考虑到这点,接口类型是非常与众不同的。其它类型要么是安全的可比较类型(如基本类型和指针)要么是完全不可比较的类型(如切片,映射类型,和函数),但是在比较接口值或者包含了接口值的聚合类型时,我们必须要意识到潜在的panic。同样的风险也存在于使用接口作为map的键或者switch的操作数。只能比较你非常确定它们的动态值是可比较类型的接口值。

type Test interface {      Tester()}
type MyFloat float64
func (m MyFloat) Tester() {      fmt.Println(m)}
func describe(t Test) {      fmt.Printf("Interface type %T value %v\n", t, t)}
func main() {      var t Test    f := MyFloat(89.7)    t = f    describe(t)    t.Tester()}
输出:Interface type main.myFloat value 89.789.7

空接口

没有包含方法的接口称为空接口。空接口表示为 interface{}。由于空接口没有方法,因此所有类型都实现了空接口。

当指定参数为空接口时,可以接收任意类型,那如何获取参数的值呢?  通过类型断言。v, ok := p.(int),判定参数是否为int并获取参数值。

函数可以接收interface{}作为参数,但最好不要返回interface{}。

类型断言

类型断言用于提取接口的底层值(Underlying Value)。

在语法 i.(T) 中,接口 i 的具体类型是 T,该语法用于获得接口的底层值。

v, ok := i.(T)

如果 i 的具体类型是 T,那么 v 赋值为 i 的底层值,而 ok 赋值为 true。

如果 i 的具体类型不是 T,那么 ok 赋值为 false,v 赋值为 T 类型的零值,此时程序不会报错

类型选择(Type Switch)

类型选择用于将接口的具体类型与很多 case 语句所指定的类型进行比较。它与一般的 switch 语句类似。

类型选择的语法是 i.(type),获取接口的类型,type是固定关键字。需要注意的是,只有接口类型才可以使用类型选择。

还可以将一个类型和接口相比较。如果一个类型实现了接口,那么该类型与其实现的接口就可以互相比较。

type Describer interface {        Describe()}
type Person struct {        name string        age int}func (p Person) Describe(){        fmt.Printf("%s is %d years old\n", p.name, p.age)}
func findType(i interface{}){        switch v := i.(type){                case Describer:                        v.Describe()                default:                        fmt.Printf("unknown type\n")        }}
func main(){        findType("wang")        p := Person{                name: "qing",                age: 25,        }findType(p)}

在上面程序中,结构体 Person 实现了 Describer 接口。在第 19 行的 case 语句中,v 与接口类型 Describer 进行了比较。p 实现了 Describer,因此满足了该 case 语句,于是当程序运行到第 32 行的 findType(p) 时,程序调用了 Describe() 方法。

package main
import "fmt"
type InterfaceA interface {    Print()    Get() string}
type InterfaceB interface {    Get() string    Print()}
type InterfaceC interface {    Print()}
type People struct {    name string}
func (p *People) Print() {    fmt.Println(p.name)}
func (p *People) Get() string {    return p.name}
// 只要两个接口拥有相同的方法列表(次序不同不要紧),那么他们就是等价的,可以相互赋值
func main() {    var a InterfaceA    var b InterfaceB    var c InterfaceC    a = b    p := &People{name: "wang"}    a = p //接口赋值若错误,编译时直接报错, a = *p    b = a    a.Print()    b.Print()    v, ok := b.(*People) // 接口查询若错误,编译直接报错, b.(People)    fmt.Println(v, ok)    c = a     // 多方法接口可以赋值到少方法接口    c.Print()    //    b = c    // InterfaceC does not implement InterfaceB(missing Get method)    //  b.Print()}

实现接口:指针接受者与值接受者

使用值接受者声明的方法,接口既可以用值来调用,也能用指针调用。

对于使用指针接受者的方法,接口必须用一个指针或者一个可取得地址的值(&method)来调用。但接口中存储的具体值(Concrete Value)并不能取到地址,对于编译器无法自动获取 a 的地址,于是程序报错。

type Describer interface {      Describe()}type Person struct {      name string    age  int}
func (p Person) Describe() { // 使用值接受者实现      fmt.Printf("%s is %d years old\n", p.name, p.age)}
type Address struct {    state   string    country string}
func (a *Address) Describe() { // 使用指针接受者实现    fmt.Printf("State %s Country %s", a.state, a.country)}
func main() {      var d1 Describer    p1 := Person{"Sam", 25}    d1 = p1    d1.Describe()    p2 := Person{"James", 32}    d1 = &p2    d1.Describe()var d2 Describer    a := Address{"Washington", "USA"}/* 如果下面一行取消注释会导致编译错误:       cannot use a (type Address) as type Describer       in assignment: Address does not implement       Describer (Describe method has pointer       receiver)    */    //d2 = ad2 = &a // 这是合法的    // 因为在第 22 行,Address 类型的指针实现了 Describer 接口    d2.Describe()}

接口的嵌套

type SalaryCalculator interface {      DisplaySalary()}
type LeaveCalculator interface {      CalculateLeavesLeft() int}
type EmployeeOperations interface {      SalaryCalculator    LeaveCalculator}

接口的零值

接口的零值是 nil。对于值为 nil 的接口,其底层值(Underlying Value)和具体类型(Concrete Type)都为 nil。对于值为 nil 的接口,由于没有底层值和具体类型,当我们试图调用它的方法时,程序会产生 panic 异常。

一个接口值基于它的动态类型被描述为空或非空,可以通过w==nil或w!=nil来判断接口值是否为空。

当且仅当动态值和动态类型都为 nil 时,接口类型值才为 nil。

w=nil将重置接口w的所有部分(类型和值)为nil。

警告:一个包含nil指针的接口可能不是nil接口。

var buf *bytes.Bufferf(buf)
func f(out io.Writer){    // ...    if out != nil {        out.Write([]byte("done!\n"))    // painic: nil pointer dereference    }}

当执行f(buf)时,f函数的out参数赋了一个bytes.Buffer的空指针,所以out的动态值是nil,但是它的动态类型是bytes.Buffer,意思是out变量是一个包含空指针的非空接口,所以out!=nil的结果依然是true。

正确的处理是 var buf io.Writer,因此可以避免一开始就将一个不完全的值赋值给这个接口。

Go接口最佳实践

1)倾向于使用小的接口定义,很多接口只包含一个方法。    如Reader,Writer,便于类型实现接口,方法太多,类型实现越麻烦。

2)较大的接口定义,可以由多个小接口定义组合而成。  即接口的嵌套。

3)只依赖于必要功能的最小接口。方法或函数的接口参数的范围或方法越小越好,这样便于参数的调用,和方法或函数被其他程序调用。

如func StoreData(reader Reader) error{},能传递Reader就不传递ReadWriter。

4)接口一般有默认实现,应用时嵌套默认实现。

package main
import "fmt"
type Animal interface {        Speak()        SpeakTo(string)}
// default implementationtype Pet struct {}
func (p *Pet) Speak() {        fmt.Print("Pet...")}
func (p *Pet) SpeakTo(host string){        p.Speak()        fmt.Println("---", host)}
// real functiontype Dog struct {        *Pet  // include default struct}/*func (d *Dog) Speak() {        fmt.Print("Dog")}*/
func (d *Dog) SpeakTo(host string){        d.Speak() // no implement, using default        fmt.Println(host)}
func main(){        dog := new(Dog)        dog.SpeakTo("Chao")}

欢迎加入Go语言的学习wx群:wdw11079533

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

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

相关文章

在 MATLAB 中显示 3D 图像

文章目录 前言1. 曲面图 (Surface Plot)2. 网格图 (Mesh Plot)3. 散点图 (Scatter Plot)4. 等值线图 (Contour Plot) 前言 提示:这里可以添加本文要记录的大概内容: 项目需要: 提示:以下是本篇文章正文内容,下面案例…

享元模式(设计模式)

享元模式(Flyweight Pattern)是一种结构型设计模式,它通过共享细粒度对象来减少内存使用,从而提高性能。在享元模式中,多个对象可以共享相同的状态以减少内存消耗,特别适合用于大量相似对象的场景。 享元模…

解决“Duplicate keys detected: ‘ ‘.This may cause an update error.”问题

问题原因 出现“Duplicate keys detected”的错误,通常表示在v-for指令中使的:key绑定值有重复。 如果前端是静态数据,一般能自我避免:key绑定值有重复。如果前端是绑定的动态数据,那么需要另外提供一个唯一的键。 在这个例子中&#xff0c…

【LeetCode】接雨水

目录 一、题目二、解法完整代码 一、题目 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 示例 1: 输入:height [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释&#xff…

面向对象,常用类,集合,异常,JDBC,mysql数据库内容的复习,

1,面向对象 面向对象与面向过程对比 面向过程:关注过程,适合解决简单直接的问题,代码结构以函数为单位,如C语言。 面向对象:关注类,适合解决复杂问题更加适合解决复杂的项目中的问题等等&…

跨平台编程:在Conda中搭建R语言环境的终极指南

🌐 跨平台编程:在Conda中搭建R语言环境的终极指南 🌐 在数据科学和统计分析领域,R语言以其强大的数据处理能力和丰富的图形表示功能而广受欢迎。然而,对于习惯了使用Linux操作系统的用户来说,如何方便地在…

【UML用户指南】-23-对高级行为建模-状态机

目录 1、概述 2、状态 2.1、状态的组成 3、转移 3.1、转移的组成 4、高级状态和转移 4.1、进入效应和退出效应 4.2、内部转移 4.3、do活动 4.4、延迟事件 4.5、子状态机 5、子状态 5.1、非正交子状态 5.2、历史状态 5.3、正交子状态 6、分叉与汇合 7、主动对象…

GOROOT GOPATH GOPROXY GO111MODULE

GOROOT GOROOT代表Go的安装目录。可执行程序go(或go.exe)和gofmt(或gofmt.exe)位于 GOROOT/bin目录中。 配置GOROOT环境变量,其值为Go的安装目录;然后在环境变量PATH中添加GOROOT/bin路径。 注意:GOROOT变量只是代表了安装目录,不…

泛型的实际应用示例

泛型的实际应用示例 1. 集合框架中的泛型 在Java的集合框架中,泛型被广泛使用以确保类型安全并减少运行时错误。以下是一个使用泛型ArrayList的示例: java import java.util.ArrayList; import java.util.List; public class GenericCollectionsExamp…

【面试题】信息系统安全运维要做什么

信息系统安全运维是确保信息系统稳定、可靠、安全运行的一系列活动和措施。 其主要包括以下几个方面: 1.系统监控: 实时监测信息系统的运行状态,如服务器的性能指标、网络流量、应用程序的运行情况等。通过监控工具,及时发现系统…

企业数据治理的下一步是数据资产管理?

随着信息技术的飞速发展和数字化转型的深入推进,企业数据已成为驱动业务增长和创新的核心要素。当企业数据治理工作取得显著成效后,如何进一步发挥数据的价值,实现数据资产的有效管理,成为企业面临的重要课题。 数据治理的基石作用…

算法练习——函数、递归和递推

在此记录一些有关函数、递归和递推的问题。所有题目均来自洛谷的题单能力提升综合题单Part1 入门阶段 - 题单 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) (实际上都没有用递推做) [NOIP2001 普及组] 数的计算 题目描述 给出正整数 n n n&#xf…

学习感悟丨在誉天学习数通HCIP怎么样

大家好,我是誉天学员的徐同学,学习的数通HCIP课程。 在学校的时候,听说下半年就要出去实习了,心中坎坷不安,现在我学到的知识远远不够的。然后就想着学点东西充实一下自己的知识面和专业能力,有一次和同学谈…

【漏洞复现】飞企互联——SQL注入

声明:本文档或演示材料仅供教育和教学目的使用,任何个人或组织使用本文档中的信息进行非法活动,均与本文档的作者或发布者无关。 文章目录 漏洞描述漏洞复现测试工具 漏洞描述 飞企互联-FE企业运营管理平台是一个基于云计算、智能化、大数据…

[图解] 向量数据库之何谓乘积量化器?

Product Quantization 在前面一节讲解了向量数据库索引相关的内容,那么本节将会讲解其中压缩方法的量化手段:乘积量化器。 简单来说将向量的所有维度划分为多个子空间,每个子空间一部分维度,然后每个子空间独立去找最近距离。例如…

haproxy实现代理和负载均衡

HaProxy介绍: haproxy是法国开发者威利塔罗在2000年使用C语言开发的一个开源软件,是一款具备高并发(一万以上)、高性能的TCP和HTTP负载均衡器,支持基于cookie的持久性,自动故障切换,支持正则表达式及web状态统计&…

Numpy array和Pytorch tensor的区别

1.Numpy array和Pytorch tensor的区别 笔记来源: 1.Comparison between Pytorch Tensor and Numpy Array 2.numpy.array 4.Tensors for Neural Networks, Clearly Explained!!! 5.What is a Tensor in Machine Learning? 1.1 Numpy Array Numpy array can only h…

arthas监控工具笔记(一)

文章目录 启动 math-game启动 arthas查看 dashboard通过 thread 命令来获取到math-game进程的 Main Class通过 jad 来反编译 Main Class退出 arthas 界面linux服务器挂不上进程怎么办? 核心表达式变量loader 本次调用类所在的 ClassLoaderclazz 本次调用类的 Class 引用method…

信息学奥赛初赛天天练-39-CSP-J2021基础题-哈夫曼树、哈夫曼编码、贪心算法、满二叉树、完全二叉树、前中后缀表达式转换

PDF文档公众号回复关键字:20240629 2022 CSP-J 选择题 单项选择题(共15题,每题2分,共计30分:每题有且仅有一个正确选项) 5.对于入栈顺序为a,b,c,d,e的序列,下列( )不合法的出栈序列 A. a,b&a…

螺旋矩阵问题C代码

给定一个n行m列的二维数组,要求按顺时针螺旋顺序输出矩阵中的所有元素,n和m小于等于10 如下图是一个三行四列的螺旋矩阵 要求输出 1 2 3 4 8 12 11 10 9 5 6 7 全局变量定义 int a[11][11]; int vis[11][11]; // 访问标记数组关键代码如下 int dx[] …