build-web-application-with-golang
大神astaxie制作的學(xué)習(xí)資料build-web-application-with-golang
https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/preface.md

1.golang? 關(guān)鍵字總共也就二十五個? 你將發(fā)現(xiàn),Go的世界是那么地簡潔,設(shè)計是如此地美妙,編寫Go將會是一件愉快的事情。等回過頭來,你就會發(fā)現(xiàn)這二十五個關(guān)鍵字是多么地親切。

var和const參考2.2Go語言基礎(chǔ)里面的變量和常量申明
package和import已經(jīng)有過短暫的接觸
func 用于定義函數(shù)和方法
return 用于從函數(shù)返回
defer 用于類似析構(gòu)函數(shù)
go 用于并發(fā)
select 用于選擇不同類型的通訊
interface 用于定義接口,參考2.6小節(jié)
struct 用于定義抽象數(shù)據(jù)類型,參考2.5小節(jié)
break、case、continue、for、fallthrough、else、if、switch、goto、default這些參考2.3流程介紹里面
chan用于channel通訊
type用于聲明自定義類型
map用于聲明map類型數(shù)據(jù)
range用于讀取slice、map、channel數(shù)據(jù)
close,make,new? ?Panic ,Recover
package?(在我們的例子中是package main)這一行告訴我們當(dāng)前文件屬于哪個包,而包名main則告訴我們它是一個可獨立運行的包,它在編譯后會產(chǎn)生可執(zhí)行文件。除了main包之外,其它的包最后都會生成*.a文件(也就是包文件)并放置在$GOPATH/pkg/$GOOS_$GOARCH中(以Mac為例就是$GOPATH/pkg/darwin_amd64)。
func定義了一個main函數(shù),函數(shù)體被放在{}(大括號)中,就像我們平時寫C、C++或Java時一樣。
Go使用package(和Python的模塊類似)來組織代碼。main.main()函數(shù)(這個函數(shù)位于主包)是每一個獨立的可運行程序的入口點。Go使用UTF-8字符串和標(biāo)識符(因為UTF-8的發(fā)明者也就是Go的發(fā)明者之一),所以它天生支持多語言。
var關(guān)鍵字是Go最基本的定義變量方式,與C語言不同的是Go把變量類型放在變量名后面:定義多個變量,定義變量并初始化值,同時初始化多個變量
const定義常量,可定義為數(shù)值、布爾值或字符串等類型。Go 常量和一般程序語言不同的是,可以指定相當(dāng)多的小數(shù)位數(shù)(例如200位), 若指定給float32自動縮短為32bit,指定給float64自動縮短為64bit
內(nèi)置基礎(chǔ)類型Boolean,?數(shù)值類型,?字符串,?錯誤類型error類型Go的package里面還專門有一個包errors來處理錯誤:err:=errors.New("emit macho dwarf: elf header corrupted")iferr !=nil{fmt.Print(err)}? ?
有些技巧:分組聲明,iota枚舉,大寫公有,小寫私有
array就是數(shù)組,它的定義方式如下:var arr[n] type? ?[n]type中,n表示數(shù)組的長度,type表示存儲元素的類型。
slice??“動態(tài)數(shù)組”?并不是真正意義上的動態(tài)數(shù)組,而是一個引用類型。slice總是指向一個底層array,slice的聲明也可以像array一樣,只是不需要長度。?var fslice []int? ?;ar[:n]等價于ar[0:n],ar[n:]等價于ar[n:len(ar)],ar[:]等價于ar[0:len(ar)] ;slice是引用類型,所以當(dāng)引用改變其中元素的值時,其它的所有引用都會改變該值. 幾個內(nèi)置函數(shù):len 獲取slice的長度;cap 獲取slice的最大容量; append 向slice里面追加一個或者多個元素,然后返回一個和slice一樣類型的slice;copy 函數(shù)copy從源slice的src中復(fù)制元素到目標(biāo)dst,并且返回復(fù)制的元素的個數(shù)
map也就是Python中字典的概念,它的格式為map[keyType]valueType
//聲明一個key是字符串,值為int的字典,這種方式的聲明需要在使用之前使用make初始化
varnumbersmap[string]int
幾個點:
map是無序的,?
map的長度是不固定的,也就是和slice一樣,也是一種引用類型,len函;
它不是thread-safe,在多個go-routine存取時,必須使用mutex lock機制;通過delete刪除map的元素:
make用于內(nèi)建類型(map、slice?和channel)的內(nèi)存分配。make只能創(chuàng)建slice、map和channel,并且返回一個有初始值(非零)的T類型,而不是*T。
(關(guān)于“零值”,所指并非是空值,而是一種“變量未填充前”的默認(rèn)值,通常為0。 此處羅列 部分類型 的 “零值”)
int0
int80
int320
int640
uint0x0
rune0//rune的實際類型是 int32
byte0x0//byte的實際類型是 uint8
float320//長度為 4 byte
float640//長度為 8 byte
boolfalse
string""
new用于各種類型的內(nèi)存分配。new(T)分配了零值填充的T類型的內(nèi)存空間,并且返回其地址,即一個*T類型的值。用Go的術(shù)語說,它返回了一個指針,指向新分配的類型T的零值。有一點非常重要:new返回指針。
if也許是各種編程語言中最常見的了,它的語法概括起來就是:如果滿足條件就做某事,否則做另一件事。
Go里面if條件判斷語句中不需要括號?ifx >10{
if還有一個強大的地方就是條件判斷語句里面允許聲明一個變量,這個變量的作用域只能在該條件邏輯塊內(nèi),其他地方就不起作用了ifx:=computedValue(); x >10{
;多個條件?}elseifinteger <3{
goto? ??跳轉(zhuǎn)到當(dāng)前函數(shù)內(nèi)定義的標(biāo)簽
goto跳轉(zhuǎn)到必須在當(dāng)前函數(shù)內(nèi)定義的標(biāo)簽。例如假設(shè)這樣一個循環(huán):??標(biāo)簽名是大小寫敏感的。
funcmyFunc() {i:=0Here://這行的第一個詞,以冒號結(jié)束作為標(biāo)簽println(i)i++gotoHere//跳轉(zhuǎn)到Here去}
for強大的一個控制邏輯,它既可以用來循環(huán)讀取數(shù)據(jù),又可以當(dāng)作while(golang 沒有while)來控制邏輯,還能迭代操作forexpression1; expression2; expression3 {
expression1、expression2和expression3都是表達式,其中expression1和expression3是變量聲明或者函數(shù)調(diào)用返回值之類的,expression2是用來條件判斷,expression1在循環(huán)開始之前調(diào)用,expression3在每輪循環(huán)結(jié)束之時調(diào)用。
break操作是跳出當(dāng)前循環(huán),當(dāng)嵌套過深的時候,break可以配合標(biāo)簽使用,即跳轉(zhuǎn)至標(biāo)簽所指定的位置
break和continue還可以跟著標(biāo)號,用來跳到多重循環(huán)中的外層循環(huán)
need-to-insert-img
range ? ??配合for 讀取slice,map 和channel
range?for配合range可以用于讀取slice和map的數(shù)據(jù):
fork,v:=rangemap{fmt.Println("map's key:",k)fmt.Println("map's val:",v)}//通過range,像操作slice或者map一樣操作緩存類型的channel,請看下面的例子 for i := range c能夠不斷的讀取channel里面的數(shù)據(jù),直到該channel被顯式的關(guān)閉。c:=make(chanint,10)gofibonacci(cap(c),c)fori:=rangec{fmt.Println(i)? }
?Go 支持 “多值返回”, 而對于“聲明而未被調(diào)用”的變量, 編譯器會報錯, 在這種情況下, 可以使用_來丟棄不需要的返回值 例如??for_,v:=rangemap{
switch? 處理多個條件的邏輯處理, case 匹配項,?default沒有匹配的默認(rèn)條件
switchsExpr{caseexpr1:someinstructionscaseexpr2:someotherinstructionscaseexpr3:someotherinstructionsdefault:othercode}
sExpr和expr1、expr2、expr3的類型必須一致。Go的switch非常靈活,表達式不必是常量或整數(shù),執(zhí)行的過程從上至下,直到找到匹配項;而如果switch沒有表達式,它會匹配true。
case2,3,4:
我們把很多值聚合在了一個case里面,同時,Go里面switch默認(rèn)相當(dāng)于每個case最后帶有break,匹配成功后不會自動向下執(zhí)行其他case,而是跳出整個switch, 但是可以使用
fallthrough強制執(zhí)行后面的case代碼。
fallthrough? ??強制執(zhí)行后面的case代碼。
func來聲明函數(shù)
funcfuncName(input1type1,input2type2) (output1type1,output2type2) {//這里是處理邏輯代碼//返回多個值returnvalue1,value2}
關(guān)鍵字func用來聲明一個函數(shù)funcName
函數(shù)可以有一個或者多個參數(shù),每個參數(shù)后面帶有類型,通過,分隔
函數(shù)可以返回多個值
上面返回值聲明了兩個變量output1和output2,如果你不想聲明也可以,直接就兩個類型
如果只有一個返回值且不聲明返回值變量,那么你可以省略 包括返回值 的括號
如果沒有返回值,那么就直接省略最后的返回信息
如果有返回值, 那么必須在函數(shù)的外層添加return語句
幾個特性?
多個返回值
funcSumAndProduct(A,Bint) (int,int) {
returnA+B, A*B
}
變參
funcmyfunc(arg...int) {}? //for_,n:=rangearg {
傳值與傳指針
funcadd1(aint)int{//返回一個新值//簡單的一個函數(shù),實現(xiàn)了參數(shù)+1的操作funcadd1(a*int)int{// 請注意,*a=*a+1// 修改了a的值return*a// 返回新值}x1:=add1(&x)// 調(diào)用 add1(&x) 傳x的地址
變量在內(nèi)存中是存放于一定地址上的,修改變量實際是修改變量地址處的內(nèi)存。只有add1函數(shù)知道x變量所在的地址,才能修改x變量的值。所以我們需要將x所在地址&x傳入函數(shù),并將函數(shù)的參數(shù)的類型由int改為*int,即改為指針類型,才能在函數(shù)中修改x變量的值。此時參數(shù)仍然是按copy傳遞的,只是copy的是一個指針。
底傳指針有什么好處:
傳指針使得多個函數(shù)能操作同一個對象。
傳指針比較輕量級 (8bytes),只是傳內(nèi)存地址,我們可以用指針傳遞體積大的結(jié)構(gòu)體
Go語言中channel,slice,map這三種類型的實現(xiàn)機制類似指針,所以可以直接傳遞,而不用取地址后傳遞指針
Go中函數(shù)也是一種變量,我們可以通過type來定義它,
type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...])
defer?延遲(defer)語句,你可以在函數(shù)中添加多個defer語句。當(dāng)函數(shù)執(zhí)行到最后時,這些defer語句會按照逆序執(zhí)行,最后該函數(shù)返回。defer后指定的函數(shù)會在函數(shù)退出前調(diào)用。
Go沒有像Java那樣的異常機制,它不能拋出異常,而是使用了panic和recover機制。你的代碼中應(yīng)當(dāng)沒有,或者很少有panic的東西。這是個強大的工具,請明智地使用它
Panic?是一個內(nèi)建函數(shù),可以中斷原有的控制流程,進入一個panic狀態(tài)中。當(dāng)函數(shù)F調(diào)用panic,函數(shù)F的執(zhí)行被中斷,但是F中的延遲函數(shù)會正常執(zhí)行,然后F返回到調(diào)用它的地方。在調(diào)用的地方,F(xiàn)的行為就像調(diào)用了panic。這一過程繼續(xù)向上,直到發(fā)生panic的goroutine中所有調(diào)用的函數(shù)返回,此時程序退出。panic可以直接調(diào)用panic產(chǎn)生。也可以由運行時錯誤產(chǎn)生,例如訪問越界的數(shù)組。
Recover? ??是一個內(nèi)建的函數(shù),可以讓進入panic狀態(tài)的goroutine恢復(fù)過來。recover僅在延遲函數(shù)中有效。在正常的執(zhí)行過程中,調(diào)用recover會返回nil,并且沒有其它任何效果。如果當(dāng)前的goroutine陷入panic狀態(tài),調(diào)用recover可以捕獲到panic的輸入值,并且恢復(fù)正常的執(zhí)行。
///這個函數(shù)演示了如何在過程中使用panicvaruser=os.Getenv("USER")funcinit() {ifuser==""{panic("no value for $USER")? }}//下面這個函數(shù)檢查作為其參數(shù)的函數(shù)在執(zhí)行時是否會產(chǎn)生panic:functhrowsPanic(ffunc()) (bbool) {deferfunc() {ifx:=recover();x!=nil{b=true}? }()f()//執(zhí)行函數(shù)f,如果f中出現(xiàn)了panic,那么就可以恢復(fù)回來return}
Go里面有兩個保留的函數(shù):init函數(shù)(能夠應(yīng)用于所有的package)和main函數(shù)(只能應(yīng)用于package main)。每個package中的init函數(shù)都是可選的,但package main就必須包含一個main函數(shù)。
將其它包導(dǎo)入進來,然后再對這些包中的包級常量和變量進行初始化,接著執(zhí)行init函數(shù)(如果有的話),依次類推。等所有被導(dǎo)入的包都加載完畢了,就會開始對main包中的包級常量和變量進行初始化,然后執(zhí)行main包中的init函數(shù)(如果存在的話),最后執(zhí)行main函數(shù)。

import? ??import這個命令用來導(dǎo)入包文件
相對路徑
import “./model” //當(dāng)前文件同一目錄的model目錄,但是不建議這種方式來import
絕對路徑
import “shorturl/model” //加載gopath/src/shorturl/model模塊
一些特殊的import
點操作點操作的含義就是這個包導(dǎo)入之后在你調(diào)用這個包的函數(shù)時,你可以省略前綴的包名,也就是前面你調(diào)用的fmt.Println("hello world")可以省略的寫成Println("hello world")
別名操作?別名操作顧名思義我們可以把包命名成另一個我們用起來容易記憶的名字.?別名操作的話調(diào)用包函數(shù)時前綴變成了我們的前綴
_操作??_操作其實是引入該包,而不直接使用包里面的函數(shù),而是調(diào)用了該包里面的init函數(shù)。
import("database/sql"_"github.com/ziutek/mymysql/godrv")
struct?聲明新的類型,作為其它類型的屬性或字段的容器我們可以創(chuàng)建一個自定義類型person代表一個人的實體。這個實體擁有屬性:姓名和年齡。這樣的類型我們稱之struct。如下代碼所示:
typepersonstruct{namestringageint}varPperson// P現(xiàn)在就是person類型的變量了P.name="Astaxie"http:// 賦值"Astaxie"給P的name屬性.///匿名字段? 最外層的優(yōu)先訪問typeStudentstruct{Human// 匿名字段,那么默認(rèn)Student就包含了Human的所有字段specialitystring}mark:=Student{Human{"Mark",25,120},"Computer Science"}mark.age=46// 返回的是 mark.Human.age
按照順序提供初始化值
P := person{"Tom", 25}
通過field:value的方式初始化,這樣可以任意順序
P := person{age:24, name:"Tom"}
當(dāng)然也可以通過new函數(shù)分配一個指針,此處P的類型為*person
P := new(person)
method? ??函數(shù)的另一種形態(tài),帶有接收者的函數(shù).?method是附屬在一個給定的類型上的,他的語法和函數(shù)的聲明語法幾乎一樣,只是在func后面增加了一個receiver(也就是method所依從的主體)。
func (r ReceiverType) funcName(parameters) (results)
雖然method的名字一模一樣,但是如果接收者不一樣,那么method就不一樣
method里面可以訪問接收者的字段
調(diào)用method通過.訪問,就像struct里面訪問字段一樣
指針作為receiverGo知道receiver是指針,他自動幫你轉(zhuǎn)
method繼承如果匿名字段實現(xiàn)了一個method,那么包含這個匿名字段的struct也能調(diào)用該method。
method重寫我們可以在struct上面定義一個method,重寫了匿名字段的方法,go語言會根據(jù)receiver,優(yōu)先調(diào)用最外層的method (最外層的優(yōu)先訪問)
interface? ??interface是一組method簽名的組合,我們通過interface來定義對象的一組行為。它讓面向?qū)ο?,?nèi)容組織實現(xiàn)非常的方便
interface類型interface類型定義了一組方法,如果某個對象實現(xiàn)了某個接口的所有方法,則此對象就實現(xiàn)了此接口
// 定義interfacetypeMeninterface{SayHi()Sing(lyricsstring)Guzzle(beerSteinstring)}
任意的類型都實現(xiàn)了空interface(我們這樣定義:interface{}),也就是包含0個method的interface。空interface在我們需要存儲任意類型的數(shù)值的時候相當(dāng)有用,因為它可以存儲任意類型的數(shù)值。它有點類似于C語言的void*類型。
interface函數(shù)參數(shù)interface的變量可以持有任意實現(xiàn)該interface類型的對象,這給我們編寫函數(shù)(包括method)提供了一些額外的思考,我們是不是可以通過定義interface參數(shù),讓函數(shù)接受各種類型的參數(shù)
interface變量存儲的類型? ??
Comma-ok斷言ifvalue,ok:=element.(int); ok {
switch測試? ??switchvalue:=element.(type) {
caseint:
嵌入interface? ? 類似Struct時學(xué)習(xí)的匿名字段,如果一個interface1作為interface2的一個嵌入字段,那么interface2隱式的包含了interface1里面的method。
反射運用reflect包
使用reflect一般分成三步
1.首先需要把它轉(zhuǎn)化成reflect對象(reflect.Type或者reflect.Value,根據(jù)不同的情況調(diào)用不同的函數(shù))。這兩種獲取方式如下:
t:=reflect.TypeOf(i)//得到類型的元數(shù)據(jù),通過t我們能獲取類型定義里面的所有元素
v:=reflect.ValueOf(i)//得到實際的值,通過v我們獲取存儲在里面的值,還可以去改變值
2.轉(zhuǎn)化為reflect對象之后我們就可以進行一些操作了,也就是將reflect對象轉(zhuǎn)化成相應(yīng)的值,例如
tag:=t.Elem().Field(0).Tag//獲取定義在struct里面的標(biāo)簽
name:=v.Elem().Field(0).String()//獲取存儲在第一個字段里面的值
3.獲取反射值能返回相應(yīng)的類型和數(shù)值
varxfloat64=3.4
v:=reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())
反射的話,那么反射的字段必須是可修改的??p:=reflect.ValueOf(&x)
type? ? 申明類型 ,stuct,接口, switch測試
類型包含array,slice ,map,int..等等,也可以新建的類型
typetestIntfunc(int)bool// 聲明了一個函數(shù)類型typepersonstruct{//聲明struct 類型namestringageint}typeElderlyGentinterface{//申明接口類型SayHi()Sing(songstring)SpendSalary(amountfloat32)}list:=make(List,3)// switch 測試list[0]=1//an intlist[1]="Hello"http://a stringlist[2]=Person{"Dennis",70}forindex,element:=rangelist{switchvalue:=element.(type) {caseint:fmt.Printf("list[%d] is an int and its value is %d\n",index,value)casestring:fmt.Printf("list[%d] is a string and its value is %s\n",index,value)casePerson:fmt.Printf("list[%d] is a Person and its value is %s\n",index,value)default:fmt.Println("list[%d] is of a different type",index)? ? }}
go? ??Go的runtime管理的一個線程管理器。goroutine通過go關(guān)鍵字實現(xiàn)了,其實就是一個普通的函數(shù).goroutine是Go并行設(shè)計的核心。goroutine說到底其實就是協(xié)程,但是它比線程更小,十幾個goroutine可能體現(xiàn)在底層就是五六個線程,Go語言內(nèi)部幫你實現(xiàn)了這些goroutine之間的內(nèi)存共享。執(zhí)行g(shù)oroutine只需極少的棧內(nèi)存(大概是4~5KB),當(dāng)然會根據(jù)相應(yīng)的數(shù)據(jù)伸縮。也正因為如此,可同時運行成千上萬個并發(fā)任務(wù)。goroutine比thread更易用、更高效、更輕便。? ??
不要通過共享來通信,而要通過通信來共享。
chan? ??通信機制channel。channel可以與Unix shell 中的雙向管道做類比:可以通過它發(fā)送或者接收值。這些值只能是特定的類型:channel類型。定義一個channel時,也需要定義發(fā)送到channel的值的類型。注意,必須使用make 創(chuàng)建channel:
ci:=make(chanint)cs:=make(chanstring)cf:=make(chaninterface{})//channel通過操作符<-來接收和發(fā)送數(shù)據(jù)ch<-v// 發(fā)送v到channel ch.v:=<-ch// 從ch中接收數(shù)據(jù),并賦值給v
默認(rèn)情況下,channel接收和發(fā)送數(shù)據(jù)都是阻塞的,除非另一端已經(jīng)準(zhǔn)備好,這樣就使得Goroutines同步變的更加的簡單,而不需要顯式的lock。所謂阻塞,也就是如果讀?。╲alue := <-ch)它將會被阻塞,直到有數(shù)據(jù)接收。其次,任何發(fā)送(ch<-5)將會被阻塞,直到數(shù)據(jù)被讀出。無緩沖channel是在多個goroutine之間同步很棒的工具。
Buffered Channelsch:= make(chan bool, 4),創(chuàng)建了可以存儲4個元素的bool 型channel。在這個channel 中,前4個元素可以無阻塞的寫入。當(dāng)寫入第5個元素時,代碼將會阻塞,直到其他goroutine從channel 中讀取一些元素,騰出空間。? ??ch:=make(chantype, value)
當(dāng) value = 0 時,channel 是無緩沖阻塞讀寫的,當(dāng)value > 0 時,channel 有緩沖、是非阻塞的,直到寫滿 value 個元素才阻塞寫入。
range,像操作slice或者map一樣操作緩存類型的channel,?fori:=rangec {
close?? ??關(guān)閉channel
生產(chǎn)者通過內(nèi)置函數(shù)close關(guān)閉channel。關(guān)閉channel之后就無法再發(fā)送任何數(shù)據(jù)了,在消費方可以通過語法v, ok := <-ch測試channel是否被關(guān)閉。如果ok返回false,那么說明channel已經(jīng)沒有任何數(shù)據(jù)并且已經(jīng)被關(guān)閉。
記住應(yīng)該在生產(chǎn)者的地方關(guān)閉channel,而不是消費的地方去關(guān)閉它,這樣容易引起panic
另外記住一點的就是channel不像文件之類的,不需要經(jīng)常去關(guān)閉,只有當(dāng)你確實沒有任何發(fā)送數(shù)據(jù)了,或者你想顯式的結(jié)束range循環(huán)之類的
select? ??可以監(jiān)聽channel上的數(shù)據(jù)流動
select? ??可以監(jiān)聽channel上的數(shù)據(jù)流動。select默認(rèn)是阻塞的,只有當(dāng)監(jiān)聽的channel中有發(fā)送或接收可以進行時才會運行,當(dāng)多個channel都準(zhǔn)備好的時候,select是隨機的選擇一個執(zhí)行的。
funcfibonacci(c,quitchanint) {x,y:=1,1for{select{casec<-x:x,y=y,x+ycase<-quit:fmt.Println("quit")return}? }}//select來設(shè)置超時? case <- time.After(5 * time.Second): // 這個是channel啊select{casev:=<-c:println(v)case<-time.After(5*time.Second):println("timeout")o<-truebreak}
runtime goroutine ? ??//特殊強調(diào)
runtime包中有幾個處理goroutine的函數(shù):
Goexit
退出當(dāng)前執(zhí)行的goroutine,但是defer函數(shù)還會繼續(xù)調(diào)用
Gosched
讓出當(dāng)前goroutine的執(zhí)行權(quán)限,調(diào)度器安排其他等待的任務(wù)運行,并在下次某個時候從該位置恢復(fù)執(zhí)行。
NumCPU
返回 CPU 核數(shù)量
NumGoroutine
返回正在執(zhí)行和排隊的任務(wù)總數(shù)
GOMAXPROCS
用來設(shè)置可以并行計算的CPU核數(shù)的最大值,并返回之前的值。