Go系列:语法糖和比较规则

3,782 阅读6分钟

本文标题是语法糖和比较规则,但是内容安排方面不局限于语法糖,内容主要是总结整理Go使用过程中遇到的易错或易忽视的语法点以及数据的可比较规则。本文内容会持续补充和更新。

语法糖

:= 操作符

短类型申明(推导申明)操作符:=仅限在函数内使用,也就是只能申请临时变量,申明全局变量使用var操作符。

示例:

// 交换两个变量
func exc() {
    a, b := 20, 21
    b, a = a, b
}

_ 操作符

1、包的空导入

如果导入的包的名字没有在文件中引用,会产生一个编译错误。但是,有时候我们必须导入一个包以对包级别的变量执行初始化表达式求值,并执行init函数,为了防止这种错误,我们使用_重命名导入一个不使用的包。示例:

import _ "image/png"

2、忽略字段

将不需要的值赋给空标识符,示例:

_, ok = m[key] //查询map 丢弃结果

3、结构体标签

结构体标签里,用_对不需要序列化的字段做忽略处理,示例:

type User struct {
    ID   int     `json:"-"`            // 该字段不进行序列化
    Name string  `json:name,omitempy`  // 如果为类型零值或空值,序列化时忽略该字段
}

... 操作符

1、"不定长"数组定义

我们知道数组一般有固定长度,那么能不能申请长度不固定(不知道长度)的数组呢,答案是可以,用...申明不定长数组。数组的长度由初始化数组的元素个数决定。其实并不是长度不确定,是长度在写代码的时候不确定,程序编译的时候其实已经确定了,其实和定长数组没有区别。示例:

    a := [...]int{1,2,3}
    fmt.Printf("%T\n", n) // [3]int 数组的长度是数组类型的一部分 a 的类型是 [3]int
    
    r := [...]int{99:1}   // 申请拥有100个元素的数组 r 
    fmt.Printf("%T\n", n) // [100]int 除了最后一个元素值为 1 其他元素都为类型零值 0

2、切片解包

slice 追加 slice类型数据的时候,使用...将切片转换为参数列表。示例:

    var x []int
    x = append(x, 1)
    x = append(x, 2, 3)
    x = append(x, x...) //追加x中的所有元素
    fmt.Println(x)      //"[1,2,3,1,2,3]"

3、变长参数

函数声明时,形参中的...标识该函数可以接受可变长度参数列表。示例:

//slice 追加 append 函数声明
func append(slice []Type, elems ...Type) []Type {
    ...
} 

三个操作符

通常函数使用额外的返回值来指示一些错误结果,这里有三个操作符也有类似的行为。

1、map查询

// 判断 map 中键否存在的特殊写法
if v, ok := map[key]; ok {
    fmt.Println("map中存在键为key的元素,值为",v)
}

2、类型断言

    v, ok := x.(T)

3、通道接收

    v, ok := <-ch

比较规则

总结

数据类型==作比较nil作比较说明
数值可以--
浮点可以--
复数可以--
布尔可以--
字符可以--
数组可以不可以如果数组中元素类型可比较,则数组可比较
struct可以不可以如果struct中所有的字段都是可比较的,则两个struct是可比较的;如果struct对应的非空白字段相等,则它们相等
指针可以可以如果两个指针指向同一个变量,则这两个指针相等;两个指针同为nil,它们也相等
通道可以可以如果两个通道是由同一个make创建的,则这两个通道相等;两个通道值都为nil,它们也相等
接口可以可以如果两个接口值的动态值和动态类型都相等,则这两个接口相等;两个接口值都为nil,那么它们是相等的。
map不可以可以map不可比较,唯一合法的就是和nil做比较,比较两个map是否有相同的键值,必须写一个循环
slice不可以可以标准库里提供了bytes.Equal来比较两个[]bytesslice 是否相等,对于其他类型的需要自己写函数实现
func不可以可以函数类型零值是nil,调用一个nil函数会导致宕机

指针

两个指针指向同一个变量,则这两个指针相等,或者两个指针同为nil,它们也相等。指针值可以与nil比较。

    var num1, num2 int
    num1 = 8
    num2 = 8

    pt1 := &num1
    pt2 := &num1
    pt3 := &num2

    var pt4 *int //定义一个空指针

    // 只有指向同一个变量,两个指针才相等
    fmt.Printf("%v %v\n", pt1 == pt2, pt1 == pt3)  //true false

    // 指针可以与nil直接比较
    fmt.Printf("%v %v\n", pt4 == nil, pt1 == nil) //true false

chan

如果两个通道是由同一个make创建的,或者两个通道值都为nil,那么它们是相等的。

    ch1 := make(chan int)
    ch2 := make(chan int)
    
    var ch3 chan int
    ch3 = ch1
    
    var ch4 chan int
    var ch5 chan int
    var ch6 chan string
    
    // 同一个make创建的通道才相等
    fmt.Printf("%v %v\n", ch1 == ch2, ch3 == ch1) //false true
    
    // 通道值可与nil比较
    fmt.Printf("%v %v\n", ch4 == ch5, ch5 == ch1) //true true
    
    //有错误提示 两个不同类型的通道,即使都是空值,也不能比较
    fmt.Printf("%v\n", ch5 == ch6)

map

map中键的类型必须是可以通过==来进行比较的数据类型,所以map可以检验某一键是否已存在,值类型没有任何限制。虽然浮点数可以用==进行比较,但比较浮点数的相等性会有精度损失,所以浮点数作为map的键也不是一个好的选择。

    args := make(map[string]int) {
        "alice":   31,
        "charles": 34,
    }
    // 检验key是否存在
    if v, ok := args["alice"]; ok {
        fmt.Println(v)
    }
// 比较两个map是否拥有相同的键和值
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
}

slice

检查一个slice是否为空,应该使用len(s) == 0,而不是s == nil,因为s != nil的情况下 也可能是空。

    var s[]int     // len(s) == 0, s == nil  nil切片
    s = nil        // len(s) == 0, s == nil  nil切片
    s = []int(nil) // len(s) == 0, s == nil  nil切片
    s = []int{}    // len(s) == 0, s != nil  空切片

对于其他类型的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
}