go常见语法错误|青训营笔记

135 阅读8分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第1篇笔记

go易错总结

希望每天能总结15个易错点,这样离秃头就不太晚了

常规错误总结

  • 左大括号 { 不能单独放一行

    // 错误示例
    func main()                    
    {
        println("小林你好帅")
    }
    
  • 简短声明的变量只能在函数内部使用

    // 错误示例
    myvar := 1  
    func main() {
    }
    

1.使用简短声明来重复声明变量

不能对同一个变量进行多次建议简明声明,除非该简易声明变量旁出现了新的变量

// 错误示例
func main() {  
    one := 0
    one := 1 // error: no new variables on left side of :=
}


// 正确示例
func main() {
    one := 0
    one, two := 1, 2    // two 是新变量,允许 one 的重复声明。比如 error 处理经常用同名变量 err
}

2.不能使用简短声明来设置字段的值

struct 的变量字段不能使用 := 来赋值

type info struct {
	result int
}

func work() int {
	return 3
}

func main() {
	var data info
	data.result := work()    // error: non-name data.result on left side of :=
	fmt.Printf("info: %+v\n", data)
}

3.简易声明的变量作用域

简易声明的变量作用域只在代码块(可以理解为花括号)内有效(包括内部调用也有效)

func main() {
	x := 1
	println(x)        // 1
	{
		println(x)    // 1
		x := 2
		println(x)    // 2    // 新的 x 变量的作用域只在代码块内部
	}
	println(x)        // 1
}

4.显式类型变量的初始值

nil 是interface、function、pointer、map、slice和channel类型变量的初始值

5.没有对map、slice进行make等申请空间操作

// map 错误示例
func main() {
    var m map[string]int
    m["one"] = 1       
}    

6.不能对已经make的变量进行cap()操作获取容量

// 错误示例
func main() {
    m := make(map[string]int, 99)
    println(cap(m))     // error: invalid argument m1 (type map[string]int) for cap  
}

##7.不能对string类型进行初始化为nil,其默认值为""

func main() {
    var s string = nil    // cannot use nil as type string in assignment
    if s == nil {    // invalid operation: s == nil (mismatched types string and nil)
        s = "default"
    }
}

8.数组类型作为函数参数的时候必须要写清他的容量

func main() {
    x := [3]int{1,2,3}

    func(arr []int) { //没有些清楚容量,这个编译器认为传递的是切片
    }(x)
}

9.数组作为函数参数时,其为值传递

// 数组使用值拷贝传参
func main() {
    x := [3]int{1,2,3}

    func(arr [3]int) {
        arr[0] = 7
        fmt.Println(arr)    // [7 2 3]
    }(x)
    fmt.Println(x)            // [1 2 3]    // 并不是你以为的 [7 2 3]
}

##10.数组作为函数参数时,想地址传递需要:&[x]

// 传址会修改原数据
func main() {
    x := [3]int{1,2,3}

    func(arr *[3]int) {
        (*arr)[0] = 7    
        fmt.Println(arr)    // &[7 2 3]
    }(&x)
    fmt.Println(x)    // [7 2 3]
}

11.切片类型作为函数参数时,其为地址传递

// 会修改 slice 的底层 array,从而修改 slice
func main() {
    x := []int{1, 2, 3}
    func(arr []int) {
        arr[0] = 7
        fmt.Println(x)    // [7 2 3]
    }(x)
    fmt.Println(x)    // [7 2 3]
}

12.range遍历切片时

range遍历切片的时候返回的是两个值,第一个是索引,第二个是值内容

func main() {
    x := []string{"a", "b", "c"}
    for _, v := range x {    // 使用 _ 丢弃索引
        fmt.Println(v)
    }
}

13.简化版两个数进行交换

func main() {
    one := 0
    one, two := 1, 2    
    one, two = two, one    // 交换两个变量值的简写
}

##14.二维数组的创建

// 使用各自独立的 6 个 slice 来创建 [2][3] 的动态多维数组

// 方法1
func main() {
    x := 2
    y := 4

    table := make([][]int, x)
    for i  := range table {  //需要注意的是,i为索引值,i后面也可以跟个参数,但是没必要
        table[i] = make([]int, y)
    }
}

//方法2
func main() {
    h, w := 2, 4
    raw := make([]int, h*w)

    for i := range raw {
        raw[i] = i
    }

    // 初始化原始 slice
    fmt.Println(raw, &raw[4])    // [0 1 2 3 4 5 6 7] 0xc420012120 

    table := make([][]int, h)
    for i := range table {

        // 等间距切割原始 slice,创建动态多维数组 table
        // 0: raw[0*4: 0*4 + 4]
        // 1: raw[1*4: 1*4 + 4]
        table[i] = raw[i*w : i*w + w]
    }

    fmt.Println(table, &table[1][0])    // [[0 1 2 3] [4 5 6 7]] 0xc420012120
}

15.访问 map 中不存在的 key

判断map中是否存在key,不能通过其返回值判断,而实通过接收到的第2个参数判断。因为go中map里面存在某个key,但是其value没初始化,则value值为对应数据类型的零值,比如 nil、'' 、false 和 0

// 错误的 key 检测方式
func main() {
    x := map[string]string{"one": "2", "two": "", "three": "3"}
    if v := x["two"]; v == "" {
        fmt.Println("key two is no entry")    // 键 two 存不存在都会返回的空字符串
    }
}

// 正确示例
func main() {
    x := map[string]string{"one": "2", "two": "", "three": "3"}
    if _, ok := x["two"]; !ok {
        fmt.Println("key two is no entry")
    }
}

16. string 类型的值是常量,不可更改(纯英文)

// 修改字符串的错误示例
func main() {
    x := "text"
    x[0] = "T"        // error: cannot assign to x[0]
    fmt.Println(x)
}


// 修改示例
func main() {
    x := "text"
    xBytes := []byte(x)
    xBytes[0] = 'T'    // 注意此时的 T 是 rune 类型
    x = string(xBytes)
    fmt.Println(x)    // Text
}

17. string 类型修改汉字需要转化为rune slice类型

func main() {
    x := "text"
    xRunes := []rune(x)
    xRunes[0] = '我'
    x = string(xRunes)
    fmt.Println(x)    // 我ext
}

18. string 索引操作返回的是byte值

func main() {
    x := "ascii"
    fmt.Println(x[0])        // 97
    fmt.Printf("%T\n", x[0])// uint8
}

19. 计算字符串长度

Go 的内建函数len() 返回的是字符串的 byte 数量 , 如果要得到字符串的字符数,可使用 "unicode/utf8" 包中的RuneCountInString(str string) (n int)

func main() {
	data := "12345"
	fmt.Println(len(data))   //5
}
func main() {
	data := "小林我爱你"
	fmt.Println(utf8.RuneCountInString(data))  //5
}

注意: RuneCountInString 并不总是返回我们看到的字符数,因为有的字符会占用 2 个 rune:

func main() {
    char := "é"
    fmt.Println(len(char))    // 3
    fmt.Println(utf8.RuneCountInString(char))    // 2
    fmt.Println("cafe\u0301")    // café    // 法文的 cafe,实际上是两个 rune 的组合
}

20. 在多行 array、slice、map初始化时缺少 , 号

func main() {
    x := []int {
        1,
        2    // syntax error: unexpected newline, expecting comma or }
    }
    y := []int{1,2,}    // 声明语句中 } 折叠到单行后,尾部的 , 不是必需的。
    z := []int{1,2}    
}

21. log.Fatal 和 log.Panic 不只是 log

log 标准库提供了不同的日志记录等级,与其他语言的日志库不同,Go 的 log 包在调用 Fatal*()Panic*() 时能做更多日志外的事,如中断程序的执行等:

func main() {
    log.Fatal("Fatal level log: log entry")        // 输出信息后,程序终止执行
    log.Println("Nomal level log: log entry")
}

22. range 迭代 map,迭代结果会不一样

func main() {
    m := map[string]int{"one": 1, "two": 2, "three": 3, "four": 4}
    for k, v := range m {
        fmt.Println(k, v)
    }
}

如果你去 Go Playground 重复运行上边的代码,输出是不会变的,只有你更新代码它才会重新编译。重新编译后迭代顺序是被打乱的:

23. switch 中的 fallthrough 语句

switch语句中的 case 代码块会默认带上 break,但可以使用 fallthrough 来强制执行下一个 case 代码块。

func main() {
    isSpace := func(char byte) bool {
        switch char {
        case ' ':    // 空格符会直接 break,返回 false // 和其他语言不一样
        // fallthrough    // 返回 true
        case '\t':
            return true
        }
        return false
    }
    fmt.Println(isSpace('\t'))    // true
    fmt.Println(isSpace(' '))    // false
}

也可以改写 case 为多条件判断

func main() {
    isSpace := func(char byte) bool {
        switch char {
        case ' ', '\t':   // case ' ' || '\t' 这种写法报错
            return true
        }
        return false
    }
    fmt.Println(isSpace('\t'))    // true
    fmt.Println(isSpace(' '))    // true
}

24. 按位取反^

// 错误的取反操作
func main() {
    fmt.Println(~2)        // bitwise complement operator is ^
}


// 正确示例
func main() {
    var d uint8 = 2
    fmt.Printf("%08b\n", d)        // 00000010
    fmt.Printf("%08b\n", ^d)    // 11111101
}

25. go优先级列表

    Precedence    Operator
        5             *  /  %  <<  >>  &  &^
        4             +  -  |  ^
        3             ==  !=  <  <=  >  >=
        2             &&
        1             ||

26. 以小写字母开头的字段成员是无法被encode

以小写字母开头的字段成员是无法被外部直接访问的,所以 struct 在进行 json、xml、gob 等格式的 encode 操作时,这些私有字段会被忽略,导出时得到零值:

type MyData struct {
	Age int
	name string
}
func main() {
    in := MyData{18, "faker"}
    fmt.Printf("%#v\n", in)    // main.MyData{age:1, name:"faker"}

    encoded, _ := json.Marshal(in)
    fmt.Println(string(encoded))    // {"age":18}    // 私有字段 two 被忽略了

    var out MyData
    json.Unmarshal(encoded, &out)
    fmt.Printf("%#v\n", out)     // main.MyData{age:18, name:""}
}

27. 使用值为nil的channel

在一个值为 nil 的 channel 上发送和接收数据将永久阻塞:

func main() {
    var ch chan int // 未初始化,值为 nil
    for i := 0; i < 3; i++ {
        go func(i int) {
            ch <- i  // 这里直接报错 不能进行数据的接收
        }(i)
    }

    fmt.Println("Result: ", <-ch)
    time.Sleep(2 * time.Second)
}

28. 当struct方法为值传递,除了map与slice,其他类型不能修改原始值

struct 方法里面,接收的是值得话,那么不会改变变量内容(除了map, slice,参考上述第11条),除非传递是指针,或者变量就是指针

type data struct {
    num   int   // 不会修改其内容
    key   *string   // 这里变量是指针,如果用下面函数进行值接收,还是会修改其内容
    items map[string]bool // 因为是map类型,所以即使是值传递还是会修改其内容
}

func (this data) valueFunc() {
    this.num = 8
    *this.key = "valueFunc.key"
    this.items["valueFunc"] = true
}

func main() {
    key := "key1"
	
    d := data{1, &key, make(map[string]bool)}
    
    // num=1  key=key1  items=map[]
    fmt.Printf("num=%v  key=%v  items=%v\n", d.num, *d.key, d.items) 

    d.valueFunc()    // 修改 key 和 items 的值
    
    // num=1  key=valueFunc.key  items=map[valueFunc:true]
    fmt.Printf("num=%v  key=%v  items=%v\n", d.num, *d.key, d.items)
}
type data struct {
name  []string    // 这里我添加了 切片字段
}

func (this data) valueFunc() {
this.name[0] = "faker"
}

func main() {
key := "key1"

d := data{make([]string, 1)}
//name=[]
fmt.Printf("name=%v\n", d.name)

d.valueFunc()    // 修改 name 的值
// name=[faker]
fmt.Printf("name=%v\n", d.name)
}
type data struct {
	num   int
	key   *string
	items map[string]bool
}

func (this *data) pointerFunc() {  // 进行得是指针传递,所以 所有变量都会被修改
	this.num = 7
	*this.key = "faker"
	this.items["valueFunc"] = true
}



func main() {
	key := "key1"

	d := data{1, &key, make(map[string]bool)}
    // num=1  key=key1  items=map[]
	fmt.Printf("num=%v  key=%v  items=%v\n", d.num, *d.key, d.items)

	d.pointerFunc()    // 修改 num 的值为 7 key 为faker items["valueFunc"] = true
    // num=7  key=faker  items=map[valueFunc:true]
	fmt.Printf("num=%v  key=%v  items=%v\n", d.num, *d.key, d.items)
}

29. 使用了值为nil的channel

在一个值为 nil 的 channel 上发送和接收数据将永久阻塞

func main() {
    var ch chan int // 初始化值为nil
    for i := 0; i < 3; i++ {
        go func(i int) {
           ch <- i  // 这里直接死锁,意思是堵塞在这里
        }(i)
    }
    fmt.Println("i:", <-ch)
    time.sleep(2 * time.Second)
}

30. 利用29的特性动态的打开和关闭case语句

func main() {
    inCh := make(chan int)
    outCh := make(chan int)

    go func() {
        var in <-chan int = inCh
        var out chan<- int
        var val int

        for {
            select {
            case out <- val:
                println("--------")
                out = nil
                in = inCh
            case val = <-in:
                println("++++++++++")
                out = outCh
                in = nil
            }
        }
    }()

    go func() {
        for r := range outCh {
            fmt.Println("Result: ", r)
        }
    }()

    time.Sleep(0)
    inCh <- 1
    inCh <- 2
    time.Sleep(3 * time.Second)
}

代码流程图