初学者常犯的错误 #
索引运算符和字符串 #
字符串上的 index 方法 (运算符) 返回一个字节值,而不是一个字符类型(就像在其他语言中一样)。
package main
import "fmt"
func main() {
x := "text"
fmt.Println(x[0]) //print 116
fmt.Printf("%T",x[0]) //prints uint8
}
如果需要访问特定字符串 “characters”(unicode 代码点 / 运行符),请使用 for range
语句。官方的 “unicode/utf8” 包和基础的 utf8string 包 (golang.org/x/exp/utf8string) 也很有用。utf8string 包有一个方便的 At()
方法,将字符串转换为切片也是一种选择。
使用 「for range」子句遍历 Map #
- level:初学者
如果你希望 Map 每项数据按照顺序排列 (例如,按键值顺序),这是不可能的,每次 Map 迭代会输出不一样的结果。GO 运行时可能会随机分配迭代顺序,因此你可能会得到几次相同的 Map 迭代结果也不用惊讶。
package main
import "fmt"
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 ( play.golang.org/) 运行这段代码,将始终得到相同的迭代结果,因为除非进行更改代码,否则它不会重新编译代码。
增量和减量 #
- 级别:初学者
许多语言都有递增和递减运算符。与其他语言不同,Go 不支持操作的前缀版本。你也不能在表达式中使用这两个运算符。
失败:
package main
import "fmt"
func main(){
data := []int{1,2,3}
i := 0
++i //错误
fmt.Println(data [i++])//错误
}
编译错误:
/tmp/sandbox101231828/main.go:8:语法错误:意外的 ++ /tmp/sandbox101231828/main.go:9:语法错误:意外的 ++,期望:
作品:
package main
import "fmt"
func main(){
data := []int{1,2,3}
i := 0
i++
fmt.Println(data[i])
}
按位 NOT 运算符 #
- 级别:初学者
许多语言都使用〜
作为一元 NOT 运算符 (也称为按位补码),但是 Go 为此重用了 XOR 运算符 (^
)。
失败:
package main
import "fmt"
func main(){
fmt.Println(〜2)//错误
}
编译错误:
/tmp/sandbox965529189/main.go:6:按位补码运算符是 ^
作品:
package main
import "fmt"
func main(){
var d uint8 = 2
fmt.Printf(“%08b \ n”,^ d)
}
Go 仍然使用 ^
作为 XOR 运算符,这可能会使某些人感到困惑。
如果你愿意,你可以用二进制的 XOR 操作 (例如,' NOT 0x02 ‘) 来表示一个单目的 NOT 操作 (例如,’ 0x02 XOR 0xff ‘)。这可以解释为什么 ^
被重用于表示一元 NOT 操作。
Go 还具有一个特殊的 ‘AND NOT’ 按位运算符 (&^
),这增加了 NOT 运算符的困惑。看起来像一个特性 / 黑客,不需要括号就可以支持 A AND (NOT B)
。
package main
import "fmt"
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))
}
运算符优先级差异 #
- 级别:初学者
除了「位清除」运算符 (&^
) 之外,Go 还有许多其他语言共享的一组标准运算符。但是,运算符优先级并不总是相同。
package main
import "fmt"
func main() {
fmt.Printf("0x2 & 0x2 + 0x4 -> %#x\n",0x2 & 0x2 + 0x4)
//prints: 0x2 & 0x2 + 0x4 -> 0x6
//Go: (0x2 & 0x2) + 0x4
//C++: 0x2 & (0x2 + 0x4) -> 0x2
fmt.Printf("0x2 + 0x2 << 0x1 -> %#x\n",0x2 + 0x2 << 0x1)
//prints: 0x2 + 0x2 << 0x1 -> 0x6
//Go: 0x2 + (0x2 << 0x1)
//C++: (0x2 + 0x2) << 0x1 -> 0x8
fmt.Printf("0xf | 0x2 ^ 0x2 -> %#x\n",0xf | 0x2 ^ 0x2)
//prints: 0xf | 0x2 ^ 0x2 -> 0xd
//Go: (0xf | 0x2) ^ 0x2
//C++: 0xf | (0x2 ^ 0x2) -> 0xf
}
应用退出与活动的 Goroutines #
- 级别:初学者
应用程序不会等待你的所有 goroutine 完成。对于一般的初学者来说,这是一个常见的错误。每个人都从某个地方开始,所以在犯菜鸟错误时不要觉得丢脸
package main
import (
"fmt"
"time"
)
func main() {
workerCount := 2
for i := 0; i < workerCount; i++ {
go doit(i)
}
time.Sleep(1 * time.Second)
fmt.Println("all done!")
}
func doit(workerId int) {
fmt.Printf("[%v] is running\n",workerId)
time.Sleep(3 * time.Second)
fmt.Printf("[%v] is done\n",workerId)
}
你会看到的:
[0] 正在运行
[1] 正在运行
全部完成!
最常见的解决方案之一是使用 “WaitGroup” 变量。它将允许主 goroutine 等待直到所有工作程序 goroutine 完成。如果你的应用程序具有长时间运行的消息处理循环,则你还需要一种方法向那些 goroutine 发出退出信号的信号。你可以向每个工作人员发送 “杀死” 消息。另一个选择是关闭所有工作人员正在接收的渠道。这是一次发出所有 goroutine 信号的简单方法。
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
done := make(chan struct{})
workerCount := 2
for i := 0; i < workerCount; i++ {
wg.Add(1)
go doit(i,done,wg)
}
close(done)
wg.Wait()
fmt.Println("all done!")
}
func doit(workerId int,done <-chan struct{},wg sync.WaitGroup) {
fmt.Printf("[%v] is running\n",workerId)
defer wg.Done()
<- done
fmt.Printf("[%v] is done\n",workerId)
}
如果你运行此应用,将会看到:
[0] is running
[0] is done
[1] is running
[1] is done
看起来 worker 在主 goroutine 退出之前已经完成。这太棒了!但是 1,你还会看到这样的情况:
fatal error: all goroutines are asleep - deadlock!
这不太好 发生了什么?为什么会出现死锁?当 worker 离开时,它们执行了 wg.Done()
。应用程序应该是可以工作的。
发生死锁是因为每个 Worker 都会获得原始「WaitGroup」变量的副本。当工人执行 wg.Done()
时,它不会影响主 goroutine 中 的「WaitGroup」变量。
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
done := make(chan struct{})
wq := make(chan interface{})
workerCount := 2
for i := 0; i < workerCount; i++ {
wg.Add(1)
go doit(i,wq,done,&wg)
}
for i := 0; i < workerCount; i++ {
wq <- i
}
close(done)
wg.Wait()
fmt.Println("all done!")
}
func doit(workerId int, wq <-chan interface{},done <-chan struct{},wg *sync.WaitGroup) {
fmt.Printf("[%v] is running\n",workerId)
defer wg.Done()
for {
select {
case m := <- wq:
fmt.Printf("[%v] m => %v\n",workerId,m)
case <- done:
fmt.Printf("[%v] is done\n",workerId)
return
}
}
}
现在它可以按预期工作了
“nil” 使用 “nil” 通道 #
Send and receive operations on a nil
channel block forver. It’s a well documented behavior, but it can be a surprise for new Go developers.
package main
import (
"fmt"
"time"
)
func main() {
var ch chan int
for i := 0; i < 3; i++ {
go func(idx int) {
ch <- (idx + 1) * 2
}(i)
}
//get first result
fmt.Println("result:",<-ch)
//do other work
time.Sleep(2 * time.Second)
}
如果运行你的代码,你会看到这样的报错:fatal error: all goroutines are asleep - deadlock!
出现这样的错误是因为你在 select
语句中 case
块中动态启用和禁用了管道。
package main
import "fmt"
import "time"
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:
out = nil
in = inch
case val = <- in:
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)
}
方法中的接受者不能修改原始值 #
- 级别:初学者
方法接收者就像常规函数参数一样。如果声明为值,那么你的函数 / 方法将获得接收器参数的副本。这意味着对接收者进行更改不会影响原始值,除非你的接收者是映射或切片变量,并且你要更新集合中的项,或者你要在接收者中更新的字段是指针。
package main
import "fmt"
type data struct {
num int
key *string
items map[string]bool
}
func (this *data) pmethod() {
this.num = 7
}
func (this data) vmethod() {
this.num = 8
*this.key = "v.key"
this.items["vmethod"] = true
}
func main() {
key := "key.1"
d := data{1,&key,make(map[string]bool)}
fmt.Printf("num=%v key=%v items=%v\n",d.num,*d.key,d.items)
//prints num=1 key=key.1 items=map[]
d.pmethod()
fmt.Printf("num=%v key=%v items=%v\n",d.num,*d.key,d.items)
//prints num=7 key=key.1 items=map[]
d.vmethod()
fmt.Printf("num=%v key=%v items=%v\n",d.num,*d.key,d.items)
//prints num=7 key=v.key items=map[vmethod:true]
}