import "fmt"
type A struct {
age int
name string
}
func StructCompare1() {
aObj1 := A{
age: 13,
name: "张三",
}
aObj2 := A{
age: 13,
name: "张三",
}
fmt.Println(aObj1 == aObj2) // true
aObj3 := &A{
age: 13,
name: "张三",
}
aObj4 := &A{
age: 13,
name: "张三",
}
fmt.Println(aObj3 == aObj4) // false
var aObj5 A
fmt.Println(aObj5) //{0 } ,未明确初始化时,struct 实例的成员取各自的零值
//fmt.Println( aObj5 == nil) // 报错,无法将 nil 转换为类型 A
var aObj6 *A
fmt.Println(aObj6) // ,指针类型数据的零值为 nil
fmt.Println(aObj6 == nil) // true,指针类型的数据可以和 nil 比较
}
go 语言中规定 slice 之间不能比较,因此我们不能使用==操作符来判断两个slice是否含有全部相等元素。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PBbMGQ9Y-1620873981270)(pics/20210513094853152_1085247374.png)]
但标准库提供了高度优化的 bytes.Equal 函数来判断两个字节型 slice 是否相等( []byte ):
func SliceCompare() {
slice1 := []byte{1, 2, 3}
slice2 := []byte{1, 2, 3}
fmt.Println(bytes.Equal(slice1, slice2)) // true
}
对于其他类型的 slice,我们必须自己展开每个元素进行比较,如:
func equal(x, y []string) bool {
if len(x) != len(y) {
return false
}
for i := range x {
if x[i] != y[i] {
return false
}
}
return true
}
为何slice不直接支持比较运算符呢?
slice 唯一合法的比较操作是和 nil 比较,一个 slice 的零值为 nil 。
一个 nil 值的 slice 并没有底层数组。一个 nil 值的 slice 的长度和容量都是0,但是也有非 nil 值的 slice 的长度和容量也是 0 的,例如 []int{}
或 make([]int, 3)[3:]
。
与任意类型的 nil 值一样,我们可以用 []int(nil)
类型转换表达式来生成一个对应类型 slice 的 nil 值。
和 slice 一样,map 之间也不能进行相等比较; 唯一的例外是和 nil 进行比较。
要判断两个 map 是否包含相同的 key 和 value,我们必须通过一个循环实现:
func equal(x, y map[string]int) bool {
if len(x) != len(y) {
return false
}
for k, xv := range x {
if yv, ok := y[k]; !ok || yv != xv {
return false
}
}
return true
}
上述例子中在判断 xv 和 yv 是否相等时,先通过 ok 判断了 yv 是否存在,这一步判断是必须的,如果简单地用 xv != y[k] 则可能会出现错误的结果,比如下面的例子:
equal(map[string]int{"A": 0}, map[string]int{"B": 42})
在比较上面这两个 map 时,如果没有对 ok 进行判断,那么从后面的 map 中取 y["A"]
时,由于后面的 map 中不存在 A 这个键,所有会拿到值类型的零值,即0,此时 x["A"]
的值恰好也是 0 ,这样就会出现错误的结果。
在 go 语言中,函数可以作为变量的类型,如 var f func(int) int
,该语句声明了一个变量 f ,其类型为接收 int 类型参数且返回值为 int 的函数。
// squares返回一个匿名函数。
// 该匿名函数每次被调用时都会返回下一个数的平方。
func squares() func() int {
var x int
return func() int {
x++
return x * x
}
}
func main() {
f := squares()
fmt.Println(f()) // "1"
fmt.Println(f()) // "4"
fmt.Println(f()) // "9"
fmt.Println(f()) // "16"
}
在上述示例中,将函数赋值给变量 f 时,函数内部的成员变量 x 的生命周期会受变量 f 的生命周期影响,所以输出结果依次是 1、4、9、16。也就是说,函数类型的值不仅仅是一串代码,还记录了状态。在 squares 中定义的匿名内部函数可以访问和更新 squares 中的局部变量,这意味着匿名函数和 squares 中,存在变量引用。这就是函数值属于引用类型和函数值不可比较的原因。
在上上述示例中,如果不将函数赋值给变量,而是直接调用,其写法和运行结果如下
fmt.Println(Squares()()) //1
fmt.Println(Squares()()) //1