圖片:Gopher 吉祥物和舊 logo 簡要介紹下 Go(Golang) 語言。Go 語言是由谷歌工程師 Robert Griesemer、Rob Pike 和 Ken Thompson 創(chuàng)造的一種靜態(tài)類型編譯語言。首個開源版本發(fā)布于2012年3月。
在諸多編程語言中,解決給定問題的方法有很多,程序員往往需要花很多時間思考關(guān)于解決問題的最佳方法。 但是,Go 相信更少的功能 —— 只有一種正確的方法來解決問題。 這節(jié)省了開發(fā)人員的時間,并使大型代碼庫易于維護(hù)。 Go 中沒有 maps 和 filters 等“富有表現(xiàn)力”的功能。
Golang 最新發(fā)布的 logo: https://blog./go-brand 入門Go 程序由包組成。編譯器會將 main 包編譯成可執(zhí)行程序,而不是共享庫。main 包是整個應(yīng)用的入口。main 包的定義如下: package main 接下來,咱們在 Go 的工作空間中創(chuàng)建一個叫做 main.go 的文件,來實現(xiàn)一個簡單的 hello world 例子。 工作空間(Workspace)Go 的工作空間可以通過環(huán)境變量 GOPATH 定義。 你需要在這個工作空間中編寫你自己的代碼。Go 為搜索 GOPATH 以及 GOROOT 指定的目錄中的所有包,GOROOT 變量是在安裝 GO 時設(shè)置的,也就是 go 語言安裝的目錄。 將 GOPATH 設(shè)置到預(yù)期目錄。現(xiàn)在,增加一個文件夾 ~/workspace # export env export GOPATH=~/workspace # go inside the workspace directory cd ~/workspace 接下來在這個工作空間中創(chuàng)建 main.go ,寫入下面的代碼。 Hello World!package mainimport ( 'fmt')func main(){ fmt.Println('Hello World!')} 在上面的示例代碼中,fmt 是 Go 的一個內(nèi)置包,主要實現(xiàn)格式化 I/O 的功能。 在 Go 語言中,通過 import 關(guān)鍵字引入一個包。func main 是可執(zhí)行代碼的入口。fmt 中的 Println 函數(shù)實現(xiàn) “hello world” 的打印。 下面嘗試運行這個文件。我們可以通過兩種方法運行一個 Go 命令。我們知道 Go 是一個編譯性語言,所以,在執(zhí)行之前我們先來編譯它。 > go build main.go 上面的命令就生成了一個可執(zhí)行文件 main ,接下來我們運行這個文件: > ./main # Hello World! 接下來看另外一種更加簡單的執(zhí)行方式。go run 命令將編譯步驟抽象。那么,通過下面的命令就可以執(zhí)行這個程序。 go run main.go # Hello World! 注意:可以使用 https://play. 嘗試上面的代碼。 變量Go 的變量必須顯式聲明。Go 是靜態(tài)類型的語言,也就是說,在變量聲明時要檢查變量類型??梢匀缦侣暶饕粋€變量: var a int 在這種情況下,變量的值會被設(shè)為0。也可以通過下面的語法聲明變量并初始化為不同的值: var a = 1 在這里,變量自動被判斷為一個整形變量。我們可以通過簡化形式來聲明變量: message := 'hello world' 也可以在一行聲明多個變量: var b, c int = 2, 3 數(shù)據(jù)類型和其他編程語言一樣,Go 語言也有不同的數(shù)據(jù)類型。接下來就來看一下: 數(shù)字(Number)、字符串(String)和布爾(Boolean) 可支持?jǐn)?shù)字存儲類型有 int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr… 字符串以字節(jié)序列的形式存儲。用string關(guān)鍵字表示和聲明。 布爾型變量的關(guān)鍵字是 bool 。 Go 還支持以 complex64 和 complex128 聲明的復(fù)雜數(shù)字類型。 var a bool = true var b int = 1 var c string = 'hello world' var d float32 = 1.222 var x complex128 = cmplx.Sqrt(-5 + 12i) 數(shù)組、切片、集合數(shù)組是相同數(shù)據(jù)類型的元素序列。數(shù)組具有在聲明中定義的固定長度,因此不能進(jìn)行擴展。數(shù)組聲明為: var a [5]int 數(shù)組也可以是多維的。我們可以使用以下格式創(chuàng)建它們: var multiD [2][3]int 當(dāng)數(shù)組的值在運行時更改時,數(shù)組限制了這種情況。 數(shù)組也不提供獲取子數(shù)組的能力。 為此,Go 有一個名為 slices 的數(shù)據(jù)類型。 切片存儲一系列元素,可以隨時擴展。 切片聲明類似于數(shù)組聲明 - 沒有定義容量: var b []int 這將創(chuàng)建一個零容量和零長度的切片。切片也可以定義容量和長度。我們可以使用以下語法: numbers := make([]int,5,10) 這里,切片的初始長度為5,容量為10。 切片是數(shù)組的抽象。切片使用數(shù)組作為底層結(jié)構(gòu)。切片包含三個組件:容量,長度和指向底層數(shù)組的指針,如下圖所示: 圖片來源: https://blog./go-slices-usage-and-internals 通過使用append或copy函數(shù)可以增加切片的容量。 append函數(shù)可以為數(shù)組的末尾添加值,并在需要時增加容量。 numbers = append(numbers, 1, 2, 3, 4) 增加切片容量的另一種方法是使用復(fù)制功能。只需創(chuàng)建另一個具有更大容量的切片,并將原始切片復(fù)制到新創(chuàng)建的切片: // create a new slicenumber2 := make([]int, 15)// copy the original slice to new slicecopy(number2, number) 我們可以創(chuàng)建切片的子切片。這可以使用以下命令完成: // initialize a slice with 4 len and values number2 = []int{1,2,3,4}fmt.Println(numbers) // -> [1 2 3 4]// create sub slices slice1 := number2[2:]fmt.Println(slice1) // -> [3 4] slice2 := number2[:3]fmt.Println(slice2) // -> [1 2 3] slice3 := number2[1:4]fmt.Println(slice3) // -> [2 3 4] 集合是Go中的數(shù)據(jù)類型,它將鍵映射到值。我們可以使用以下命令定義映射: var m map[string]int 這里m是新的集合變量,它的鍵為字符串,值為整數(shù)。我們可以輕松地將鍵和值添加到地圖中: // adding key/value m['clearity'] = 2 m['simplicity'] = 3 // printing the values fmt.Println(m['clearity']) // -> 2 fmt.Println(m['simplicity']) // -> 3 數(shù)據(jù)類型轉(zhuǎn)換 一種數(shù)據(jù)類型可以通過類型轉(zhuǎn)換得到另一種數(shù)據(jù)類型。我們來看一個簡單的例子: a := 1.1b := int(a)fmt.Println(b)//-> 1 不是所有的數(shù)據(jù)類型都可以轉(zhuǎn)換為別的數(shù)據(jù)類型。必須確保兩種數(shù)據(jù)類型之間的兼容性。 條件語句if else對于條件判斷,我們可以像如下的例子那樣使用 if-else 語句。要確保大括號和條件語句在同一行。 if num := 9; num <><> switch caseSwitch cases 有助于組織多個條件語句。以下示例顯示了一個簡單的 switch case 語句: i := 2switch i {case 1: fmt.Println('one')case 2: fmt.Println('two')default: fmt.Println('none')} 循環(huán)語句Go有一個循環(huán)關(guān)鍵字。 單個for循環(huán)命令有助于實現(xiàn)不同類型的循環(huán): i := 0sum := 0for i <> 上面的示例類似于C中的while循環(huán)。對于for循環(huán),也可以使用常見的for語句 sum := 0for i := 0; i <> Go中的無限循環(huán): for {} 指針Go提供指針。指針是保存值的地址的地方。指針由*定義。根據(jù)數(shù)據(jù)類型定義指針。例: ar ap *int 這里ap是指向整數(shù)類型的指針。 &運算符可用于獲取變量的地址。 a := 12ap = &a 可以使用*運算符訪問指針的值指針: fmt.Println(*ap)// => 12 在將結(jié)構(gòu)作為參數(shù)傳遞時或在為已定義類型聲明方法時,通常首選指針
例: func increment(i *int) { *i++}func main() { i := 10 increment(&i) fmt.Println(i)}//=> 11 注意:嘗試本文示例代碼時,不要忘記將其包含在main包中,并在需要時導(dǎo)入fmt或其他包,如上面第一個main.go示例所示。 函數(shù)主函數(shù)中定義的主要功能是執(zhí)行程序的入口點,可以定義和使用更多功能。讓我們看一個簡單的例子: func add(a int, b int) int { c := a + b return c}func main() { fmt.Println(add(2, 1))}//=> 3 正如我們在上面的例子中所看到的,使用 func 關(guān)鍵字后跟函數(shù)名來定義 Go 函數(shù)。函數(shù)所需的參數(shù)需要根據(jù)其數(shù)據(jù)類型定義,最后是返回的數(shù)據(jù)類型。 函數(shù)的返回也可以在函數(shù)中預(yù)定義: func add(a int, b int) (c int) { c = a + b return}func main() { fmt.Println(add(2, 1))}//=> 3 這里c被定義為返回變量。 因此,定義的變量c將自動返回,而無需在結(jié)尾的return語句中定義。 您還可以從使用逗號分隔返回值的單個函數(shù)返回多個值。 func add(a int, b int) (int, string) { c := a + b return c, 'successfully added'}func main() { sum, message := add(2, 1) fmt.Println(message) fmt.Println(sum)} 方法,結(jié)構(gòu)和接口Go不是一個完全面向?qū)ο蟮恼Z言,但是對于結(jié)構(gòu),接口和方法,它有很多面向?qū)ο蟮闹С趾透杏X。 結(jié)構(gòu)結(jié)構(gòu)是不同字段的類型集合。 結(jié)構(gòu)用于將數(shù)據(jù)分組在一起。 例如,如果我們想要對Person類型的數(shù)據(jù)進(jìn)行分組,我們會定義一個人的屬性,其中可能包括姓名,年齡,性別。 可以使用以下語法定義結(jié)構(gòu): type person struct { name string age int gender string} 在定義了人類型結(jié)構(gòu)的情況下,現(xiàn)在讓我們創(chuàng)建一個人: //way 1: specifying attribute and valuep = person{name: 'Bob', age: 42, gender: 'Male'}//way 2: specifying only valueperson{'Bob', 42, 'Male'} 我們可以用點(.)輕松訪問這些數(shù)據(jù) p.name//=> Bobp.age//=> 42p.gender//=> Male 您還可以使用其指針直接訪問結(jié)構(gòu)的屬性 pp = &person{name: 'Bob', age: 42, gender: 'Male'}pp.name//=> Bob 方法方法是具有接收器的特殊類型的功能。接收器既可以是值,也可以是指針。讓我們創(chuàng)建一個名為describe的方法,它具有我們在上面的例子中創(chuàng)建的接收器類型: package mainimport 'fmt'// struct definationtype person struct { name string age int gender string}// method definationfunc (p *person) describe() { fmt.Printf('%v is %v years old.', p.name, p.age)}func (p *person) setAge(age int) { p.age = age}func (p person) setName(name string) { p.name = name}func main() { pp := &person{name: 'Bob', age: 42, gender: 'Male'} pp.describe() // => Bob is 42 years old pp.setAge(45) fmt.Println(pp.age) //=> 45 pp.setName('Hari') fmt.Println(pp.name) //=> Bob} 正如我們在上面的例子中所看到的,現(xiàn)在可以使用點運算符作為pp.describe來調(diào)用該方法。 請注意,接收器是指針。 使用指針,我們傳遞對值的引用,因此如果我們對方法進(jìn)行任何更改,它將反映在接收器pp中。它也不會創(chuàng)建對象的新副本,這樣可以節(jié)省內(nèi)存。 請注意,在上面的示例中,age的值已更改,而name的值未更改,因為方法setName屬于receiver類型,而setAge屬于pointer類型。 接口Go接口是方法的集合。接口有助于將類型的屬性組合在一起。我們以接口動物為例: type animal interface { description() string} 這里的動物是一種接口類型。現(xiàn)在讓我們創(chuàng)建兩種不同類型的動物來實現(xiàn)動物接口類型: package mainimport ( 'fmt')type animal interface { description() string}type cat struct { Type string Sound string}type snake struct { Type string Poisonous bool}func (s snake) description() string { return fmt.Sprintf('Poisonous: %v', s.Poisonous)}func (c cat) description() string { return fmt.Sprintf('Sound: %v', c.Sound)}func main() { var a animal a = snake{Poisonous: true} fmt.Println(a.description()) a = cat{Sound: 'Meow!!!'} fmt.Println(a.description())}//=> Poisonous: true//=> Sound: Meow!!! 在main函數(shù)中,我們創(chuàng)建了一個動物類型的變量a。 我們?yōu)閯游锓峙渖吆拓堫愋停⑹褂肞rintln打印a.description。 由于我們以不同的方式實現(xiàn)了兩種類型(貓和蛇)中描述的方法,我們可以打印出動物的描述。 包 (Package)我們在Go中編寫所有代碼。主程序包是程序執(zhí)行的入口點。 Go中有很多內(nèi)置包。我們一直使用的最著名的是fmt包。
安裝包go get 我們安裝的軟件包保存在GOPATH env中,這是我們的工作目錄。您可以通過cd $GOPATH/pkg進(jìn)入我們的工作目錄中的pkg包管理文件夾。 自定義一個讓我們開始創(chuàng)建一個自定義包文件目錄: > mkdir custom_package> cd custom_package 要創(chuàng)建自定義包,我們需要首先使用我們需要的包名創(chuàng)建一個文件夾。假設(shè)我們正在建立一個包person。為此,我們在custom_package文件夾中創(chuàng)建一個名為person的文件夾: > mkdir person> cd person 現(xiàn)在讓我們在這個文件夾中創(chuàng)建一個文件person.go。 package personfunc Description(name string) string { return 'The person name is: ' + name}func secretName(name string) string { return 'Do not share'} 我們現(xiàn)在需要安裝包,以便可以導(dǎo)入和使用它。所以讓我們安裝它: > go install 現(xiàn)在讓我們回到custom_package文件夾并創(chuàng)建一個main.go文件 package mainimport( 'custom_package/person' 'fmt')func main(){ p := person.Description('Milap') fmt.Println(p)}// => The person name is: Milap 現(xiàn)在,我們可以導(dǎo)入我們創(chuàng)建的包person并使用函數(shù)Description。請注意,我們在包中創(chuàng)建的函數(shù)secretName將無法訪問。在Go中,以大寫字母開頭的方法名稱將是私有的。 包文檔 Go內(nèi)置了對包文檔的支持。運行以下命令以生成文檔: godoc person Description 這將為我們的包person生成Description函數(shù)的文檔。要查看文檔,請使用以下命令運行Web服務(wù)器: godoc -http=':8080' 現(xiàn)在轉(zhuǎn)到URL http://localhost:6060/pkg /并查看我們剛剛創(chuàng)建的包的文檔。 Go中的一些內(nèi)置包 fmt 該包實現(xiàn)了格式化的I/O功能。我們已經(jīng)使用該包打印到stdout。 JSON Go中另一個有用的包是json包。這有助于編碼/解碼JSON。讓我們舉一個例子來編碼/解碼一些json: 編碼 package mainimport ( 'fmt' 'encoding/json'func main(){ mapA := map[string]int{'apple': 5, 'lettuce': 7} mapB, _ := json.Marshal(mapA) fmt.Println(string(mapB))} 解碼 package mainimport ( 'fmt' 'encoding/json')type response struct { PageNumber int `json:'page'` Fruits []string `json:'fruits'`}func main(){ str := `{'page': 1, 'fruits': ['apple', 'peach']}` res := response{} json.Unmarshal([]byte(str), &res) fmt.Println(res.PageNumber)}//=> 1 在使用unmarshal解碼json字節(jié)時,第一個參數(shù)是json字節(jié),第二個參數(shù)是我們希望json映射到的響應(yīng)類型struct的地址。 請注意,json:“page”將頁面鍵映射到struct中的PageNumber鍵。 錯誤處理錯誤是程序的意外和意外結(jié)果。 假設(shè)我們正在對外部服務(wù)進(jìn)行API調(diào)用。 此API調(diào)用可能成功或可能失敗。 當(dāng)存在錯誤類型時,可以識別Go程序中的錯誤。 我們來看看這個例子: resp, err := http.Get('http:///') 這里對錯誤對象的API調(diào)用可能通過或可能失敗。我們可以檢查錯誤是否為nil,并相應(yīng)地處理響應(yīng): package mainimport ( 'fmt' 'net/http')func main(){ resp, err := http.Get('http:///') if err != nil { fmt.Println(err) return } fmt.Println(resp)} 從函數(shù)返回自定義錯誤 當(dāng)我們編寫自己的函數(shù)時,有些情況下我們會遇到錯誤。可以在錯誤對象的幫助下返回這些錯誤: func Increment(n int) (int, error) { if n < 0 { return error object return nil, errors.new('math: cannot process negative number') } return (n + 1), nil}func main() { num :=""> 使用Go構(gòu)建的大多數(shù)軟件包或我們使用的外部軟件包都有一種錯誤處理機制。 所以我們調(diào)用的任何函數(shù)都可能存在錯誤。 這些錯誤永遠(yuǎn)不應(yīng)該被忽略,并且總是在我們稱之為函數(shù)的地方優(yōu)雅地處理,就像我們在上面的例子中所做的那樣。 PanicPanic是一種未經(jīng)處理的異常,在程序執(zhí)行期間突然遇到。 在Go中,Panic不是處理程序中異常的理想方式。 建議使用錯誤對象。 發(fā)生Panic時,程序執(zhí)行停止。 Panic之后執(zhí)行的事情就是defer。 DeferDefer總是在函數(shù)結(jié)束時執(zhí)行。 //Gopackage mainimport 'fmt'func main() { f() fmt.Println('Returned normally from f.')}func f() { defer func() { if r := recover(); r != nil { fmt.Println('Recovered in f', r) } }() fmt.Println('Calling g.') g(0) fmt.Println('Returned normally from g.')}func g(i int) { if i > 3 { fmt.Println('Panicking!') panic(fmt.Sprintf('%v', i)) } defer fmt.Println('Defer in g', i) fmt.Println('Printing in g', i) g(i + 1)} 在上面的例子中,我們使用panic()執(zhí)行程序。 正如您所注意到的,有一個defer語句,它將使程序在程序執(zhí)行結(jié)束時執(zhí)行該行。 當(dāng)我們需要在函數(shù)結(jié)束時執(zhí)行某些操作時,也可以使用Defer,例如關(guān)閉文件。 并發(fā)Go是建立在并發(fā)性的基礎(chǔ)上的。 Go中的并發(fā)可以通過輕量級線程的Go例程來實現(xiàn)。 Go routineGo routine是可以與另一個函數(shù)并行或同時運行的函數(shù)。 創(chuàng)建Go routine非常簡單。 只需在函數(shù)前添加關(guān)鍵字Go,我們就可以使它并行執(zhí)行。 Go routine非常輕量級,因此我們可以創(chuàng)建數(shù)千個例程。 讓我們看一個簡單的例子: package mainimport ( 'fmt' 'time')func main() { go c() fmt.Println('I am main') time.Sleep(time.Second * 2)}func c() { time.Sleep(time.Second * 2) fmt.Println('I am concurrent')}//=> I am main//=> I am concurrent 正如您在上面的示例中所看到的,函數(shù)c是一個Go routine ,它與主Go線程并行執(zhí)行。 有時我們想要在多個線程之間共享資源。 Go更喜歡不與另一個線程共享變量,因為這會增加死鎖和資源等待的可能性。 還有另一種在Go routine 之間共享資源的方法:via go channels。 通道 我們可以使用通道在兩個Go例程之間傳遞數(shù)據(jù)。在創(chuàng)建頻道時,必須指定頻道接收的數(shù)據(jù)類型。讓我們創(chuàng)建一個字符串類型的簡單通道,如下所示: c := make(chan string) 使用此通道,我們可以發(fā)送字符串類型數(shù)據(jù)。我們都可以在此頻道中發(fā)送和接收數(shù)據(jù): package mainimport 'fmt'func main(){ c := make(chan string) go func(){ c <><-c fmt.println(msg)} =="">'hello' 接收方通道等待發(fā)送方向通道發(fā)送數(shù)據(jù)。 單向通道 在某些情況下,我們希望 Go routine 通過通道接收數(shù)據(jù)但不發(fā)送數(shù)據(jù),反之亦然。為此,我們還可以創(chuàng)建單向通道。讓我們看一個簡單的示例: package mainimport ( 'fmt')func main() { ch := make(chan string) go sc(ch) fmt.Println(<><><> 在上面的例子中,sc 是一個 Go routine ,它只能向通道發(fā)送消息但不能接收消息。 使用select為Go例程組織多個通道函數(shù)可能有多個通道正在等待。為此,我們可以使用select語句。讓我們看一個更清晰的例子: package mainimport ( 'fmt' 'time')func main() { c1 := make(chan string) c2 := make(chan string) go speed1(c1) go speed2(c2) fmt.Println('The first to arrive is:') select { case s1 := <><><><> 在上面的例子中,main正在等待兩個通道c1和c2。使用select case語句打印主函數(shù),消息從通道發(fā)送,無論它先收到哪個。 緩沖通道有些情況下我們需要向通道發(fā)送多個數(shù)據(jù)。您可以為此創(chuàng)建緩沖通道。使用緩沖通道,接收器在緩沖區(qū)已滿之前不會收到消息。我們來看看這個例子: package mainimport 'fmt'func main(){ ch := make(chan string, 2) ch <><><> 為什么Golang成功了?
好極了!我們已經(jīng)學(xué)習(xí)了 Go 語言的一些主要組件及功能。
恭喜,你對 Go 已經(jīng)有不錯的理解了。
不要止步于此。繼續(xù)前進(jìn)。構(gòu)思一個小應(yīng)用,然后開始構(gòu)建之。 |
|