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
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/38588.html
標籤:Go
上一篇:Golang常見型別轉換
