「Go是一種開源編程語言,能夠輕鬆構建簡單,可靠,高效的軟件」。 ——GoLang程序員
在許多語言中,有許多方法能夠解決給定的問題。程序員能夠花不少時間思考解決問題的最佳方法。另外一方面,Golang堅持精簡的功能 - 只有一種正確的方法能夠解決問題。github
這節省了開發人員的時間,並使大型代碼庫易於維護。Golang中沒有像地圖和過濾器這樣的「富有表現力」的功能。golang
最近發佈的golang新標識:https://blog.golang.org/go-brand「若是你有增長表現力的功能,一般會增長費用」 ——羅伯派克編程
Golang由包組成。包main告訴Golang編譯器該程序被編譯爲可執行文件,而不是共享庫。它是應用程序的入口點。主包定義爲:json
package main
複製代碼
讓咱們經過main.go 在Go lang工做區中建立一個文件來編寫一個簡單的hello world示例數組
Go中的工做空間由環境變量GOPATH定義。bash
您編寫的任何代碼都將寫在工做區內。Go將搜索GOPATH目錄中的全部的包,或者GOROOT在安裝Go時默認設置的目錄。 GOROOT是安裝go的路徑。服務器
設置 GOPATH 爲所需的目錄。如今,讓咱們將 ~/workspace設置爲GOPATH。數據結構
# export env
export GOPATH=~/workspace
# go inside the workspace directory
cd ~/workspace
複製代碼
在main.go 咱們剛剛建立的工做空間文件夾中使用如下代碼建立文件。
HELLO WORLD!
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello World!")
}
複製代碼
在上面的示例中,fmt
是Go中的內置包實現了格式化I / O的函數。
咱們使用import
關鍵字在Go中導入包。func main
是代碼執行的主要入口點。Println
是包內的一個函數,fmt
它爲咱們打印「hello world」。
讓咱們看一下運行這個文件。咱們能夠經過兩種方式運行Go命令。咱們知道,Go是一種編譯語言,因此咱們首先須要在執行以前編譯它。
> go build main.go
複製代碼
這將建立一個二進制可執行文件 main ,如今咱們能夠運行
> ./main
# Hello World!
複製代碼
注意:要嘗試運行本文提到的代碼,您可使用 play.golang.org
Go中的變量是明確聲明的。Go是一種靜態類型語言。這意味着在變量聲明時檢查變量類型。變量能夠聲明爲:
var a int
複製代碼
在這種狀況下,該值將設置爲0.使用如下語法聲明和初始化具備不一樣值的變量:
var a = 1
複製代碼
這裏變量自動指定爲int。咱們可使用變量聲明的簡寫定義:
message := "hello world"
複製代碼
咱們還能夠在同一行中聲明多個變量:
var b,c int = 2,3
複製代碼
與任何其餘編程語言同樣,Golang支持各類不一樣的數據結構。讓咱們探討一下:
一些INT類型的有int,int8,int16,int32,int64, uint,uint8,uint16,uint32,uint64,uintptr ......
字符串類型存儲一系列字節。它用關鍵字string
表示和聲明。
使用關鍵字bool
存儲布爾類型。
Golang還支持複雜的數字類型數據類型,能夠用complex64
和complex128
。
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)
複製代碼
數組是相同數據類型的元素序列。數組具備在聲明中定義的固定長度,所以不能進行擴展。數組聲明爲:
var a [5] int
複製代碼
數組也能夠是多維的。咱們可使用如下格式建立它們:
var multiD [2] [3] int
複製代碼
當數組的值在運行時更改時,數組限制了這種狀況。數組也不提供獲取子數組的能力。爲此,Golang有一個名爲slices的數據類型。
切片存儲一系列元素,能夠隨時擴展。切片聲明相似於數組聲明 - 沒有定義容量:
var b [] int
複製代碼
這將建立一個零容量和零長度的切片。切片也能夠定義容量和長度。咱們可使用如下語法:
numbers:= make([] int,5,10)
複製代碼
這裏,切片的初始長度爲5,容量爲10。
切片是數組的抽象。切片使用數組做爲底層結構。切片包含三個組件:容量,長度和指向底層數組的指針,以下圖所示:
經過使用append或copy函數能夠增長切片的容量。append函數能夠爲數組的末尾增長值,並在須要時增長容量。
numbers = append(numbers, 1, 2, 3, 4)
複製代碼
增長切片容量的另外一種方法是使用複製功能。只需建立另外一個具備更大容量的切片,並將原始切片複製到新建立的切片:
// create a new slice
number2 := make([]int, 15)
// copy the original slice to new slice
copy(number2, number)
複製代碼
咱們能夠建立切片的子切片。這可使用如下命令完成:
package main
import (
"fmt"
)
func main() {
// initialize a slice with 4 len and values
number2 := []int{1, 2, 3, 4}
fmt.Println(number2) // -> [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]
}
複製代碼
Map
是Go中的數據類型,它將鍵映射到值。咱們可使用如下命令定義映射:
var m map[string]int
複製代碼
這 m 是新的map變量,其鍵是string
,值是integers
。咱們能夠輕鬆地將鍵和值添加到地map中:
package main
import (
"fmt"
)
func main() {
m := make(map[string]int)
// adding key/value
m["clearity"] = 2
m["simplicity"] = 3
// printing the values
fmt.Println(m["clearity"]) // -> 2
fmt.Println(m["simplicity"]) // -> 3
}
複製代碼
可使用類型轉換將一種類型的數據類型轉換爲另外一種類型。咱們來看一個簡單的類型轉換:
package main
import (
"fmt"
)
func increment(i *int) {
*i++
}
func main() {
a := 1.1
b := int(a)
fmt.Println(b)
//-> 1
}
複製代碼
並不是全部類型的數據類型均可以轉換爲其餘類型。要確保數據類型與轉換兼容。
對於條件語句,咱們可使用if-else語句,以下例所示。確保花括號與條件位於同一行。
package main
import (
"fmt"
)
func increment(i *int) {
*i++
}
func main() {
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
}
複製代碼
Switch cases
有助於組織多個條件語句. 如下示例顯示了一個簡單的switch case語句:
package main
import (
"fmt"
)
func increment(i *int) {
*i++
}
func main() {
i := 2
switch i {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
default:
fmt.Println("none")
}
}
複製代碼
Golang有一個循環關鍵字。單個for循環命令有助於實現不一樣類型的循環:
package main
import (
"fmt"
)
func increment(i *int) {
*i++
}
func main() {
i := 0
sum := 0
for i < 10 {
sum += 1
i++
}
fmt.Println(sum)
}
複製代碼
上面的示例相似於C中的while循環。對於for循環,可使用相同的for語句:
package main
import (
"fmt"
)
func increment(i *int) {
*i++
}
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum)
}
複製代碼
Go中的無限循環:
for {
}
複製代碼
Golang提供了指針。指針是保存值的地址的地方。指針由*定義。根據數據類型定義指針。例:
var ap * int
複製代碼
這ap
是指向整數類型的指針。該&
操做可用於獲取變量的地址。
a := 12
ap =&a
複製代碼
可使用 *
運算符訪問指針指向的值 :
fmt.Println(* AP)
// => 12
複製代碼
在將結構體做爲參數傳遞或者爲已定義類型聲明方法時,一般首選指針。
例:
package main
import (
"fmt"
)
func increment(i *int) {
*i++
}
func main() {
i := 10
increment(&i)
fmt.Println(i)
}
//=> 11
複製代碼
注意:在文中運行示例代碼時,不要忘記將其包含在main包中,並在須要時導入fmt或其餘包,如上面第一個main.go示例所示。
main包中定義的main函數是golang程序執行的入口點。能夠定義和使用更多的函數。讓咱們看一個簡單的例子:
package main
import (
"fmt"
)
func add(a int, b int) int {
c := a + b
return c
}
func main() {
fmt.Println(add(2, 1))
}
//=> 3
複製代碼
正如咱們在上面的例子中所看到的,使用func
關鍵字後跟函數名來定義Golang函數。函數所需的參數須要根據其數據類型定義,最後是返回的數據類型。
函數的返回值也能夠在函數中預約義:
package main
import (
"fmt"
)
func add(a int, b int) (c int) {
c = a + b
return
}
func main() {
fmt.Println(add(2, 1))
}
//=> 3
複製代碼
這裏c被定義爲返回變量。所以,定義的變量c將自動返回,而無需在結尾的return語句中定義。
您還能夠從一個函數返回多個返回值,將返回值與逗號分隔開。
package main
import (
"fmt"
)
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)
}
//=> successfully added
//=> 3
複製代碼
Golang不是一個徹底面向對象的語言,可是有結構體,接口和方法,來支持面向對象的開發。
結構體是不一樣字段的類型集合。結構體用於將數據分組在一塊兒。例如,若是咱們想要對Person類型的數據進行分組,咱們會定義一我的的屬性,其中可能包括姓名,年齡,性別。可使用如下語法定義結構:
type person struct {
名字串
年齡int
性別串
}
複製代碼
在定義了人類型結構體的狀況下,如今讓咱們建立一我的:
//way 1: specifying attribute and value
p = person{name: "Bob", age: 42, gender: "Male"}
//way 2: specifying only value
person{"Bob", 42, "Male"}
複製代碼
咱們能夠用點(.)輕鬆訪問這些數據
p.name
//=> Bob
p.age
//=> 42
p.gender
//=> Male
複製代碼
您還可使用其指針直接訪問結構的屬性:
pp = &person{name: "Bob", age: 42, gender: "Male"}
pp.name
//=> Bob
複製代碼
方法是具備接收器的特殊類型的函數。接收器既能夠是值,也能夠是指針。讓咱們建立一個名爲describe的方法,它是咱們上面的例子中person
結構體的一個方法:
package main
import "fmt"
// struct defination
type person struct {
name string
age int
gender string
}
// method defination
func (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
}
複製代碼
正如咱們在上面的例子中所看到的,如今可使用點運算符調用該方法 pp.describe
。請注意,接收器是指針。使用指針,咱們傳遞對該值的引用,所以若是咱們對方法進行任何更改,它將反映在接收器pp
中。它也不會建立對象的新副本,從而節省了內存。
請注意,在上面的示例中,age的值已更改,而name的值未更改,由於方法setName屬於接收器類型,而setAge屬於指針類型。
Golang接口是方法的集合。接口有助於將類型的屬性組合在一塊兒。咱們以動物接口爲例:
type animal interface {
description() string
}
複製代碼
這裏的animal是接口類型。如今讓咱們建立兩種不一樣類型的animal來實現animal接口:
package main
import (
"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函數中,咱們建立了一個a
類型爲animal
的變量。咱們爲animal分配snake和cat類型,並使用Println打印a.description。因爲咱們以不一樣的方式實現了兩種類型(貓和蛇)中描述的方法,咱們獲得了不一樣animal的描述。
咱們在一個包中寫入Golang中的全部代碼。該主包是程序執行的入口點。Go中有不少內置包。咱們一直使用的最多的是fmt包。
「Go packages in the main mechanism for programming in the large that go provides and they make possible to divvy up a large project into smaller pieces.」
— Robert Griesemer
go get
// example
go get github.com/satori/go.uuid
複製代碼
咱們安裝的軟件包保存在GOPATH中,這是咱們的工做目錄。您能夠經過咱們的工做目錄中的pkg文件夾進入包 cd $GOPATH/pkg
。
讓咱們從建立一個文件夾custom_package開始:
> mkdir custom_package
> cd custom_package
複製代碼
要建立自定義包,咱們須要首先使用咱們須要的包名建立一個文件夾。假設咱們正在構建一個包 person
。爲此,咱們建立一個名爲person
的文件夾在custom_package
文件夾中:
> mkdir person
> cd person
複製代碼
如今讓咱們在這個文件夾中建立一個文件person.go。
package person
func Description(name string) string {
return "The person name is: " + name
}
func secretName(name string) string {
return "Do not share"
}
複製代碼
咱們如今如今須要安裝包,以即可以導入和使用它。因此讓咱們安裝它:
> go install
複製代碼
如今讓咱們回到custom_package文件夾並建立一個main.go文件
package main
import (
"custom_package/person"
"fmt"
)
func main() {
p := person.Description("Milap")
fmt.Println(p)
}
// => The person name is: Milap
複製代碼
如今咱們能夠導入咱們建立的person
包,並使用函數Description。請注意,secretName 咱們在包中建立的函數將沒法訪問。在Go中,以小寫字母開頭的方法名稱將是私有的。
Golang內置了對包文檔的支持。運行如下命令以生成文檔:
godoc person Description
複製代碼
這將爲咱們的包開發人員生成Description函數的文檔。要查看文檔,請使用如下命令運行Web服務器:
godoc -http =「:8080」
複製代碼
如今打開URLhttp//localhost8080/pkg/
並查看咱們剛建立的包的文檔。
該包實現了格式化的I / O功能。咱們已經使用該包打印到stdout。
Golang中另外一個有用的包是json包。這有助於編碼/解碼JSON。讓咱們舉一個例子來編碼/解碼一些json:
package main
import (
"encoding/json"
"fmt"
)
func main() {
mapA := map[string]int{"apple": 5, "lettuce": 7}
mapB, _ := json.Marshal(mapA)
fmt.Println(string(mapB))
}
複製代碼
package main
import (
"encoding/json"
"fmt"
)
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字節時,第一個參數是json字節,第二個參數是咱們但願json映射到的響應類型struct的地址。請注意, json:」page」 映射頁面鍵是結構體中的PageNumber鍵。
錯誤是程序的不但願的和意外的結果。假設咱們正在對外部服務進行API調用。此API調用可能成功或可能失敗。當存在錯誤類型時,能夠識別Golang程序中的錯誤。咱們來看看這個例子:
resp, err := http.Get("http://example.com/")
複製代碼
這裏的err表示API調用可能會經過或可能失敗。咱們能夠檢查錯誤是否爲nil
,並相應地處理響應:
package main
import (
"fmt"
"net/http"
)
func main() {
resp, err := http.Get("http://example.com/")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(resp)
}
複製代碼
當咱們編寫本身的函數時,有些狀況下咱們會遇到錯誤。能夠經過errors
來返回這些錯誤:
package main
import (
"errors"
"fmt"
)
func Increment(n int) (int, error) {
if n < 0 {
// return error object
return 0, errors.New("math: cannot process negative number")
}
return (n + 1), nil
}
func main() {
num := 5
if inc, err := Increment(num); err != nil {
fmt.Printf("Failed Number: %v, error message: %v", num, err)
} else {
fmt.Printf("Incremented Number: %v", inc)
}
}
// => The person name is: Milap
複製代碼
在Go中構建的大多數軟件包或咱們使用的外部軟件包都有一個錯誤處理機制。因此咱們調用的任何函數均可能存在錯誤。這些錯誤永遠不該該被忽略,而且老是在咱們稱之爲函數的地方優雅地處理,就像咱們在上面的例子中所作的那樣。
panic是一種未經處理的錯誤,在程序執行期間忽然遇到。在Go中,panic不是處理程序中異常的理想方式。建議使用錯誤對象。發生panic時,程序執行中止。panic以後執行的事情就是defer。
package main
import "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)
}
複製代碼
Defer老是在函數結束時執行。
在上面的例子中,咱們使用panic()來恐慌地執行程序。正如您所注意到的,有一個延遲語句,它將使程序在程序執行結束時執行該行。當咱們須要在函數結束時執行某些操做時,也可使用Defer,例如關閉文件。
Golang在構建時考慮了併發性。Golang中的併發能夠經過輕量級線程的Go例程來實現。
Goroutine是能夠與另外一個函數並行或同時運行的函數。建立Goroutine很是簡單。只需在函數前面添加關鍵字Go,咱們就可使它並行執行。Go協程很是輕量級,所以咱們能夠建立數千個例程。讓咱們看一個簡單的例子:
package main
import (
"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
複製代碼
正如您在上面的示例中所看到的,函數c是一個Goroutine,它與主Go線程並行執行。有時咱們但願在多個線程之間共享資源。Golang不喜歡將一個線程的變量與另外一個線程共享,由於這會增長死鎖和資源等待的可能性。還有另外一種在Goroutine之間共享資源的方法:channels。
咱們可使用通道在兩個Goroutine之間傳遞數據。在創通道時,必須指定通道接收的數據類型。讓咱們建立一個字符串類型的簡單通道,以下所示:
c := make(chan string)
複製代碼
使用這個通道,咱們能夠發送字符串類型數據。咱們均可以在此通道中發送和接收數據:
package main
import "fmt"
func main() {
c := make(chan string)
go func() { c <- "hello" }()
msg := <-c
fmt.Println(msg)
}
//=>"hello"接收方通道等待發送方向通道發送數據。
複製代碼
在某些狀況下,咱們但願Goroutine經過通道接收數據但不發送數據,反之亦然。爲此,咱們還能夠建立單向通道。讓咱們看一個簡單的例子:
package main
import (
"fmt"
)
func main() {
ch := make(chan string)
go sc(ch)
fmt.Println(<-ch)
}
func sc(ch chan<- string) {
ch <- "hello"
}
複製代碼
在上面的示例中,sc
是一個Goroutine,它只能向通道發送消息但不能接收消息。
在一個函數中可能有多個通道正在等待。爲此,咱們可使用select
語句。讓咱們看一個更清晰的例子:
package main
import (
"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 := <-c1:
fmt.Println(s1)
case s2 := <-c2:
fmt.Println(s2)
}
}
func speed1(ch chan string) {
time.Sleep(2 * time.Second)
ch <- "speed 1"
}
func speed2(ch chan string) {
time.Sleep(1 * time.Second)
ch <- "speed 2"
}
複製代碼
在上面的示例中,main正在等待兩個通道c1和c2。使用select case語句打印主函數,消息從通道中發送,不管它先收到哪一個。
您能夠在Golang中建立緩衝通道。使用緩衝通道,若是緩衝區已滿,則將阻止發送到通道的消息。咱們來看看這個例子:
package main
import "fmt"
func main() {
ch := make(chan string, 2)
ch <- "hello"
ch <- "world"
ch <- "!" // extra message in buffer
fmt.Println(<-ch)
}
// => fatal error: all goroutines are asleep - deadlock!
複製代碼
Simplicity… — Rob-pike
咱們瞭解了Golang的一些主要組件和功能。
恭喜你,你如今對Go有了不錯的認識。
One of my most productive days was throwing away 1,000 lines of code. --Ken Thompson
不要停在這裏。繼續前進。考慮一個小應用程序並開始構建。