golang中的nil接收器详解

我们先看一个简单的例子,我们自定义一个错误,用来把多个错误放在一起输出:

type CustomError struct {
errors []string
}

func (c *CustomError) Add(err string) {
c.errors = append(c.errors, err)
}

func (c *CustomError) Error() string {
return strings.Join(c.errors, ";")
}

因为实现了Error() string方法,所以它实现了error接口。

现在我们要实现一个添加课件的功能,但是添加之前需要验证参数的合法性,所以我们创建了一个Validate方法,我们可能会这么写:

package main

import (
	"errors"
	"fmt"
	"strings"
)

type CustomError struct {
	errors []string
}

func (c *CustomError) Add(err error) {
	c.errors = append(c.errors, err.Error())
}

func (c *CustomError) Error() string {
	return strings.Join(c.errors, ";")
}

type Courseware struct {
	Name string
	Code string
}

func (c *Courseware) Validate() error {
	var m *CustomError // 1
	if c.Name == "" { // 2
		m = &CustomError{}
		m.Add(errors.New("课件名不能为空"))
	}
	if c.Code == "" { // 3
		if m == nil {
			m = &CustomError{}
		}
		m.Add(errors.New("课件编号不能为空"))
	}

	return m // 4
}

func main() {
	m := Courseware{
		Name: "多媒体课件",
		Code: "CW330",
	}
	if err := m.Validate(); err != nil {
		fmt.Println("valid err: ", err)
	}
}

看上去好像一点问题都没有:

  • 定义一个CustomError类型的指针
  • 如果Name为空,初始化m,调用Add方法把错误添加到CustomError.errors
  • 如果Code为空,如果m还没有初始化,先初始化,调用Add方法把错误添加到CustomError.errors
  • 最后返回自定义错误

但是当我们执行上面的代码时,会发现结果并不是我们想要的:

go run 8.go
valid err:  <nil>

我们发现居然走到了打印错误的判断里,但是打印出来的错误居然是一个nil

在 Go 中,我们必须知道指针接收器可以为 nil。我们看一个简单的例子:

package main

import (
	"fmt"
)

type Demo struct {
}

func (d *Demo) Print() string {
	return "demo"
}

func main() {
	var d *Demo
	fmt.Println(d)
	fmt.Println(d.Print())
}
go run 8.go
<nil>
demo

Demo被初始化为nil,但是这段代码可以正常运行。说明nil指针也可以作为接收器。

其实上面的Print方法等价于:

func Print(d *Demo) string {
	return "demo"
}

因为将 nil 指针传递给函数是有效的。 所以使用 nil 指针作为接收器也是有效的。

我们继续回到上面的自定义错误。

m 被初始化为指针的零值:nil。 如果所有验证都通过,return 语句返回的结果不是 nil,而是一个 nil 指针。 因为 nil 指针是一个有效的接收器,所以将结果转换为error接口不会产生 nil 值。

所以我们虽然返回了一个nil指针,但是转换为error接口时并不是一个nil的接口(虽然是nil指针,但是是*CustomError类型,并实现了error)。

要解决这个问题,我们只要直接返回nil值,不返回nil的指针:

package main

import (
	"errors"
	"fmt"
	"strings"
)

type CustomError struct {
	errors []string
}

func (c *CustomError) Add(err error) {
	c.errors = append(c.errors, err.Error())
}

func (c *CustomError) Error() string {
	return strings.Join(c.errors, ";")
}

type Courseware struct {
	Name string
	Code string
}

func (c *Courseware) Validate() error {
	var m *CustomError
	if c.Name == "" {
		m = &CustomError{}
		m.Add(errors.New("课件名不能为空"))
	}
	if c.Code == "" {
		if m == nil {
			m = &CustomError{}
		}
		m.Add(errors.New("课件编号不能为空"))
	}

  // 这里如果m指针为nil,直接返回nil
	if m == nil {
		return nil
	}

	return m
}

func main() {
	m := Courseware{
		Name: "多媒体课件",
		Code: "CW330",
	}

	if err := m.Validate(); err != nil {
		fmt.Println("valid err: ", err)
	}
}

或者我们直接返回*CustomError类型的错误:

package main

import (
	"errors"
	"fmt"
	"strings"
)

type CustomError struct {
	errors []string
}

func (c *CustomError) Add(err error) {
	c.errors = append(c.errors, err.Error())
}

func (c *CustomError) Error() string {
	return strings.Join(c.errors, ";")
}

type Courseware struct {
	Name string
	Code string
}

// 返回*CustomError
func (c *Courseware) Validate() *CustomError {
	var m *CustomError
	if c.Name == "" {
		m = &CustomError{}
		m.Add(errors.New("课件名不能为空"))
	}
	if c.Code == "" {
		if m == nil {
			m = &CustomError{}
		}
		m.Add(errors.New("课件编号不能为空"))
	}

	return m
}

func main() {
	m := Courseware{
		Name: "多媒体课件",
		Code: "CW330",
	}

	if err := m.Validate(); err != nil {
		fmt.Println("valid err: ", err)
	}
}

但这并不是可取的,为了扩展我们实现了error接口,也需要返回error类型的错误。

到此这篇关于golang中的nil接收器的文章就介绍到这了,更多相关golangnil接收器内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Go中的nil切片和空切片区别详解

    Go语言在声明变量的时候,会自动对变量对应的内存区域进行初始化操作.每个变量会被初始化成其类型的默认值,例如: 整型和浮点型变量的默认值为0. 字符串变量的默认值为空字符串. 布尔型变量默认为false. 切片.函数.指针变量的默认为nil. func main() { var s1 []int // nil切片 s2 := make([]int,0) // 空切片 s4 := make([]int,0) // 空切片 s5 := []int{} // 空切片 } 直接看代码,不同声明方式:使用

  • golang interface判断为空nil的实现代码

    要判断interface 空的问题,首先看下其底层实现. interface 底层结构 根据 interface 是否包含有 method,底层实现上用两种 struct 来表示:iface 和 eface.eface表示不含 method 的 interface 结构,或者叫 empty interface. 对于 Golang 中的大部分数据类型都可以抽象出来 _type 结构,同时针对不同的类型还会有一些其他信息. 1.eface type eface struct { _type *_t

  • Go语言interface 与 nil 的比较

    interface简介 Go语言以简单易上手而著称,它的语法非常简单,熟悉C++,Java的开发者只需要很短的时间就可以掌握Go语言的基本用法. interface是Go语言里所提供的非常重要的特性.一个interface里可以定义一个或者多个函数,例如系统自带的io.ReadWriter的定义如下所示: type ReadWriter interface { Read(b []byte) (n int, err error) Write(b []byte) (n int, err error)

  • 彻底理解golang中什么是nil

    nil是什么 相信写过Golang的程序员对下面一段代码是非常非常熟悉的了: if err != nil { // do something.... } 当出现不等于nil的时候,说明出现某些错误了,需要我们对这个错误进行一些处理,而如果等于nil说明运行正常.那什么是nil呢?查一下词典可以知道,nil的意思是无,或者是零值.零值,zero value,是不是有点熟悉?在Go语言中,如果你声明了一个变量但是没有对它进行赋值操作,那么这个变量就会有一个类型的默认零值. 这是每种类型对应的零值:

  • go语言 nil使用避坑指南

    目录 引言 nil 默认值nil (重点记住) nil没有默认类型 不同类型的nil值占用的内存大小可能是不一样的 不同类型 nil 的指针是一样的 不同类型的 nil 是不能比较的 引言 今天笔试题遇到 var x string = nil ,问这个定义是否正确? 这里给出答案: cannot use nil as string value in variable declaration. 也就是说,string类型和nil八竿子打不着,要想判断字符串是否为空,可以使用str == "&quo

  • Go语言中nil判断引起的问题详析

    前言 代码封装是百干不厌的事,但有时候封装会导致一些问题.本文记录了个人在封装 http 请求时遇到的一个和 nil 判断有关的问题. nil 是什么 在 Go 语言中,布尔类型的零值(初始值)为 false,数值类型的零值为 0,字符串类型的零值为空字符串"",而指针.切片.映射.通道.函数和接口的零值则是 nil. nil 内置的一个变量,用来代表空值,且只有指针.channel.方法.接口.map 和切片可以被赋值为 nil. 有过其他编程语言开发经验的开发者也许会把 nil 看

  • golang中的nil接收器详解

    我们先看一个简单的例子,我们自定义一个错误,用来把多个错误放在一起输出: type CustomError struct {errors []string} func (c *CustomError) Add(err string) {c.errors = append(c.errors, err)} func (c *CustomError) Error() string {return strings.Join(c.errors, ";")} 因为实现了Error() string

  • 深入Golang中的sync.Pool详解

    我们通常用golang来构建高并发场景下的应用,但是由于golang内建的GC机制会影响应用的性能,为了减少GC,golang提供了对象重用的机制,也就是sync.Pool对象池. sync.Pool是可伸缩的,并发安全的.其大小仅受限于内存的大小,可以被看作是一个存放可重用对象的值的容器. 设计的目的是存放已经分配的但是暂时不用的对象,在需要用到的时候直接从pool中取. 任何存放区其中的值可以在任何时候被删除而不通知,在高负载下可以动态的扩容,在不活跃时对象池会收缩. sync.Pool首先

  • golang中context的作用详解

    当一个goroutine可以启动其他goroutine,而这些goroutine可以启动其他goroutine,依此类推,则第一个goroutine应该能够向所有其它goroutine发送取消信号. 上下文包的唯一目的是在goroutine之间执行取消信号,而不管它们如何生成.上下文的接口定义为: type Context interface { Deadline() (deadline time.Time, ok bool) Done() <- chan struct{} Err() erro

  • Golang中的参数传递示例详解

    前言 本文主要给大家介绍了关于Golang参数传递的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 关于参数传递,Golang文档中有这么一句: after they are evaluated, the parameters of the call are passed by value to the function and the called function begins execution. 函数调用参数均为值传递,不是指针传递或引用传递.经测试引申出来,

  • Golang中的路由使用详解

    之前有篇文章比较浅显的分析了一下golang的服务器如何实现,还有Handler, DefaultServeMux,HandlerFunc的用处. 我们现在已经明白了DefaultServeMux就是存放pattern和handler的地方,我们称其为路由,那么我们可能会想,既然golang能够实现这个路由,我们能否也模仿一个呢? 首先我们需要一个能够保存客户端的请求的一个容器(路由). 创建路由结构体 type CopyRouter struct { router map[string]map

  • Golang中runtime的使用详解

    runtime 调度器是个非常有用的东西,关于 runtime 包几个方法: Gosched:让当前线程让出 cpu 以让其它线程运行,它不会挂起当前线程,因此当前线程未来会继续执行 NumCPU:返回当前系统的 CPU 核数量 GOMAXPROCS:设置最大的可同时使用的 CPU 核数 Goexit:退出当前 goroutine(但是defer语句会照常执行) NumGoroutine:返回正在执行和排队的任务总数 GOOS:目标操作系统 NumCPU package main import

  • Golang中定时器的陷阱详解

    前言 在业务中,我们经常需要基于定时任务来触发来实现各种功能.比如TTL会话管理.锁.定时任务(闹钟)或更复杂的状态切换等等.百纳网主要给大家介绍了关于Golang定时器陷阱的相关内容,所谓陷阱,就是它不是你认为的那样,这种认知误差可能让你的软件留下隐藏Bug.刚好Timer就有3个陷阱,我们会讲 1)Reset的陷阱和 2)通道的陷阱, 3)Stop的陷阱与Reset的陷阱类似,自己探索吧. 下面话不多说了,来一起看看详细的介绍吧 Reset的陷阱在哪 Timer.Reset()函数的返回值是

  • Golang中的自定义函数详解

    不管是面向过程的编程,还是面向对象的编程,都离不开函数的概念,分别是,参数,函数名,返回值.接下来我们看看Go语言在这三个方面是做怎么操作的吧. 参数 谈到参数,我们在写函数或者是类中的方法的时候都需要考虑我们应该传递怎样的参数,或者是是否需要参数. 参数首先分为无参函数有参.无参也就是没有参数,也就不用写了. 有参 func functionTest() {  # 小括号内就是用来放参数的     # 函数体内 } Go语言是强数据类型的语言,参数是要指定类型的不然就报错.func 是函数的声

  • Go中strings的常用方法详解

    string操作在编程中具有极高的频率,那么string中有哪些有用的方法呢? 使用strings直接操作 Compare func Compare(a, b string) int 按照字典序比较两个字符串,通常情况下直接使用=,>,<会更快一些. Contains,ContainsAny 和 ContainsRune func Contains(s, substr string) bool func ContainsAny(s, chars string) bool func Contai

  • Golang之sync.Pool使用详解

    前言 我们通常用 Golang 来开发并构建高并发场景下的服务,但是由于 Golang 内建的GC机制多少会影响服务的性能,因此,为了减少频繁GC,Golang提供了对象重用的机制,也就是使用sync.Pool构建对象池. sync.Pool介绍 首先sync.Pool是可伸缩的临时对象池,也是并发安全的.其可伸缩的大小会受限于内存的大小,可以理解为是一个存放可重用对象的容器.sync.Pool设计的目的就是用于存放已经分配的但是暂时又不用的对象,而且在需要用到的时候,可以直接从该pool中取.

随机推荐