《快学 Go 语言》第 9 课 —— 接口

本文深入解析Go语言的接口概念,包括接口的隐式实现、空接口的用途、接口变量的本质及其实现多态的方式。同时,文章探讨了接口的组合继承,以及接口变量在赋值过程中的内存行为。

640?wx_fmt=png

接口是一个对象的对外能力的展现,我们使用一个对象时,往往不需要知道一个对象的内部复杂实现,通过它暴露出来的接口,就知道了这个对象具备哪些能力以及如何使用这个能力。

我们常说「佛有千面」,不同的人看到的佛并不一样。一个复杂的复合对象常常也可以是一个多面手,它具备多种能力,在形式上实现了多种接口。「弱水三千,只取一瓢」,使用时我们根据不同的场合来挑选满足需要的接口能力来使用这个对象即可。

Go 语言的接口类型非常特别,它的作用和 Java 语言的接口一样,但是在形式上有很大的差别。Java 语言需要在类的定义上显式实现了某些接口,才可以说这个类具备了接口定义的能力。但是 Go 语言的接口是隐式的,只要结构体上定义的方法在形式上(名称、参数和返回值)和接口定义的一样,那么这个结构体就自动实现了这个接口,我们就可以使用这个接口变量来指向这个结构体对象。下面我们看个例子

package main

import "fmt"

// 可以闻
type Smellable interface {
  smell()
}

// 可以吃
type Eatable interface {
  eat()
}

// 苹果既可能闻又能吃
type Apple struct {}

func (a Apple) smell() {
  fmt.Println("apple can smell")
}

func (a Apple) eat() {
  fmt.Println("apple can eat")
}

// 花只可以闻
type Flower struct {}

func (f Flower) smell() {
  fmt.Println("flower can smell")
}

func main() {
  var s1 Smellable
  var s2 Eatable
  var apple = Apple{}
  var flower = Flower{}
  s1 = apple
  s1.smell()
  s1 = flower
  s1.smell()
  s2 = apple
  s2.eat()
}

--------------------
apple can smell
flower can smell
apple can eat

空接口

如果一个接口里面没有定义任何方法,那么它就是空接口,任意结构体都隐式地实现了空接口。

Go 语言为了避免用户重复定义很多空接口,它自己内置了一个,这个空接口的名字特别奇怪,叫 interface{} ,初学者会非常不习惯。之所以这个类型名带上了大括号,那是在告诉用户括号里什么也没有。我始终认为这种名字很古怪,它让代码看起来有点丑陋。

空接口里面没有方法,所以它也不具有任何能力,其作用相当于 Java 的 Object 类型,可以容纳任意对象,它是一个万能容器。比如一个字典的 key 是字符串,但是希望 value 可以容纳任意类型的对象,类似于 Java 语言的 Map 类型,这时候就可以使用空接口类型 interface{}。

package main

import "fmt"

func main() {
 // 连续两个大括号,是不是看起来很别扭
    var user = map[string]interface{}{
        "age": 30,
        "address": "Beijing Tongzhou",
        "married": true,
    }
    fmt.Println(user)
    // 类型转换语法来了
 var age = user["age"].(int)
    var address = user["address"].(string)
    var married = user["married"].(bool)
    fmt.Println(age, address, married)
}

-------------
map[age:30 address:Beijing Tongzhou married:true]
30 Beijing Tongzhou true

接口变量的本质

在使用接口时,我们要将接口看成一个特殊的容器,这个容器只能容纳一个对象,只有实现了这个接口类型的对象才可以放进去。

640?wx_fmt=png
图片

接口变量作为变量来说它也是需要占据内存空间的,通过翻阅 Go 语言的源码可以发现,接口变量也是由结构体来定义的,这个结构体包含两个指针字段,一个字段指向被容纳的对象内存,另一个字段指向一个特殊的结构体 itab,这个特殊的结构体包含了接口的类型信息和被容纳对象的数据类型信息。

// interface structure
type iface struct {
  tab *itab  // 类型指针
  data unsafe.Pointer  // 数据指针
}

type itab struct {
  inter *interfacetype // 接口类型信息
  _type *_type // 数据类型信息
  ...
}

package main

import "fmt"
import "unsafe"

func main() {
    var s interface{}
    fmt.Println(unsafe.Sizeof(s))
    var arr = [10]int {1,2,3,4,5,6,7,8,9,10}
    fmt.Println(unsafe.Sizeof(arr))
    s = arr
    fmt.Println(unsafe.Sizeof(s))
}

----------
16
80
16

用接口来模拟多态

前面我们说到,接口是一种特殊的容器,它可以容纳多种不同的对象,只要这些对象都同样实现了接口定义的方法。如果我们将容纳的对象替换成另一个对象,那不就可以完成上一节我们没有完成的多态功能了么?好,顺着这个思路,下面我们就来模拟一下多态

package main

import "fmt"

type Fruitable interface {
    eat()
}

type Fruit struct {
    Name string  // 属性变量
    Fruitable  // 匿名内嵌接口变量
}

func (f Fruit) want() {
    fmt.Printf("I like ")
    f.eat() // 外结构体会自动继承匿名内嵌变量的方法
}

type Apple struct {}

func (a Apple) eat() {
    fmt.Println("eating apple")
}

type Banana struct {}

func (b Banana) eat() {
    fmt.Println("eating banana")
}

func main() {
    var f1 = Fruit{"Apple", Apple{}}
    var f2 = Fruit{"Banana", Banana{}}
    f1.want()
    f2.want()
}

---------
I like eating apple
I like eating banana

接口的组合继承

接口的定义也支持组合继承,比如我们可以将两个接口定义合并为一个接口如下

type Smellable interface {
  smell()
}

type Eatable interface {
  eat()
}

type Fruitable interface {
  Smellable
  Eatable
}

type Fruitable interface {
  smell()
  eat()
}

接口变量的赋值

变量赋值本质上是一次内存浅拷贝,切片的赋值是拷贝了切片头,字符串的赋值是拷贝了字符串的头部,而数组的赋值呢是直接拷贝整个数组。接口变量的赋值会不会不一样呢?接下来我们做一个实验

package main

import "fmt"

type Rect struct {
    Width int
    Height int
}

func main() {
    var a interface {}
    var r = Rect{50, 50}
    a = r

    var rx = a.(Rect)
    r.Width = 100
    r.Height = 100
    fmt.Println(rx)
}

------
{50 50}

指向指针的接口变量

如果将上面的例子改成指针,将接口变量指向结构体指针,那结果就不一样了

package main

import "fmt"

type Rect struct {
    Width int
    Height int
}

func main() {
    var a interface {}
    var r = Rect{50, 50}
    a = &r // 指向了结构体指针

    var rx = a.(*Rect) // 转换成指针类型
    r.Width = 100
    r.Height = 100
    fmt.Println(rx)
}

-------
{100 100}

下一节我们开讲 Go 语言的错误处理

640?wx_fmt=png

扫一扫上面的二维码关注「码洞」,免费阅读《快学 Go 语言》更多章节

当前,全球经济格局深刻调整,数字化浪潮席卷各行各业,智能物流作为现代物流发展的必然趋势和关键支撑,正迎来前所未有的发展机遇。以人工智能、物联网、大数据、云计算、区块链等前沿信息技术的速迭代与深度融合为驱动,智能物流不再是传统物流的简单技术叠加,而是正在经历一场从自动化向智能化、从被动响应向主动预测、从信息孤岛向全面互联的深刻变革。展望2025年,智能物流系统将不再局限于提升效率、降低成本的基本目标,而是要构建一个感知更全面、决策更精准、执行更高效、协同更顺畅的智慧运行体系。这要求我们必须超越传统思维定式,以系统化、前瞻性的视角,全面规划和实施智能物流系统的建设。本实施方案正是基于对行业发展趋势的深刻洞察和对未来需求的精准把握而制定。我们的核心目标在于:通过构建一个集成了先进感知技术、大数据分析引擎、智能决策算法和高效协同平台的综合智能物流系统,实现物流全链路的可视化、透明化和智能化管理。这不仅是技术层面的革新,更是管理模式和服务能力的全面提升。本方案旨在明确系统建设的战略方向、关键任务、技术路径和实施步骤,确保通过系统化部署,有效应对日益复杂的供应链环境,提升整体物流韧性,优化资源配置效率,降低运营成本,并最终为客户创造更卓越的价值体验。我们致力于通过本方案的实施,引领智能物流迈向更高水平,为构建现代化经济体系、推动高质量发展提供强有力的物流保障。
电源题电赛单相并网离网软件硬件锁相环单极性双极性调制等代码及仿真环路计算资料+原理图PCB内容概要:本文档是一份关于电力电子与能源系统仿真研究的技术资料集合,涵盖单相并网/离网系统、软件与硬件锁相环设计、单极性与双极性调制技术、虚拟同步机控制建模、P2G-CCS耦合系统、微电网优化调度、光伏风电联合运行、储能配置及需求响应等多个电力系统核心主题。文档提供了大量基于Matlab/Simulink的代码实现与仿真模型,包括LLC谐振变换器小信号分析、永磁同步电机控制、DC-AC变换器设计、光伏阵列故障仿真、直流微电网建模等,并附有原理图与PCB设计资源。同时整合了智能优化算法(如遗传算法、粒子群、灰狼优化器)、机器习模型(如LSTM、CNN-GRU-Attention)在负荷预测、故障诊断、路径规划等领域的应用案例,形成一个跨科的科研资源包。; 适合人群:电气工程、自动化、能源系统及相关专业的研究生、科研人员以及从事电力电子、微电网、新能源控制方向的工程师;具备Matlab/Simulink编程基础和一定电力系统理论知识者更佳。; 使用场景及目标:① 支持电赛或科研项目中对并网逆变器、锁相环、调制策略的设计与验证;② 用于复现高水平论文(如EI/SCI)中的优化调度、控制算法与仿真模型;③ 辅助开展微电网能量管理、储能配置、需求响应策略等题的研究与代码开发;④ 提供可直接调用的算法模板与仿真平台,提升科研效率。; 阅读建议:建议按照文档结构逐步浏览,优先下载并整理网盘中的完整资源包,结合具体研究方向选取对应代码与模型进行调试与二次开发;对于复杂算法(如NSGA-II、ADMM、MPC),应配合文献理解其数原理后再实施仿真;关注其中“论文复现”类内容以提升术研究规范性与技术深度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值