20《Go语言入门》细讲interface和nil

这是我纯手写的《Go语言入门》,手把手教你入门Go。源码+文章,看了你就会,此处应有掌声!
文章中所有的代码我都放到了github.com/GanZhiXiong/go_learning这个仓库中。在看文章的时候,对照仓库中代码学习效果更佳!

目录

  • 前言
  • nil是预定义的标识符
  • 默认值nil
  • 相同类型的nil值可能无法比较,不同类型的nil值不能比较
  • 判断interface是否为nil的一个坑
    • interface类型和nil比较的应用场景
    • 面对这个坑,如何判断interface里面的值是否为空呢?
  • 参考
  • 支持

前言

在之前的文章中经常有写到类似下面的代码:

if err != nil {
	// do something...
}

那么什么是nil呢?

nil是预定义的标识符

nil是预定义的标识符,代表指针、通道、函数、接口、映射或切片的零值,也就是预定义好的一个变量

type Type int
var nil Type //是预定义好的变量

因此nil不是Go的关键字,所以你可以定义一个变量的名称为nil,你甚至可以改变nil的值。

var nil string
nil = "hi"
t.Log(nil)

默认值nil

在Go中,布尔类型的零值(默认值)为false,数值类型的零值为0,字符串类型的零值为空字符串
而以下类型的零值为nil且nil只能赋值给以下类型

  • 指针(包括unsafe的 )
  • map
  • slice
  • function
  • channel
  • interface

相同类型的nil值可能无法比较,不同类型的nil值不能比较

func TestNilCompare(t *testing.T) {
	var arr []int
	var arr1 []int
	var num *int
	var num1 *int
	t.Log(arr == nil)
	t.Log(arr1 == nil)
	t.Log(num == nil)
	t.Log(num1 == nil)

	// 相同类型的nil值可能无法比较
	t.Log(num == num1)
	// Invalid operation: arr == arr1 (operator == is not defined on []int)
	//t.Log(arr == arr1)

	// 不同类型的nil值不能比较
	// Invalid operation: arr == num (mismatched types []int and *int)
	//t.Log(arr == num)

	t.Log(unsafe.Pointer(&arr))
	t.Log(&num)
	t.Logf("%p\n", arr)
	t.Logf("%p", num)
}
=== RUN   TestNilCompare
    20_interface_nil_test.go:57: true
    20_interface_nil_test.go:58: true
    20_interface_nil_test.go:59: true
    20_interface_nil_test.go:60: true
    20_interface_nil_test.go:63: true
    20_interface_nil_test.go:71: 0xc0000b4080
    20_interface_nil_test.go:72: 0xc0000a0028
    20_interface_nil_test.go:73: 0x0
    20_interface_nil_test.go:74: 0x0
--- PASS: TestNilCompare (0.00s)
PASS

判断interface是否为nil的一个坑

接口在底层的实现由两个部分:type (类型)和 data(值)。
type是存储变量的动态类型;
data是存储变量的具体数据。

func TestInterfaceNil1(t *testing.T) {
	// val的底层结构应该是(int64, 1)
	var val interface{} = int64(1)
	t.Log(reflect.TypeOf(val))
	// 因为字面量的整数在Go中默认类型为int,所以val的底层结构变成了(int, 2)
	val = 2
	t.Log(reflect.TypeOf(val))
}
=== RUN   TestInterfaceNil1
    20_interface_nil_test.go:46: int64
    20_interface_nil_test.go:49: int
--- PASS: TestInterfaceNil1 (0.00s)
PASS

interface是否nil,分为以下两种情况:

  • 为nil
    在源码中,显式地将 nil 赋值给接口时,接口的 type 和 data 都将为 nil。此时,接口与 nil 值判断是相等的。
  • 不为nil
    但如果将一个带有类型的 nil 赋值给接口时,所以data 为 nil,type不为 nil,此时,接口与 nil 判断将不相等。

因此这是一个坑啊,在其他编程语言直接判断是否等于nil即可,但是Go中不能这样做。
下面用一个简单的示例来说明下:

func TestInterfaceNil(t *testing.T) {
	var a interface{}
	t.Log(a)
	t.Log(a == nil) // true

	a = nil
	t.Log(a)
	t.Log(a == nil) // true

	// cannot convert nil to type int
	//var b interface{} = (int)(nil)
	var b interface{} = (*int)(nil)
	t.Log(b)
	t.Log(b == nil) // false
	b = 1
	t.Log(b == nil) // false

	t.Log()

	// 接口指针类型变量,底层结构为{*interface{}, nil},所以不为nil
	var c interface{} = (*interface{})(nil)
	c = 123
	t.Log(c)
	t.Log(c == nil) // false

	var d = (*interface{})(nil)
	// Cannot use '2' (type untyped int) as type *interface{}
	//d = 2
	// 能编译通过,但是会panic,这是因为d指向的是一个无效的内存地址
	//*d = 2
	t.Log(d)
	t.Log(d == nil) // true
}

interface类型和nil比较的应用场景

一个常见的场景就是error接口类型的值与nil比较。
请看代码:

type data struct{}

func (d *data) Error() string { return "" }

// 这样做不严谨,因为返回的指针p被包装成error类型,所以返回的底层结构为(*data, nil)
// 返回值nil相比则不相等,这不是预期的结果。
func test() error {
	var p *data
	return p
}

// 因此得用下面这种方式,如果出错了,返回p;没出错,则返回nil。
func testRight() error {
	var p *data

	isTestError := false
	if isTestError {
		return p
	}
	return nil
}

func TestNilError(t *testing.T) {
	var err error = test()
	if err == nil {
		t.Log("err is nil, success")
	} else {
		t.Log("err is not nil, error")
	}

	err = testRight()
	if err == nil {
		t.Log("err is nil, success")
	} else {
		t.Log("err is not nil, error")
	}
}

面对这个坑,如何判断interface里面的值是否为空呢?

利用反射来判断:


func IsNil(value interface{}) bool {
	if value == nil {
		return true
	}
	var rv reflect.Value
	if v, ok := value.(reflect.Value); ok {
		rv = v
	} else {
		rv = reflect.ValueOf(value)
	}
	switch rv.Kind() {
	case reflect.Chan,
		reflect.Map,
		reflect.Slice,
		reflect.Func,
		reflect.Ptr,
		reflect.Interface,
		reflect.UnsafePointer:
		return rv.IsNil()
	}
	return false
}

func TestInterfaceNilCompare(t *testing.T) {
	var s fmt.Stringer
	t.Log(s, IsNil(s))

	var i interface{}
	t.Log(i, IsNil(i))

	g := GetStringer()
	t.Log(g, IsNil(g))

	test := test()
	t.Log(test, IsNil(test))
	test1 := testRight()
	t.Log(test1, IsNil(test1))
}

参考

  • golang: 详解interface和nil
  • Go语言接口的nil判断

支持

  • 我会持续编写【软件开发相关】的文章,保持每周至少一篇文章。
  • 如果你也是【软件工程师】,【关注❤️我】,一定会对你有所帮助。
  • 如果这篇文章对你有所帮助,那就麻烦,【点赞】
  • 您的支持将给与我更大的动力,谢谢。

你可能感兴趣的:(#,Go语言入门,go,golang,go语言)