Go语言中新手开发人员的常见错误汇总

2023-06-01 00:00:00 错误 汇总 开发人员

新手Golang开发人员的常见错误汇总 : 


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

Go语言遵守分号注入规则(automatic semicolon injection):

编译器会在每行代码尾部特定分隔符后加;来分隔多条语句


// 错误示例

func main()                    

{

    println("xxx")

}

// 正确示例

func main() {

    println("xxx")

}



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

struct 的变量字段不能使用:= 来赋值以使用预定义的变量来避免解决:


// 错误示例

package main

import "fmt"

type info struct {

    result int

}

func work() (int, error) {

    return 3, nil

}

func main() {

    var data info

    data.result, err := work() // error: non-name data.result on left side of :=

    if err != nil{

        fmt.Println(err)

        return

    }

    fmt.Printf("info: %+v\n", data)

}


// 正确示例

func work() (int, error) {

    return 3, nil

}

func main() {

    tmp, err := work() // error: non-name data.result on left side of :=

    if err != nil {

        fmt.Println(err)

        return

    }

    fmt.Printf("info: %+v\n", tmp)

}

这是 Go 开发者常犯的错,而且不易被发现。可使用 vet 工具来诊断这种变量覆盖,Go 默认不做覆盖检查,添加 -shadow 选项来启用:


 > go tool vet -shadow main.go

main.go:9: declaration of "x" shadows declaration at main.go:5

注意 vet 不会报告全部被覆盖的变量,可以使用 go-nyet 来做进一步的检测:


 > $GOPATH/bin/go-nyet main.go

main.go:10:3:Shadowing variable `x`



4. 显式类型的变量无法使用 nil 来初始化


nil 是 一下 6 种 类型变量的默认初始值。但声明时不指定类型,编译器也无法推断出变量的具体类型。


interface

function

pointer

map

slice

channel

// 错误示例

func main() {

    var x = nil    // error: use of untyped nil

    _ = x

}


// 正确示例

func main() {

    var x interface{} = nil

    _ = x

}



5. 直接使用值为 nil 的 slice、map


// map 错误示例

func main() {

    var m map[string]int

    m["one"] = 1        // error: panic: assignment to entry in nil map

    // m := make(map[string]int)// map 的正确声明,分配了实际的内存

}    


// slice 正确示例

func main() {

    var s []int

    s = append(s, 1)

}

func main() {

    //m := map[string]int{}

    m := make(map[string]int, 1)

    m["one"] = 1

}



6.map 容量


在创建 map 类型的变量时可以指定容量,但不能像 slice 一样使用 cap () 来检测分配空间的大小:


// 错误示例

func main() {

    m := make(map[string]int, 99)

    println(cap(m))     // error: invalid argument m1 (type map[string]int) for cap  

}

按照官方文档 cap 函数参数中可以放如下类型:


array

pointer

sliice

channel



7.string 类型的变量值不能为 nil


对那些喜欢用 nil 初始化字符串的人来说,这就是坑:


初始化字符串为空,可以用 var 直接定义即可,默认就是空 “”


// 错误示例

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"

    }

}

// 正确示例

func main() {

    var s string    // 字符串类型的零值是空串 ""

    if s == "" {

        s = "default"

    }

}

能初始化为 nil 的类型有如下 6 种,上述也有提到过


指针

通道

函数

接口

map

切片



8.Array 类型的值作为函数参数


在 Go 中,数组是值。作为参数传进函数时,传递的是数组的原始值拷贝,此时在函数内部是无法更新该数组的:


// 数组使用值拷贝传参

func main() {

    x := [3]int{3,4,5}

    func(arr [3]int) {

        arr[0] = 8

        fmt.Println(arr)    // [8 4 5]

    }(x)

    fmt.Println(x)            // [3 4 5]    // 并不是你以为的 [8 4 5]

}

如果想修改参数中的原有数组的值,有如下 2 种方式:


直接传递指向这个数组的指针类型


// 传址会修改原数据

func main() {

    x := [3]int{3,4,5}

    func(arr *[3]int) {

        (*arr)[0] = 8    

        fmt.Println(arr)    // &[8 4 5]

    }(&x)

    fmt.Println(x)    // [8 4 5]

}



直接使用 slice:即使函数内部得到的是 slice 的值拷贝,但依旧会更新 slice 的原始数据(底层 array)


因为 slice 是引用的方式传递


// 会修改 slice 的底层 array,从而修改 slice

func main() {

    x := []int{1, 2, 3}

    func(arr []int) {

        arr[0] = 7

        fmt.Println(x)    // [8 4 5]

    }(x)

    fmt.Println(x)    // [8 4 5]

}

golang 中分为值类型和引用类型


值类型分别有:


int 系列、float 系列、bool、string、数组和结构体


引用类型有:


指针、slice 切片、管道 channel、接口 interface、map、函数等


值类型的特点是:


变量直接存储值,内存通常在栈中分配


引用类型的特点是:


变量存储的是一个地址,这个地址对应的空间里才是真正存储的值,内存通常在堆中分配




9. 访问 map 中不存在的 key


和其他编程语言类似,如果访问了 map 中不存在的 key 则希望能返回 nil,


Go 则会返回元素对应数据类型的零值,比如 nil、’’ 、false 和 0,取值操作总有值返回,故不能通过取出来的值来判断 key 是不是在 map 中。


对于值类型:布尔类型为 false, 数值类型为 0,字符串为 ""


数组和结构会递归初始化其元素或字段


其初始值取决于元素类型或字段




对于引用类型: 均为 nil,包括指针 pointer,函数 function,接口 interface,切片 slice,管道 channel,映射 map。




检查 key 是否存在可以用 map 直接访问,检查返回的第二个参数即可:


// 错误的 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")

    }

}



10.string 类型的值是常量,不可更改,可以使用 rune 来转换


尝试使用索引遍历字符串,来更新字符串中的个别字符,是不允许的,因为 string 类型的值是常量


解决方式分为英文字符串,和中文字符串 2 种




英文字符串:


string 类型的值是只读的二进制 byte slice,将 string 转为 [] byte 修改后,再转为 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

}



中文字符串:


一个 UTF8 编码的字符可能会占多个字节,比如汉字就需要 3~4 个字节来存储,此时需要使用如下做法,使用 rune slice


将 string 转为 rune slice(此时 1 个 rune 可能占多个 byte),直接更新 rune 中的字符


func main() {

    x := "text"

    xRunes := []rune(x)

    xRunes[0] = '你'

    x = string(xRunes)

    fmt.Println(x)    // 你ext

}



11.string 与索引操作符


对字符串用索引访问返回的不是字符,而是一个 byte 值。


如果需要使用 for range 迭代访问字符串中的字符(unicode code point / rune),


标准库中有 "unicode/utf8" 包来做 UTF8 的相关解码编码。


另外 utf8string 也有像 func (s *String) At(i int) rune 等很方便的库函数。




12. 字符串并不都是 UTF8 文本


string 的值不必是 UTF8 文本,可以包含任意的值。只有字符串是文字字面值时才是 UTF8 文本,字串可以通过转义来包含其他数据。


判断字符串是否是 UTF8 文本,可使用 “unicode/utf8” 包中的 ValidString () 函数:


func main() {


    str1 := "ABC"

    fmt.Println(utf8.ValidString(str1))    // true

    str2 := "A\xfeC"

    fmt.Println(utf8.ValidString(str2))    // false

    str3 := "A\\xfeC"

    fmt.Println(utf8.ValidString(str3))    // true    // 把转义字符转义成字面值

}



13. 字符串的长度


在 Go 中:


使用 len 函数计算字符串的长度,实际上是计算 byte 的数量


func main() {

    char := "♥"

    fmt.Println(len(char))    // 3

}

如果要得到字符串的字符数,可使用 “unicode/utf8” 包中的 RuneCountInString(str string) (n int)


func main() {

    char := "♥"

    fmt.Println(utf8.RuneCountInString(char))    // 1

}

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


func main() {

    char := "é"

    fmt.Println(len(char))    // 3

    fmt.Println(utf8.RuneCountInString(char))    // 2

    fmt.Println("cafe\u0301")    // café    // 法文的 cafe,实际上是两个 rune 的组合

}



14.range 迭代 string 得到的值


range 得到的索引是字符值(Unicode point /rune)第一个字节的位置,与其他编程语言不同,这个索引并不直接是字符在字符串中的位置。


注意一个字符可能占多个 rune,比如法文单词 café 中的 é。操作特殊字符可使用 norm 包。


for range 迭代会尝试将 string 翻译为 UTF8 文本,对任何无效的码点都直接使用 0XFFFD rune( )UNicode 替代字符来表示。


如果 string 中有任何非 UTF8 的数据,应将 string 保存为 byte slice 再进行操作。


func main() {

    data := "A\xfe\x02\xff\x04"

    for _, v := range data {

        fmt.Printf("%#x ", v)    // 0x41 0xfffd 0x2 0xfffd 0x4    // 错误

    }

    for _, v := range []byte(data) {

        fmt.Printf("%#x ", v)    // 0x41 0xfe 0x2 0xff 0x4    // 正确

    }

}



15.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 代码块末尾使用 fallthrough,强制执行下一个 case 代码块。




16. 按位取反


Go 重用 ^XOR 操作符来按位取反:


// 错误的取反操作

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

}

同时 ^ 也是按位异或(XOR)操作符。


一个操作符能重用两次,是因为一元的 NOT 操作 NOT 0x02,与二元的 XOR 操作 0x22 XOR 0xff 是一致的。


Go 也有特殊的操作符 AND NOT ,&^ 操作符,不同位才取 1。


func main() {

    var a uint8 = 0x82

    var b uint8 = 0x02

    fmt.Printf("%08b [A]\n", a)

    fmt.Printf("%08b [B]\n", b)

    fmt.Printf("%08b (NOT B)\n", ^b)

    fmt.Printf("%08b ^ %08b = %08b [B XOR 0xff]\n", b, 0xff, b^0xff)

    fmt.Printf("%08b ^ %08b = %08b [A XOR B]\n", a, b, a^b)

    fmt.Printf("%08b & %08b = %08b [A AND B]\n", a, b, a&b)

    fmt.Printf("%08b &^%08b = %08b [A 'AND NOT' B]\n", a, b, a&^b)

    fmt.Printf("%08b&(^%08b)= %08b [A AND (NOT B)]\n", a, b, a&(^b))

}

    10000010 [A]

    00000010 [B]

    11111101 (NOT B)

    00000010 ^ 11111111 = 11111101 [B XOR 0xff]

    10000010 ^ 00000010 = 10000000 [A XOR B]

    10000010 & 00000010 = 00000010 [A AND B]

    10000010 &^00000010 = 10000000 [A 'AND NOT' B]

    10000010&(^00000010)= 10000000 [A AND (NOT B)]







更多go语言的注意点,请收藏本站网址:https://www.zongscan.com/




相关文章