這天因為小狼和小可一起抓住了替Golang牌,導致交換身體。我知道你們在想什麼,你們是不是在想怎麼不是小狼和小櫻換,幹!就是有你們這種人
channel 通常都是可發送可接收,但如果今天我設計了一款套件,該套件會在某些情況透過 channel 發送訊息。如果今天使用者反過來透來 channel 傳送訊息,會導致套件出錯。那麼我要怎麼預防呢?
其實這個就是 time.After()
昨天的範圍有提到的函式,該函式在設定一段時間後,會透過通道傳回 Time 類型的 channel
func After(d Duration) <-chan Time
比較細心的魔法使應該有發現,回傳型態的左方多了 <-
。這個代表說該型態僅允許「接收」
package main
import "fmt"
func counter(start int, end int) <-chan int{
channel := make(chan int)
single := make(<-chan int)
single = channel
go func(){
for start <= end{
channel <- start
start = start + 1
}
close(channel)
}()
return single
}
func main(){
// counter 回傳的 channel 僅用於接收
// 若嘗試寫入將會出錯
for k := range counter(0, 10){
fmt.Println(k)
}
}
執行結果:
0
1
2
3
4
5
6
7
8
9
10
如果嘗試寫入則會在編譯時出錯:
package main
import "fmt"
func counter(start int, end int) <-chan int{
channel := make(chan int)
single := make(<-chan int)
single = channel
go func(){
for start <= end{
channel <- start
start = start + 1
}
close(channel)
}()
return single
}
func main(){
ch := counter(0, 10)
for k := range ch{
fmt.Println(k)
// 嘗試寫入回去
ch <- k
}
}
# command-line-arguments
./lesson20.go:23:12: invalid operation: ch <- k (send to receive-only type <-chan int)
只能寫和只能讀的通道其道理相同,在此就先不示範了
僅能發送
make(chan type <-)
僅能接收make(<- chan type)
在 golang 中有許多耳熟能詳的運算子,比如: +
-
*
/
%
=
然而,這些賦值運算先前只草草帶過。所以今天要講細一點
package main
import "fmt"
func main(){
var num int = 10
num += 10 // 意思同 num = num + 10
fmt.Println(num)
}
這個對初學者可能需要一些時間不習慣
num += 10 // num = num + 10
num -= 10 // num = num - 10
num *= 10 // num = num * 10
num /= 10 // num = num / 10
num %= 10 // num = num % 10
因為之前沒講所以帶範例時我都不敢用,也許之後會把這部分調到先前的範圍
package main
import "fmt"
func main(){
var num int = 10
num++ // 同 num = num + 1
fmt.Println(num)
num-- // 同 num = num - 1
fmt.Println(num)
}
執行結果:
11
10
可以很明顯的知道這裡指的 ++
就是指將 num
遞增,而 --
就是將 num
遞減。
然而,如果是學過其他語言的魔法使就會問那
++num
呢?不好意思,被廢掉了。還有那fmt.Println(num++)
呢?不好意思,也不支援,所以也沒有先加後加的問題了。
也因此遞增遞減運算符在 Golang 中變的比較沒那麼複雜
位元運算指的是針對二進位的數所設計的運算元,常見的有 &
(and), |
(or), ^
(xor), &^
(bit clear), >>
(right shift), <<
(left shift)
以下示範,用 0b 開頭來表示二進位,比如 0b10
代表 2
, 0b100
代表 4
package main
import "fmt"
func main(){
// 只有左右 bit 都為 1 ,結果才會是 1
fmt.Printf("%b\n", 0b0 & 0b0) // 0
fmt.Printf("%b\n", 0b0 & 0b1) // 0
fmt.Printf("%b\n", 0b1 & 0b0) // 0
fmt.Printf("%b\n", 0b1 & 0b1) // 1
// 只要左右有一個 bit 為 1 結果的 bit 就是 1
fmt.Printf("%b\n", 0b0 | 0b0) // 0
fmt.Printf("%b\n", 0b0 | 0b1) // 1
fmt.Printf("%b\n", 0b1 | 0b0) // 1
fmt.Printf("%b\n", 0b1 | 0b1) // 1
// 左右 bit 只能有一個 1 ,不然結果的 bit 會是 0
fmt.Printf("%b\n", 0b0 ^ 0b0) // 0
fmt.Printf("%b\n", 0b0 ^ 0b1) // 1
fmt.Printf("%b\n", 0b1 ^ 0b0) // 1
fmt.Printf("%b\n", 0b1 ^ 0b1) // 0
// 位元清除是一個比較特殊的運算
// 他會先將右式取 not 再合起來
fmt.Printf("%b\n", 0b0 &^ 0b0) // 0 & 1 -> 0
fmt.Printf("%b\n", 0b0 &^ 0b1) // 0 & 0 -> 0
fmt.Printf("%b\n", 0b1 &^ 0b0) // 1 & 1 -> 1
fmt.Printf("%b\n", 0b1 &^ 0b1) // 1 & 0 -> 0
}
這裡只拿一個位元舉例,但實際上在用時會搭配不只一個位元,初學者可以自主練習一下
package main
import "fmt"
func main(){
fmt.Printf("%08b\n", 0b00001111 & 0b00110011) // 00000011
fmt.Printf("%08b\n", 0b00001111 | 0b00110011) // 00111111
fmt.Printf("%08b\n", 0b00001111 ^ 0b00110011) // 00111100
fmt.Printf("%08b\n", 0b00001111 &^ 0b00110011) // 00001100
}
左移、右移
左移、右移這個就有趣了,在十進位中,如果我把數字左移一位,比如 15
左移一位變 150
左移兩位變 1500
,也就是每左移一位數字會變 10 倍,反之每右移一位數字會變原先的十分之一。利用這個原理套用在 2 進位上,則數字每左移 1 位就會變 2 倍,左移兩位就會變 4 倍。
package main
import "fmt"
func main(){
fmt.Printf("%04b\n", 0b0111 << 1) // 1110
fmt.Printf("%d\n", 15 << 1) // 30
fmt.Printf("%04b\n", 0b0111 >> 1) // 0011
fmt.Printf("%d\n", 15 >> 1) // 7
}
如果你以為 golang 的 const 只能當常數那你就太小看 const 了。
package main
import "fmt"
func main(){
const(
C0 = iota
C1 = iota
C2 = iota
)
fmt.Println(C0)
fmt.Println(C1)
fmt.Println(C2)
}
執行結果:
0
1
2
利用 iota
可以自 0 開始產生連續數字。但是,如果只有這樣我自己打 0, 1, 2, ... 不就好了嗎?所以你可以更簡單的寫成:
package main
import "fmt"
func main(){
const(
C0 = iota
C1
C2
)
fmt.Println(C0)
fmt.Println(C1)
fmt.Println(C2)
}
執行結果:
0
1
2
如此一來就可以很輕鬆的實現 enum
枚舉 (許多魔法都有自帶 enum
) 。但是,你以為這樣就結束了嗎?不!除了可以 1 次加 1 外,還能用來實現 flag 的功能
有時候我們在製作函式時會希望可以讓使用者條整一些參數,但如果使用 boolean 來調整其實很浪費,因為 boolean 雖然能用來表示 true 和 false 但實際上確是用了 8 個 bits 在表示,也就是 0000000
為 false
, 00000001
為 true
,其他 7 個 bit 根本沒用到。如果你想設計一個函式讓使用者可以分別調整 8 個 bit 的值,那麼你只需要花 8 個 bit 的空間,而不是 8 個 bytes (64bits)。足足省了 8 倍。
那麼要怎麼設 flag 呢?
你可以想像,比如模式 1 用 00000001,模式 2 用 00000010 如果 flag 是 00000011 就代表 模式1
, 模式2
同時啟用。
package main
import "fmt"
const(
mode1 = 0b0001
mode2 = 0b0010
mode3 = 0b0100
mode4 = 0b1000
)
func render(flag int){
if((flag & mode1) == mode1){
fmt.Println("mode 1")
}
if((flag & mode2) == mode2){
fmt.Println("mode 2")
}
if((flag & mode3) == mode3){
fmt.Println("mode 3")
}
if((flag & mode4) == mode4){
fmt.Println("mode 4")
}
}
func main(){
fmt.Println("啟用模式 1, 2")
render(mode1 | mode2)
fmt.Println("啟用模式 1, 3, 4")
render(mode1 | mode3 | mode4)
}
執行結果:
啟用模式 1, 2
mode 1
mode 2
啟用模式 1, 3, 4
mode 1
mode 3
mode 4
利用 or
可以做聯級的效果 比如 mode1 | mode2
(0001
| 0010
) 出來就變 0011
而在偵測時利用 and
可以做遮罩只針對特定的位元,比如現在 flag = abcd
而 mode1 = 0001
則 abc 會變 0 , flag & mode1
會變成 000d
只看最後一位。
每次都要刷一排 0b 很麻煩,而且其實順序根本不重要,主要是要隨機賦予只有一個 bit 是 1 的二進位數,這時就可以利用 iota 啦
iota 改寫
package main
import "fmt"
const(
mode1 = 1 << iota // 1 << 0 0b0001
mode2 = 1 << iota // 1 << 1 0b0010
mode3 = 1 << iota // 1 << 2 0b0100
mode4 = 1 << iota // 1 << 3 0b1000
)
func render(flag int){
if((flag & mode1) == mode1){
fmt.Println("mode 1")
}
if((flag & mode2) == mode2){
fmt.Println("mode 2")
}
if((flag & mode3) == mode3){
fmt.Println("mode 3")
}
if((flag & mode4) == mode4){
fmt.Println("mode 4")
}
}
func main(){
fmt.Println("啟用模式 1, 2")
render(mode1 | mode2)
fmt.Println("啟用模式 1, 3, 4")
render(mode1 | mode3 | mode4)
}
iota 簡寫
當然也可以做簡寫
const(
mode1 = 1 << iota
mode2
mode3
mode4
)
本文圖片來自: