DonkeyKane's blog DonkeyKane's blog
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

DonkeyKane

首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Golang

    • Golang接口
      • 接口类型
      • 接口值
        • 接口值的可比性
        • Golang八股:一个包含nil指针的接口不是nil接口
      • 类型断言
        • 类型分支
  • 后端
  • Golang
DonkeyKane
2025-01-15
目录

Golang接口

Golang接口声明了操作而不透露任何与数据相关的信息。

# 接口类型

接口类型是一种抽象的类型。它不会暴露出它所代表的对象的内部值的结构和这个对象支持的基础操作的集合;它们只会表现出它们自己的方法。也就是说当你有看到一个接口类型的值时,你不知道它是什么,唯一知道的就是可以通过它的方法来做什么。

接口类型具体描述了一系列方法的集合,一个实现了这些方法的具体类型是这个接口类型的实例。 --《go语言圣经 (opens new window)》

# 接口值

概念上讲一个接口的值,接口值,由两个部分组成,一个具体的类型和那个类型的值。它们被称为接口的动态类型和动态值。....在我们的概念模型中,一些提供每个类型信息的值被称为类型描述符,比如类型的名称和方法。在一个接口值中,类型部分代表与之相关类型的描述符。 image.png

[!NOTE] Interface{} 空接口类型不会对数据进行任何方法实现上的要求,因此空接口值可以存任何类型的数据

# 接口值的可比性

接口值可以使用==和!=来进行比较。两个接口值相等仅当它们都是nil值,或者它们的动态类型相同并且动态值也根据这个动态类型的==操作相等。

# Golang八股:一个包含nil指针的接口不是nil接口

const debug = true

func main() {
    var buf *bytes.Buffer
    if debug {
        buf = new(bytes.Buffer) // enable collection of output
    }
    f(buf) // NOTE: subtly incorrect!
    if debug {
        // ...use buf...
    }
}

// If out is non-nil, output will be written to it.
func f(out io.Writer) {
    // ...do something...
    if out != nil {
        out.Write([]byte("done!\n"))
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

该程序在debug=false是会发生panic,原因是

var buf *bytes.Buffer
1

这一行,buf被赋值一个*bytes.Buffer的空指针,值概念如下:image.png 而方法f以io.Writer的类型传入参数,因此,buf会被视为一个具有动态类型*bytes.Buffer,动态值为nil的接口,与nil接口的比较为false,进入条件导致nil指针解引用

只有当接口的动态方法与动态值同时为nil时,接口才是nil接口

[!NOTE] 这段程序的迷惑之处在于,我在阅读代码时,很少关注能够运行代码的传参类型,以为能传入就是类型可用,就是行为正确,只能说被rust养得太好了。

# 类型断言

有了接口值的概念模型,类型断言就会变得非常直觉 考虑这一段程序

var w io.Writer
w = os.Stdout
f := w.(*os.File)      // success: f == os.Stdout
c := w.(*bytes.Buffer) // panic: interface holds *os.File, not *bytes.Buffer
1
2
3
4

后两行所做的赋值操作,其实就是在赋动态值的基础上,检查动态类型,值的类型是不会改变的,在上例中,都是os.File

不会panic的写法:

var w io.Writer = os.Stdout
f, ok := w.(*os.File)      // success:  ok, f == os.Stdout
b, ok := w.(*bytes.Buffer) // failure: !ok, b == nil
1
2
3

# 类型分支

知道了类型断言本质上是判断类型是否符合接口,那么就可以使用常规bool判断的思路,让类型断言更进一步

接口被以两种不同的方式使用。在第一个方式中,以io.Reader,io.Writer,fmt.Stringer,sort.Interface,http.Handler和error为典型,一个接口的方法表达了实现这个接口的具体类型间的相似性,但是隐藏了代码的细节和这些具体类型本身的操作。重点在于方法上,而不是具体的类型上。

第二个方式是利用一个接口值可以持有各种具体类型值的能力,将这个接口认为是这些类型的联合。类型断言用来动态地区别这些类型,使得对每一种情况都不一样。在这个方式中,重点在于具体的类型满足这个接口,而不在于接口的方法(如果它确实有一些的话),并且没有任何的信息隐藏。我们将以这种方式使用的接口描述为discriminated unions(可辨识联合)。

例子

func sqlQuote(x interface{}) string {
    switch x := x.(type) {
    case nil:
        return "NULL"
    case int, uint:
        return fmt.Sprintf("%d", x) // x has type interface{} here.
    case bool:
        if x {
            return "TRUE"
        }
        return "FALSE"
    case string:
        return sqlQuoteString(x) // (not shown)
    default:
        panic(fmt.Sprintf("unexpected type %T: %v", x, x))
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
编辑 (opens new window)
#golang
上次更新: 2025/01/15, 16:09:25
最近更新
01
网格布局中的动画
09-15
02
Git修改分支名
08-11
03
CSS给table的tbody添加滚动条
06-29
更多文章>
Theme by Vdoing | Copyright © 2025-2025 DonkeyKane | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式