golang 自學系列(三)—— if,for,channel 一般情況下,if 語句跟大多數語言的 if 判斷語句一樣,根據一個 boolean 表達式結果來執行兩個分支邏輯。 但凡總是有例外,go 語言還有這種寫法: 寫法 1 的意思是在判斷邏輯前,可以加一個表達式,比如獲取 ID 賦值給 i, ...
golang 自學系列(三)—— if,for,channel
一般情況下,if 語句跟大多數語言的 if 判斷語句一樣,根據一個 boolean 表達式結果來執行兩個分支邏輯。
但凡總是有例外,go 語言還有這種寫法:
// 寫法1
if i:= getID(); i < currentID {
execute some logic
} else {
execute some other logic
}
// 寫法2
var obj = map[string]interface{}
if val,ok := obj["id"]; ok {
execute some logic
} else {
execute some other logic
}
寫法 1 的意思是在判斷邏輯前,可以加一個表達式,比如獲取 ID 賦值給 i,然後參與後續的判斷是否小於當前 ID。
寫法 2 的意思同樣是在判斷邏輯前,可以加一個表達式,獲取對象 ID(obj["id"])給 val,但是與 1 不同的是,這裡 val、ok 的值是有直接關聯的。val 值取得成功與否,就是 ok 的結果值。
即 ok 一定是 boolean 類型值,表示 val = obj["id"] 是否賦值成功。我認為這種特性很好,完全不用取得值是否不存在,會報錯等。
for 語句
for 語句一般表示為重覆執行塊。由三個部分組成,一個是單條件控制的迭代,一個是 “for” 語句,最後一個是 “range” 語句。
ForStmt = "for" [ Condition | ForClause | RangeClause ] Block .
Condition = Expression .
for 查了資料才發現用法特別多
一. 使用單條件的 for 語句
for a < b {
a *= 2
}
這個是最簡單的,意思就是只要你的條件計算得出來的是 true 就會重覆執行代碼段。就如上面所示,只要 a < b 就會一直會執行 a *= 2。相當於 while 死迴圈。
二. 使用 for 從句
// 句法格式
ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] .
InitStmt = SimpleStmt .
PostStmt = SimpleStmt .
for i := 0; i < 10; i++ {
f(i)
}
使用 for 從句的 for 語句也是通過條件控制的,但是會額外指定一個 init 以及 post 語句,就好比分配一個數,這個數會自增或遞減。滿足條件判斷,就會在重覆運行執行體。
只要初始化語句變數不為空,就會在第一次迭代運行之前計算。有幾點要註意:
for 從句中的任何元素都可以為空,除非它之後一個條件,否則這種情況下分號是不能丟的。如果條件是預設的,它就等價於這個條件是 true。例如
for condtion { exec() } 等同於 for ; condition ; { exec() }
for { exec() } 等同於 for true ; { exec() }
三. 使用 range 從句
使用了 range 從句的 for 語句代表從執行的這些對象,這些對象會是數組、分片、字元串或映射以及通道(channel)上接收的值。如果迭代的條目存在,就把它賦值給迭代變數。
RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .
這個表達式 “range” 的後邊的表達式被稱為 range 表達式,它可能是數組、數組指針、分片、字元串、映射(map)或者是通道接收操作(channel permitting receive operations.)。就像賦值一樣,如果左邊有操作數,那麼則必須是可定址的或是映射索引表達式。
如果範圍表達式是一個通道(channel),那麼最多只有一個迭代變數,否則最多有兩個變數。如果最後一個迭代變數是空標識符,那麼就相當於沒有這個標識符的 range 表達式。
range 表達式 x 要在迴圈體開始之前計算一次,有一個例外:如果存在最多一個迭代變數以及 len(x) 是常熟,那麼 range 表達式就不會計算。下麵是官網給出的例子
var testdata *struct {
a *[7]int
}
for i, _ := range testdata.a {
// testdata.a is never evaluated; len(testdata.a) is constant
// i ranges from 0 to 6
f(i)
}
var a [10]string
for i, s := range a {
// type of i is int
// type of s is string
// s == a[i]
g(i, s)
}
var key string
var val interface{} // element type of m is assignable to val
m := map[string]int{"mon":0, "tue":1, "wed":2, "thu":3, "fri":4, "sat":5, "sun":6}
for key, val = range m {
h(key, val)
}
// key == last map key encountered in iteration
// val == map[key]
var ch chan Work = producer()
for w := range ch {
doWork(w)
}
// empty a channel
for range ch {}
Channel 類型
chan
關鍵字:起初這個官網沒有查到具體對 chan 的解釋,就只知道是一個關鍵字,後來經過一番資料查詢,發現這個是用來方便創建 channel
類型的快捷方式。其預設值是 nil。
既然知道 chan 是用來創建 channel
的,那麼我們就來看 channel
類型的定義:
A channel provides a mechanism for concurrently executing functions to communicate by sending and receiving values of a specified element type. The value of an uninitialized channel is nil.
就是說 channel 類型為併發運行函數提供一個機制,通過發送和接收指定元素類型的值通信。未分配的 channel 的值是 nil。
在來瞭解下 chan
的操作符 <-,它表示指定 channel 方向、發送或接收。如果沒有給定方向,就是雙向。channel 被限制只能通過賦值或顯示轉換來發送或接收。
chan T // 可以用來發送和接收 T 類型的值
chan <- float64 // 只能被用於發送 64 位浮點數
<- chan int // 只能用於接收 int 數
官網還描述下麵這種用法,說實話我沒怎麼看懂
chan<- chan int // same as chan<- (chan int)
chan<- <-chan int // same as chan<- (<-chan int)
<-chan <-chan int // same as <-chan (<-chan int)
chan (<-chan int)
go 語言提供內置函數 make
來構建新的 channel 值,該函數傳遞 channel 類型參數和一個可選的容量(capacity)參數值
make(chan int, 100)
其中的 100 是容量值(capacity),這個容量值是指 channel 內的緩衝大小。如果值為 0 說明沒有緩衝,這種情況下只有當接受者和發送者準備好才能成功通訊。否則,只要這個緩衝塊沒滿(推送)或不為空(接收),那已經緩衝的 channel 就能成功通信。
channel 能通過調用方法 close
關閉。多值賦值通過接收操作符的形式報告這個接收的值是否在 channel 在關閉之前。
單個 channel 能用在發送語句、接收操作符以及調用內置 cap
函數和 len
函數,也能在任意數量的 goroutline 使用,而不需要同步。通道是一個先進先出(FIFO)的隊列。
上面提到了兩點,發送語句和接收操作符
send 語句
簡單來講就是發送一個值給 channel。
ch <- 3
意思是發送一個值 3 給 channel 變數 ch。如果 channel 關閉了,會報 run-time panic 錯誤。
接收操作符
對於 channel 類型的操作數 ch,接收操作符的值 <- ch 意思是從 channel 類型值 ch 接收的值。<- 右邊是 channel 類型元素。表達式塊只有在值可用才不會阻塞。所以空 channel 無法接受值,因為永遠阻塞。
現在來看一下 chan 的一些例子
var c chan int // nil
c = make(chan int) // 初始化
fmt.Printf("c 的類型是%T \n", cc) // chan int
fmt.Printf("c 的值是%v \n", cc) // 0xc0000820c0
這能看出 chan int 的值是一個地址,像是指針一樣。不過目前我還是不知道這個具體的用法是什麼,用在什麼地方?
而當我嘗試讀取值的時候,卻發現好像一直在阻塞:
c <- 3
fmt.Printf("c 的值是%v \n", c)
<-c
fmt.Printf("c 的值是%v \n", c)
我想的是 chan 在發送時,沒有接收前是堵塞的,所以一直沒有執行下麵的輸出。所以我又在後面加了 <- c
讓 c 接收。結果發現還是不執行上面的輸出。
後來又查了相關的資料,得知 channel 類型很像是一個通道,消費者-生產者之間的關係。
於是我又寫了下麵代碼
cc := make(chan int)
defer close(cc)
cc <- 3 + 4
i := <-cc
fmt.Println(i)
斷電調試發現(如何在 vscode 斷電調試我稍微會另起文章說)程式運行到 cc <- 3 + 4 就不往下執行,進程也沒結束,說明是阻塞的。從之前的概念上將,cc 初始化出來的類型是沒有設置初始容量,即沒有緩存,難道在不能發送數據了?為了驗證想法,我在初始化 channel 的時候家了初始緩存 buffer:cc := make(chan int, 100)
;能正常輸出結果值。但是概念上並沒有說沒有緩衝區的就不能正常發送數據啊。
在描述 send 語句的時候說過,在接收器準備好了,發送器才會被處理。按照這個結果來看,接收器是沒有準備好的。那要怎麼才能使接收器準備好呢?
我又查了資料,發現基本上都是這麼種寫法,以上面的代碼為前提
cc := make(chan int)
defer close(cc)
go func() {
cc <- 3 + 4
}()
i := <-cc
fmt.Println(i)
這樣就是正常的。難道要把發送器以一種函數調用的形式存在?
我又嘗試把立即執行函數改為普通函數
cc := make(chan int)
defer close(cc)
fchan(cc)
i := <-cc
fmt.Println(i)
結果發現還是不行,這個問題先放一邊吧。等以後有時間在仔細學習一下。
// TODO Range 也是可以處理 chan
初始化數組
[]type{}
當我看到這個語句的時候,我內心是奔潰的,因為突然看到這種寫法的我不知道這屬於哪個特征,都不好查關鍵字資料。剛開始我以為是 type 類型的數組,然後後面再接一個空對象 {}
。但是翻遍了特性,都沒看到這種概念。後來直接寫了一個例子,查看具體的輸出
var sa = []string{}
fmt.Printf("sa 的值是%v \n", sa) //sa 的值是[]
發現它就是一個數組,並沒有什麼對象。後來我嘗試去掉後面 {}
發現根本通不過編譯,這才知道,這就是初始化一個 string 數組。
本文同步至:https://github.com/MarsonShine/GolangStudy/blob/master/src/QPaint/doc/golang-study-3.md