这是我参与「第三届青训营 -后端场」笔记创作活动的第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)
}