Go语言中新手开发人员的常见错误汇总
新手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/
相关文章