golang:理解 nil 指针和 nil 接口之间的区别

golang:理解 nil 指针和 nil 接口之间的区别

我正在思考 nil 在 go 中的不同工作方式,以及有时某些东西可以同时为 nil 和非 nil。

这是一个可以是 nil 指针但不是 nil 接口的小例子。让我们来看看这意味着什么。

接口

首先,go 有一个接口的概念,它与一些面向对象语言中的接口类似,但又不完全相同(按照大多数定义,go 不是 OOP)。在 Go 中,接口是一种类型,它定义了另一个类型必须实现才能满足该接口的函数。这允许我们拥有多种具体类型,可以以不同的方式满足接口。

例如,error 是一个具有单一方法的内置接口。看起来像这样:

输入错误接口{    错误()字符串}

登录后复制

任何想要用作错误的类型都必须有一个名为 Error 的方法,该方法返回一个字符串。例如,可以使用以下代码:

输入 ErrorMessage 字符串func (em ErrorMessage) Error() 字符串 {    返回字符串(em)}func DoSomething() 错误 {    // 尝试做某事,但失败了。    如果某事失败{        var err ErrorMessage = "此失败"        返回错误    }    返回零}函数主() {    错误 := DoSomething()    如果错误!= nil {        恐慌(错误)    }}

登录后复制

请注意,在此示例中,如果出现问题,DoSomething 将返回错误。我们可以使用 ErrorMessage 类型,因为它具有 Error 函数,该函数返回一个字符串,因此实现了错误接口。
如果没有发生错误,我们返回 nil。

指针

在go中,指针指向一个值,但也可以指向无值,这种情况下指针为nil。例如:

var i *int = nil函数主() {    如果我==零{        j := 5        我=&j    }    fmt.Println("i 是", *i)}

登录后复制

在这种情况下,i 变量是一个指向 int 的指针。它一开始是一个 nil 指针,直到我们创建一个 int 并将其指向该指针。

立即学习“go语言免费学习笔记(深入)”;

指针和接口

由于用户定义的类型可以附加函数(方法),因此我们也可以拥有指向类型的指针的函数。这是Go中很常见的做法。这也意味着指针也可以实现接口。通过这种方式,我们可以得到一个非 nil 接口的值,但仍然是一个 nil 指针。考虑以下代码:

类型TruthGetter接口{    为真() 布尔值}func PrintIfTrue(tg TruthGetter) {    如果 tg == nil {        fmt.Println("我不知道这是不是真的")        返回    }    如果 tg.IsTrue() {        fmt.Println("这是真的")    } 别的 {        fmt.Println("这不是真的")    }}

登录后复制

任何具有 IsTrue() bool 方法的类型都可以传递给 PrintIfTrue,但 nil 也可以。所以,我们可以执行 PrintIfTrue(nil) ,它会打印“我无法判断它是否为真”。

我们也可以做一些简单的事情,比如这样:

输入 Truthy boolfunc (ty Truthy) IsTrue() bool {    返回布尔值(ty)}函数主() {    var ty Truthy = true    打印如果为真(ty)}

登录后复制

这将打印“It’s true”。

或者,我们可以做一些更复杂的事情,比如:

输入 TruthyNumber intfunc (tn TruthyNumber) IsTrue() bool {    返回 tn > 0}函数主() {    var tn TruthyNumber = -4    如果为真则打印(tn)}

登录后复制

这将打印“这不是真的”。这些示例都不是指针,因此这些类型都不可能出现 nil,但请考虑这一点:

类型 TruthyPerson 结构 {    名字字符串    姓氏字符串}func (tp *TruthyPerson) IsTrue() 布尔 {    返回 tp.FirstName != "" && tp.LastName != ""}

登录后复制

在这种情况下,TruthyPerson 没有实现 TruthGetter,但 *TruthyPerson 实现了。所以,这应该有效:

func main() {    tp := &TruthyPerson{"乔恩", "格雷迪"}    如果为真则打印(tp)}

登录后复制

这是可行的,因为 tp 是一个指向 TruthyPerson 的指针。然而,如果指针为零,我们就会感到恐慌。

func main() {    var tp *TruthyPerson    如果为真则打印(tp)}

登录后复制登录后复制

这会恐慌。但是,PrintIfTrue 中不会发生恐慌。您可能会认为这很好,因为 PrintIfTrue 检查是否为 nil。但是,问题就在这里。它正在针对 TruthGetter 检查 nil。换句话说,它检查的是 nil 接口,而不是 nil 指针。在 func (tp *TruthyPerson) IsTrue() bool 中,我们不检查 nil。在 go 中,我们仍然可以在 nil 指针上调用方法,因此恐慌发生在那里。修复实际上非常简单。

func (tp *TruthyPerson) IsTrue() bool {    如果 tp == nil {        返回错误    }    返回 tp.FirstName != "" && tp.LastName != ""}

登录后复制

现在,我们检查 PrintIfTrue 中是否有 nil 接口以及 func (tp *TruthyPerson) IsTrue() bool 中是否有 nil 指针。现在它会打印“这不是真的”。我们可以看到所有这些代码都在这里工作。

奖励:通过反射立即检查两个 nil

通过反射,我们可以对 PrintIfTrue 做一些小的改变,以便它可以检查 nil 接口和 nil 指针。代码如下:

func PrintIfTrue(tg TruthGetter) {    如果 tg == nil {        fmt.Println("我不知道这是不是真的")        返回    }    val := 反射.ValueOf(tg)    k := val.Kind()    if (k ==reflect.Pointer || k ==reflect.Chan || k ==reflect.Func || k ==reflect.Map || k ==reflect.Slice) && val.IsNil() {        fmt.Println("我不知道这是不是真的")        返回    }    如果 tg.IsTrue() {        fmt.Println("这是真的")    } 别的 {        fmt.Println("这不是真的")    }}

登录后复制

在这里,我们像以前一样首先检查 nil 接口。接下来,我们使用反射来获取类型。除了指针之外,chan、func、map 和 slice 也可以为 nil,因此我们检查该值是否是这些类型之一,如果是,则检查它是否为 nil。如果是,我们也会返回“我无法判断这是否是真的”消息。这可能是也可能不是您想要的,但它是一个选择。通过这个改变,我们可以做到这一点:

func main() {    var tp *TruthyPerson    如果为真则打印(tp)}

登录后复制登录后复制

您有时可能会看到更简单的建议,例如:

//不要这样做if tg == nil && Reflect.ValueOf(tg).IsNil() {    fmt.Println("我不知道这是不是真的")    返回}

登录后复制

这效果不好有两个原因。首先,使用反射时会产生性能开销。如果您可以避免使用反射,那么您可能应该这样做。如果我们先检查nil接口,如果是nil接口,我们就不必使用反射。

第二个原因是如果值的类型不是可以为 nil 的类型,reflect.Value.IsNil() 会发生恐慌。这就是我们添加此类检查的原因。如果我们没有检查 Kind,那么我们会对 Truthy 和 TruthyNumber 类型感到恐慌。

因此,只要我们确保首先检查类型,现在就会打印“我无法判断它是否为真”,而不是“这不是真的”。根据您的观点,这可能是一种改进。这是进行此更改的完整代码。

这最初发表在 Dan’s Musings

以上就是golang:理解 nil 指针和 nil 接口之间的区别的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2331654.html

(0)
上一篇 2025年3月1日 00:22:15
下一篇 2025年2月23日 18:28:23

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • 单例设计模式

    单例设计模式是软件编程中最重要和最常用的设计模式之一。它确保类在应用程序运行时只有一个实例,并提供对该实例的全局访问点。在这篇文章中,我们将讨论 Singleton 的重要性,如何在 Golang 中实现它,以及它带来的好处,特别是在并发环…

    2025年3月1日
    200
  • 建造者设计模式

    Builder 设计模式用于增量构建复杂的对象,允许使用相同的构建过程创建对象的不同表示。在这篇文章中,我们将探讨如何在 Golang 中实现 Builder 模式,了解其好处,并分析一个实际使用示例。 什么是生成器? Builder 模式…

    2025年3月1日
    200
  • 案例(一)-KisFlow-Golang流实时计算-快速入门指南

    Github:https://github.com/aceld/kis-flow文档:https://github.com/aceld/kis-flow/wiki 第 1 部分-概览Part2.1-项目构建/基础模块Part2.2-项目构建…

    2025年3月1日
    200
  • 有没有快捷的方式学习golang框架?

    go 框架是预定义的代码库,包含应用程序的结构和功能。通过选择合适的框架并按照其文档进行安装和配置,开发人员可以显著提高 go 应用程序的开发效率和可维护性,例如使用 gin 框架构建简单的 web api。 快速掌握 Go 框架:指南与实…

    2025年3月1日
    200
  • golang 框架如何通过新特性简化 REST API 的开发?

    go 框架的新特性简化了 rest api 开发,包括:http 处理程序中的泛型,实现通用处理逻辑;servemux 中的路由参数,简化路径参数获取;嵌入式子路由,便于组织和模块化 api。 Go 框架如何通过新特性简化 REST API…

    2025年3月1日
    200
  • 用于不同场景的 Go 框架推荐

    go 框架推荐指南:web 开发:echo,一个轻量级且快速的 web 框架,适用于构建高性能 rest api。grpc:grpc-go,一个高效的 rpc 框架,用于在分布式系统中构建服务。数据访问:gorm,一个活跃的 orm 框架,…

    2025年3月1日
    200
  • 工厂设计模式

    工厂设计模式广泛应用于面向对象编程中。它提供了一个用于创建对象的接口,但允许子类决定要实例化哪些类。在本文中,我们将探讨如何在 Golang 中实现工厂模式,了解其好处,并分析受日常情况启发的实际使用示例。 什么是工厂? Factory 定…

    2025年3月1日
    200
  • 哪些因素可能会影响学习golang框架的难度?

    学习 golang 框架的难度因素包括编程基础、框架选择、语言熟练度、语言环境和实际经验。难度的关键在于:拥有扎实的编程基础,包括数据结构、算法和设计模式。根据技能水平选择合适的框架,并熟悉 golang 的并发性和错误处理机制。积极参与 …

    2025年3月1日
    200
  • golang框架的生态系统是否对学习有帮助?

    go 框架生态系统通过提供学习资源和预制解决方案,助力学习 go 语言和快速开发应用。具体而言:学习 go 语言:生态系统中的框架和库提供示例和文档,帮助新手了解 go 的核心概念和最佳实践。实战案例:使用 gin 框架构建 rest ap…

    2025年3月1日
    200
  • 揭开神秘面纱:揭开 Go 的价值变化

    在 Golang 中,数据分为两大类: 值类型:这些是独立的数据单元,在传递时会被复制。 例子包括: 基本类型:整数(int、uint等)、浮点数(float32、float64)、布尔值(bool)、字符串、符文(单个字符)。 数组:相同…

    2025年3月1日
    200

发表回复

登录后才能评论