- GOROOT: golang的安裝路徑
- GOPATH: 工作目錄
- "_": 空白標(biāo)識符
Go 不是像 C ++封拧,Java,Ruby 和 C#一樣的面向?qū)ο蟮模∣O)語言。它沒有對象和繼承的概念,也沒有很多與面向?qū)ο笙嚓P(guān)的概念,例如多態(tài)和重載止后。
Go沒有類或者說面向?qū)ο蟮母拍? 結(jié)構(gòu)體就是Go里面的類
鏡像復(fù)制
func main() {
goku := Saiyan{"Power", 9000}
Super(goku)
fmt.Println(goku.Power)
}
func Super(s Saiyan) {
s.Power += 10000
}
上面程序運(yùn)行的結(jié)果是 9000,而不是 19000,溜腐。為什么译株?因?yàn)?Super 修改了原始值 goku 的復(fù)制版本,而不是它本身
&: 取地址符
func main() {
goku := &Saiyan{"Power", 9000}
Super(goku)
fmt.Println(goku.Power)
}
func Super(s *Saiyan) {
s.Power += 10000
}
這里注意到我們?nèi)匀粋鬟f了一個 goku 的值的副本給 Super挺益,但這時 goku 的值其實(shí)是一個地址歉糜。所以這個副本值也是一個與原值相等的地址,
這就是我們間接傳值的方式望众。想象一下匪补,就像復(fù)制一個指向飯店的方向牌伞辛。你所擁有的是一個方向牌的副本,但是它仍然指向原來的飯店夯缺。
復(fù)制一個指針比復(fù)制一個復(fù)雜的結(jié)構(gòu)的消耗小多了
Go結(jié)構(gòu)體使用組合的方式, 代替面向?qū)ο笾械睦^承
指針類型和值類型(指針類型就是類似對象了, 分配在堆上的內(nèi)存)
當(dāng)不確定要用值還是指針時, 就用指針
數(shù)組
在 Go 中蚤氏,像其它大部分語言一樣,數(shù)據(jù)的長度是固定的踊兜。我們在聲明一個數(shù)組時需要指定它的長度竿滨,一旦指定了長度,那么它的長度值是不可以改變的了
var scores [10]int
scores[0] = 339
數(shù)組非常高效但是很死板
切片
在 Go 語言中捏境,我們很少直接使用數(shù)組于游。取而代之的是使用切片。切片是輕量的包含并表示數(shù)組的一部分的結(jié)構(gòu)垫言。
//和數(shù)組聲明不同的是贰剥,我們的切片沒有在方括號中定義長度
scores := []int{1,4,293,4,9}
- make方式創(chuàng)建切片
scores := make([]int, 10)- 使用 make 關(guān)鍵字代替 new, 是因?yàn)閯?chuàng)建一個切片不僅是只分配一段內(nèi)存(這個是 new 關(guān)鍵字的功能)筷频。
- 具體來講蚌成,我們必須要為一個底層數(shù)組分配一段內(nèi)存,同時也要初始化這個切片截驮。
- 上面的代碼中笑陈,我們初始化了一個長度是 10 ,容量是 10 的切片
- 長度是切片的長度葵袭,容量是底層數(shù)組的長度
- 在使用 make 創(chuàng)建切片時,我們可以分別的指定切片的長度和容量:
scores := make([]int, 0, 10)
- 上面的代碼創(chuàng)建了一個長度是 0 乖菱,容量是 10 的切片
理解切片的長度和容量之間的關(guān)系
切片就是從數(shù)組切一段下來, 切片的長度就是標(biāo)識切了多長的數(shù)組, 容量就是底層數(shù)組的長度
我們可以調(diào)整的切片大小最大范圍是多少呢坡锡?達(dá)到它的容量, 就像例子中的10
這實(shí)際上并沒有解決數(shù)組固定長度的問題。但是 append 是相當(dāng)特別的窒所。如果底層數(shù)組滿了鹉勒,它將創(chuàng)建一個更大的數(shù)組并且復(fù)制所有原切片中的值
Go 使用 2x 算法來增加數(shù)組長度(加倍容量)
四種方式創(chuàng)建切片
- names := []string{"leto", "jessica", "paul"} //字面量方式
- checks := make([]bool, 10) //知道容量
- var names []string //創(chuàng)建空切片
- scores := make([]int, 0, 20) //創(chuàng)建指定容量的切片
切片的內(nèi)存理解
其他語言的切片 ---> 一個切片實(shí)際上是復(fù)制了原始值的新數(shù)組。
scores = [1,2,3,4,5]
slice = scores[2..4]
slice[0] = 999
puts scores
//答案是 [1, 2, 3, 4, 5] 吵取。那是因?yàn)?slice 是一個新數(shù)組禽额,并且復(fù)制了原有的值。
Go ---> 切片是指向原始數(shù)組的指針
scores := []int{1,2,3,4,5}
slice := scores[2:4]
slice[0] = 999
fmt.Println(scores)
//輸出是 [1, 2, 999, 4, 5]
只有顯示的使用copy內(nèi)建函數(shù), 才會復(fù)制新數(shù)組
總結(jié)
// 數(shù)組是連續(xù)存儲的空間 切片只存放指針 沒有存放數(shù)據(jù) 所以 指針的位置
// 只收上界影響皮官,也就是數(shù)據(jù)的基址脯倒, 他的操作范圍 只能從指針開始到連續(xù)的內(nèi)存結(jié)束 如果超過容量 擴(kuò)容后
// 就產(chǎn)生了新的數(shù)組和內(nèi)存空間 切片的指針也會指向新的內(nèi)存
映射
看錯了, 以為是反射
映射就是字典, 使用 make 方法來創(chuàng)建
- 使用 len 方法類獲取映射的鍵的數(shù)量
- 使用 delete 方法來刪除一個鍵對應(yīng)的值
- 映射是動態(tài)變化的
- 通過傳遞第二個參數(shù)到 make 方法來設(shè)置一個初始大小(如果你事先知道映射會有多少鍵值,定義一個初始大小將會幫助改善性能捺氢。)
包管理
在 Go 語言中藻丢,包名遵循 Go 項(xiàng)目的目錄結(jié)構(gòu)。(意思是目錄就是包名)
- 循環(huán)導(dǎo)入問題
- 可見性, 如果類型和函數(shù)的名字是大寫就是外界可見的, 小寫的名字就是外界不可見
- go get 庫鏈接(會把下載下來的庫, 放在工作目錄GOPATH)
- 依賴管理 go get -u 包更新
go get的缺點(diǎn): 無法指定版本
接口
目前為止摄乒,我們看到的類型都是具體的類型悠反,一個具體的類型可以準(zhǔn)確的描述它所代表的值残黑。而接口類型是一種抽象的類型。它不會暴露出它所代表的對象的內(nèi)部值的結(jié)構(gòu)和這個對象支持的基礎(chǔ)操作的集合斋否;它們只會表現(xiàn)出它們自己的方法梨水。也就是說當(dāng)你有看到一個接口類型的值時,你不知道它是什么茵臭,唯一知道的就是可以通過它的方法來做什么疫诽。
type Namer interface {
Method1(param_list) return_type
Method2(param_list) return_type
...
}
- interface可以被任意的對象實(shí)現(xiàn)
- 一個對象可以實(shí)現(xiàn)任意多個interface
- 任意的類型都實(shí)現(xiàn)了空interface(我們這樣定義:interface{}),也就是包含0個method的interface
接口有助于將代碼與特定的實(shí)現(xiàn)進(jìn)行分離(按照iOS的Delegate來理解)
接口就是一種協(xié)議笼恰,和現(xiàn)實(shí)中的接口一個道理踊沸。
一提到usbc你就知道那個接口長什么樣,但是你不關(guān)心usbc芯片是什么樣的社证,怎么接線逼龟。
代碼通過接口定義來表明自己對外開放的能力,具體的實(shí)現(xiàn)則交給實(shí)現(xiàn)這個接口的類追葡。
只要實(shí)現(xiàn)了這個接口的類腺律,都一定存在對應(yīng)的方法,那么我拿到這個類的實(shí)例對象的時候宜肉,就一定可以調(diào)用這個對象所實(shí)現(xiàn)的方法匀钧。
具體的好處需要多實(shí)踐才好領(lǐng)會,
比如ios編程常見的委托谬返,java常用的控制反轉(zhuǎn)之類之斯。
如果寫過http接口,實(shí)際上也是一樣的道理遣铝。
type Logger interface {
Log(message string)
}
錯誤處理
Go 首選錯誤處理方式是返回值佑刷,而不是異常。
作為最后一點(diǎn)酿炸,Go 確實(shí)有 panic 和 recover 函數(shù)瘫絮。 panic 就像拋出異常,而 recover 就像 catch填硕,它們很少使用麦萤。
defer
盡管 Go 有一個垃圾回收器,一些資源仍然需要我們顯式地釋放他們扁眯。例如壮莹,我們需要在使用完文件之后 Close() 他們。
這種代碼總是很危險(xiǎn)恋拍。一方面來說垛孔,當(dāng)我們在寫一個函數(shù)的時候,很容易忘記關(guān)閉我們聲明了 10 行的東西施敢。
另一方面周荐,一個函數(shù)可能有多個返回點(diǎn)狭莱。Go 給出的解決方案是使用 defer 關(guān)鍵字:
無論什么情況,在函數(shù)返回之后(本例中為 main() )概作,defer 將被執(zhí)行腋妙。這使您可以在初始化的位置附近釋放資源并處理多個返回點(diǎn)。
go fmt
格式化代碼
當(dāng)你在一個項(xiàng)目內(nèi)的時候讯榕,你可以運(yùn)用格式化規(guī)則到這個項(xiàng)目及其所有子目錄:
go fmt ./...
空接口和轉(zhuǎn)換
字符串和字節(jié)數(shù)組
函數(shù)類型
函數(shù)是一種類型:
type Add func(a int, b int) int
可以作為參數(shù)傳遞
并發(fā)
Go 通常被描述為一種并發(fā)友好的語言骤素。 原因是它提供了兩種強(qiáng)大機(jī)制的簡單語法: 協(xié)程 和 通道
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("start")
go process()
time.Sleep(time.Millisecond * 10) // this is bad, don't do this!
fmt.Println("done")
}
func process() {
fmt.Println("processing")
}
- 開始一個 協(xié)程 。 我們只需使用 go 關(guān)鍵字愚屁,然后使用我們想要執(zhí)行的函數(shù)济竹。
- 協(xié)程 易于創(chuàng)建且開銷很小。最終多個 協(xié)程 將會在同一個底層的操作系統(tǒng)線程上運(yùn)行霎槐。 這通常也稱為 M:N 線程模型送浊,因?yàn)槲覀冇?M 個應(yīng)用線程( 協(xié)程 )運(yùn)行在 N 個操作系統(tǒng)線程上。結(jié)果就是丘跌,一個 協(xié)程 的開銷和系統(tǒng)線程比起來相對很低(幾 KB)袭景。在現(xiàn)代的硬件上,有可能擁有數(shù)百萬個 協(xié)程 闭树。
同步
并發(fā)編程中的問題:
- 變量會被其他線程讀寫, 如果保證讀寫安全
- 加鎖的效率問題: 需要個優(yōu)雅的鎖操作耸棒; 否則,我們最終會把多條快速通道走成單車道的报辱。
- 加鎖死鎖的問題: 當(dāng)協(xié)程 A 擁有鎖 lockA 与殃,想去訪問鎖 lockB ,同時協(xié)程 B 擁有鎖 lockB 并需要訪問鎖 lockA 碍现。
解決辦法:
- 有一個常見的鎖叫讀寫互斥鎖奈籽。它主要提供了兩種鎖功能:一個鎖定讀取和一個鎖定寫入。它的區(qū)別是允許多個同時讀取鸵赫,同時確保寫入是獨(dú)占的。在 Go 中躏升, sync.RWMutex 就是這種鎖辩棒。
- 通道 旨在讓并發(fā)編程更簡潔和不容易出錯。(加鎖容易出問題)
通道
而Go是通過一種特殊的類型膨疏,通道(channel)一睁,一個可以用于發(fā)送類型化數(shù)據(jù)的管道,由其負(fù)責(zé)協(xié)程之間的通信佃却,從而避開所有由共享內(nèi)存導(dǎo)致的陷阱者吁;這種通過通道進(jìn)行通信的方式保證了同步性。數(shù)據(jù)在通道中進(jìn)行傳遞:在任何給定時間饲帅,一個數(shù)據(jù)被設(shè)計(jì)為只有一個協(xié)程可以對其訪問复凳,所以不會發(fā)生數(shù)據(jù)競爭瘤泪。 數(shù)據(jù)的所有權(quán)(可以讀寫數(shù)據(jù)的能力)也因此被傳遞。
- 通道操作符 <-
并發(fā)編程的最大調(diào)整源于數(shù)據(jù)的共享育八。如跨多個請求共享數(shù)據(jù)对途。內(nèi)存緩存和數(shù)據(jù)庫
- 通道是協(xié)程之間用于傳遞數(shù)據(jù)的管道
換而言之,一個協(xié)程可以通過一個通道向另外一個協(xié)程傳遞數(shù)據(jù)髓棋。因此实檀,在任意時間點(diǎn),只有一個協(xié)程可以訪問數(shù)據(jù)按声。
- 一個通道膳犹,和其他任何變量一樣,都有一個類型,這個類型是在通道中傳遞的數(shù)據(jù)的類型签则。例如须床,創(chuàng)建一個通道用于傳遞一個整數(shù),我們要這樣做:
c := make(chan int)
- 通道只支持兩個操作:接收和發(fā)送怀愧。
- 接收和發(fā)送操作是阻塞的侨颈。
也就是,當(dāng)我們從一個通道接收的時候芯义, goroutine 將會直到數(shù)據(jù)可用才會繼續(xù)執(zhí)行哈垢。類似地,當(dāng)我們往通道發(fā)送數(shù)據(jù)的時候扛拨,goroutine 會等到數(shù)據(jù)接收到之后才會繼續(xù)執(zhí)行耘分。
- Go 保證了發(fā)送到通道的數(shù)據(jù)只會被一個接收器接收。(保證多線程數(shù)據(jù)安全, 讀寫的唯一)
通道阻塞
默認(rèn)情況下绑警,通信是同步且無緩沖的:在有接受者接收數(shù)據(jù)之前求泰,發(fā)送不會結(jié)束。
一個無緩沖的通道在沒有空間來保存數(shù)據(jù)的時候:必須要一個接收者準(zhǔn)備好接收通道的數(shù)據(jù)然后發(fā)送者可以直接把數(shù)據(jù)發(fā)送給接收者计盒。
所以通道的發(fā)送/接收操作在對方準(zhǔn)備好之前是阻塞的:
- 對于同一個通道渴频,發(fā)送操作(協(xié)程或者函數(shù)中的)在接收者準(zhǔn)備好之前是阻塞的:如果ch中的數(shù)據(jù)無人接收,就無法再給通道傳入其他數(shù)據(jù):新的輸入無法在通道非空的情況下傳入北启。所以發(fā)送操作會等待 ch 再次變?yōu)榭捎脿顟B(tài):就是通道值被接收時就可以再傳入變量卜朗。
- 對于同一個通道,接收操作是阻塞的(協(xié)程或函數(shù)中的)咕村,直到發(fā)送者可用:如果通道中沒有數(shù)據(jù)场钉,接收者就阻塞了。
緩沖通道
一個無緩沖通道只能包含 1 個元素懈涛,有時顯得很局限逛万。我們給通道提供了一個緩存,可以在擴(kuò)展的 make 命令中設(shè)置它的容量
buf := 100
ch1 := make(chan string, buf)
- 在緩沖滿載(緩沖被全部使用)之前批钠,給一個帶緩沖的通道發(fā)送數(shù)據(jù)是不會阻塞的宇植,而從通道讀取數(shù)據(jù)也不會阻塞得封,直到緩沖空了。
- 緩沖容量和類型無關(guān)当纱,所以可以給一些通道設(shè)置不同的容量呛每,只要他們擁有同樣的元素類型。內(nèi)置的 cap 函數(shù)可以返回緩沖區(qū)的容量坡氯。
- 如果容量大于 0晨横,通道就是異步的:因?yàn)榫彌_滿載(發(fā)送)或變空(接收)之前通信不會阻塞,元素會按照發(fā)送的順序被接收箫柳。
- 如果容量是0或者未設(shè)置手形,通道就是同步的:通信僅在收發(fā)雙方準(zhǔn)備好的情況下才可以成功。
通道注意事項(xiàng):
- channel 在 Golang 中是一等公民悯恍,它是線程安全的库糠,面對并發(fā)問題,應(yīng)首先想到 channel
- 關(guān)閉一個未初始化的 channel 會產(chǎn)生 panic
- 重復(fù)關(guān)閉同一個 channel 會產(chǎn)生 panic
- 向一個已關(guān)閉的 channel 發(fā)送消息會產(chǎn)生 panic
- 從已關(guān)閉的 channel 讀取消息不會產(chǎn)生 panic涮毫,且能讀出 channel 中還未被讀取的消息瞬欧,若消息均已被讀取,則會讀取到該類型的零值罢防。
- 從已關(guān)閉的 channel 讀取消息永遠(yuǎn)不會阻塞艘虎,并且會返回一個為 false 的值,用以判斷該 channel 是否已關(guān)閉(x,ok := <- ch)
- 關(guān)閉 channel 會產(chǎn)生一個廣播機(jī)制咒吐,所有向 channel 讀取消息的 goroutine 都會收到消息
Select
select 是 Go 中的一個控制結(jié)構(gòu)野建。select 語句類似于 switch 語句,但是select會隨機(jī)執(zhí)行一個可運(yùn)行的case恬叹。如果沒有case可運(yùn)行候生,它將阻塞,直到有case可運(yùn)行绽昼。
Select語句的作用(注意:它僅能用于channel的相關(guān)操作,select 里的 case 表達(dá)式要求是對信道的操作)
當(dāng)我們需要同時從多個通道接收數(shù)據(jù)時唯鸭,如果通道里沒有數(shù)據(jù)將會發(fā)生阻塞。為了應(yīng)對這種場景硅确,Go內(nèi)置了select關(guān)鍵字肿孵,可以同時響應(yīng)多個通道的操作。
package main
import (
"fmt"
)
func main() {
c1 := make(chan string, 1)
c2 := make(chan string, 1)
c2 <- "hello"
select {
case msg1 := <-c1:
fmt.Println("c1 received: ", msg1)
case msg2 := <-c2:
fmt.Println("c2 received: ", msg2)
default:
fmt.Println("No data received.")
}
}
//c2 received: hello
select語句的處理邏輯:case隨機(jī)選擇處理列出的多個通信情況中的一個
- 如果都阻塞了疏魏,會等待直到其中一個可以處理
- 如果多個可以處理,隨機(jī)選擇一個
- 為了避免死鎖晤愧,應(yīng)該編寫default分支或手動實(shí)現(xiàn)超時機(jī)制大莫,否則select 整體就會一直阻塞
- select 的 case 是隨機(jī)的,而 switch 里的 case 是順序執(zhí)行官份;
- select 里沒有類似 switch 里的 fallthrough 的用法只厘;
- select 里的 case 表達(dá)式要求是對信道的操作烙丛,不能像 switch 一樣接函數(shù)或其他表達(dá)式;