关于golang:浅析go中的类型比较

33次阅读

共计 4361 个字符,预计需要花费 11 分钟才能阅读完成。

概述

在最近的面试中被面试官问到 go 之间的类型比拟,答复的并不是十分好,基本上来说还是根底不够牢固啊!看了网上的一堆材料,本人做了一些简略的总结,哈哈!

go 中的类型

首先来看看 go 蕴含的最根底的集中类型

  • 根本类型:go 中最根本类型包含整型(int、uint、int8、uint8、int16、uint16、int32、uint32、int64、uint64、byte、rune等)、浮点型(float32、float64)、字符串(string也是个[]rune 数组)和比拟不罕用的复数类型(complex64/complex128)。
  • 复合类型:次要包含 构造体 数组
  • 援用类型:Slice、Map、Channel、指针
  • 接口类型:Error、io.Reader 等

go 作为强类型语言并不会和 PHP 等高级语言主动帮咱们进行类型转换,所以咱们在比拟时必须用 == 两边的类型必须统一,即便他们底部类型统一也不行。看上面的代码

package main
import "fmt"
type A struct {Id int}
type B struct {Id int}
func main() {
    var a int
    var b int16
    // 编译报错:invalid operation a == b (mismatched types int and int16)
    fmt.Println(a == b)
    aStruct := A{Id:5}
    bStruct := B{Id:5}
    // 编译报错:invalid operation: aStruct == bStruct (mismatched types A and B)
    fmt.Println(aStruct == bStruct)
}

所以 go 不会帮咱们做隐式转换,即便底层的类型统一,也不能比拟。
接下来咱们从不同的类型来剖析是否能够进行比拟。

根本类型

go 的根本类型就比较简单,只有类型是一样的,那么他们就是能够比拟的,举个栗子:

package main
import "fmt"
func main() {
    var a int = 0
    var b int = 1
    // 输入 false
    fmt.Println(a == b)
}

不过根本类型中也要留神浮点型的比拟就不像咱们事实中的一样,比方 0.1+0.2 在计算中运行后果就不是 0.3 了,而是 0.30000000000000004 了

package main
import "fmt"

func main() {
    var a float64=0.1
    var b float64=0.2
    // 0.30000000000000004
    fmt.Println(a+b)
}

为什么会这样,能够看下 draveness(https://github.com/draveness) 大佬的这篇文章 https://draveness.me/whys-the…

复合类型

数组

面试中也常常会问到 go 数组和切片的区别。数组在 go 中是必须先确定长度的,也就是长度不能再去扩容。并且它是个值拷贝,做参数传到一个函数中被批改,那么内部的值还是一样的不变的。Slice 则相同。那么数组是否能够比拟呢,看上面的例子:

package main
import "fmt"
func main() {a := [2]int{1, 2}
    b := [2]int{1, 2}
    c := [2]int{1, 3}
    d := [3]int{1, 2, 4}
    fmt.Println(a == b) // true
    fmt.Println(a == c) // false
    fmt.Println(a == d) // invalid operation: a == d (mismatched types [2]int and [3]int)
}

能够看出,雷同长度的数组是能够比拟的,而不同长度的数组是不能进行比拟的。起因是什么呢?这是因为数组类型中, 数组的长度也是类型的一部分,不同长度的数组那么他们的类型也就被认为不同的,所以无奈比拟。

构造体

同样的 Struct 也是一样的。Struct的比拟也从外部类型开始比拟,每一类型的值相等才是相等的。如下例子:

package main
import "fmt"
type A struct {
    id int
    name string
}
func main() {a := A{id:5,name:"123"}
    b := A{id:5,name:"123"}
    c := A{id:5,name:"1234"}
    fmt.Println(a == b) // true
    fmt.Println(a == c) // false
}

那么能够了解成 Struct 构造体是能够比拟的吗。咱们再来看个例子:

package main
import "fmt"
type A struct {
    id int
    name string
    son []int}
func main() {a := A{id:5,name:"123",son:[]int{1,2,3}}
    b := A{id:5,name:"123",son:[]int{1,2,3}}
    fmt.Println(a == b) // invalid operation: a == b (struct containing []int cannot be compared)
}

怎么又变成不可比拟的呢?这就要看上面的援用类型了。

援用类型

下面中的例子构造体中带上切片就无奈比拟了,在 go 中 SliceMap被定义成不能比拟的类型。咱们来看

如果 Slice 是可比拟,那么用什么来定义是一样的切片呢?如果用地址,那么如果两个地址指向的 Slice 是一样的呢?这显然不适合。如果和数组一样的形式,那么我切片扩容了呢,就不相等了。所以长度和容量导致不好比拟。尽管能够在语言层面解决这个问题,然而 golang 团队认为不值得为此消耗精力。所以 Slice 被当成不可比拟。
同样的 Map 也被定义成不可比拟类型。那么援用类型都是不可比拟吗? 也不是,看个例子:

package main
import "fmt"
type A struct {
    id int
    name string
}
func main() {a := &A { a : 1, b : "test1"}
    b := &A {a : 1, b : "test1"}
    c := a
    fmt.Println(a == b) // false
    fmt.Println(a == c) // true
    ch1 := make(chan int, 1)
    ch2 := make(chan int, 1)
    ch3 := ch1
    fmt.Println(ch1 == ch2) // false
    fmt.Println(ch1 == ch3) // true
}

援用类型变量存储的是某个变量的内存地址。所以援用类型变量的比拟,判断的是这两个援用类型存储的是不是同一个变量。

  • 如果是同一个变量,则内存地址必定也一样,则援用类型变量相等,用 ”==” 判断为 true
  • 如果不是同一个变量,则内存地址必定不一样,”==” 后果为 false

接口类型

Go 语言依据接口类型是否蕴含一组办法将接口类型分成了两类:

  • 应用 runtime.iface构造体示意蕴含办法的接口
  • 应用 runtime.eface构造体示意不蕴含任何办法的 interface{} 类型
type eface struct { // 16 字节
    _type *_type
    data  unsafe.Pointer
}

type iface struct { // 16 字节
    tab  *itab
    data unsafe.Pointer
}

所以咱们能够得悉,一个接口值是由两个局部组成的,即该接口对应的类型和接口对应具体的值。接口值的比拟波及这两局部的比拟,只有当类型和值都相等(动静值应用 == 比拟),两个接口值才是相等的。看个例子:

var a interface{} = 0
var b interface{} = 2
var c interface{} = 0
var d interface{} = 0.0
fmt.Println(a == b) // false
fmt.Println(a == c) // true
fmt.Println(a == d) // false

ac 类型雷同(都是 int),值也雷同(都是0,根本类型比拟),故两者相等。ab类型雷同,值不等,故两者不等。ad 类型不同,aintdfloat64,故两者不等。

type A struct {
    a int
    b string
}

var a interface{} = A { a: 1, b: "test"}
var b interface{} = A { a: 1, b: "test"}
var c interface{} = A { a: 2, b: "test"}

fmt.Println(a == b) // true
fmt.Println(a == c) // false

var d interface{} = &A { a: 1, b: "test"}
var e interface{} = &A { a: 1, b: "test"}
fmt.Println(d == e) // false

ab 类型雷同(都是 A),值也雷同(构造体A),故两者相等。ac类型雷同,值不同,故两者不等。de 类型雷同(都是*A),值应用指针(援用)类型的比拟,因为不是指向同一个地址,故不等。

不过须要留神的是,如果接口中类型是切片或者 Map 不可比拟的类型,那么会间接报错的。看个例子:

var a interface{} = []int{1, 2}
var b interface{} = []int{1, 2}
// panic: runtime error: comparing uncomparable type []int
fmt.Println(a == b)

ab 的类型是切片类型,而切片类型不可比拟,所以 a == bpanic

接口值的比拟不要求接口类型(留神不是动静类型)完全相同,只有一个接口能够转化为另一个就能够比拟。例如:

var f *os.File

var r io.Reader = f
var rc io.ReadCloser = f
fmt.Println(r == rc) // true

var w io.Writer = f
// invalid operation: r == w (mismatched types io.Reader and io.Writer)
fmt.Println(r == w)

r的类型为 io.Reader 接口,rc的类型为 io.ReadCloser 接口。查看源码,io.ReadCloser的定义如下:

type ReadCloser interface {
    Reader
    Closer
}

io.ReadCloser可转化为io.Reader,故两者可比拟。

io.Writer 不可转化为io.Reader,编译报错。

总结 cp

  • 可比拟:int、ifloat、string、bool、complex、pointe、channel、interface、array
  • 不可比拟:slice、map、function
  • 复合类型中如果带有不可比拟的类型,那么该类型也是不可比拟的。能够了解不可比拟类型具备传递性。

参考

https://draveness.me/golang/d…
https://juejin.cn/post/684490…

正文完
 0