[翻譯] Go 語言入門

Gundy發表於2019-07-19

原文https://milapneupane.com.np/2019/07/06/lea...

“Go是一種開源程式語言,可以輕鬆構建簡單,可靠,高效的軟體”。
——GoLang

在許多語言中,有許多方法可以解決給定的問題。程式設計師可以花很多時間思考解決問題的最佳方法。另一方面,Golang堅持精簡的功能 - 只有一種正確的方法可以解決問題。

這節省了開發人員的時間,並使大型程式碼庫易於維護。Golang中沒有像地圖和過濾器這樣的“富有表現力”的功能。

“如果你有增加表現力的功能,通常會增加費用”
——羅伯派克


最近釋出的golang新標識:https://blog.golang.org/go-brand

入門

Golang由包組成。包main告訴Golang編譯器該程式被編譯為可執行檔案,而不是共享庫。它是應用程式的入口點。主包定義為:

package main

讓我們通過main.go 在Go lang工作區中建立一個檔案來編寫一個簡單的hello world示例

工作區

Go中的工作空間由環境變數GOPATH定義。

您編寫的任何程式碼都將寫在工作區內。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!

注意:要嘗試執行本文提到的程式碼,您可以使用 https://play.golang.org

變數

Go中的變數是明確宣告的。Go是一種靜態型別語言。這意味著在變數宣告時檢查變數型別。變數可以宣告為:

var a int

在這種情況下,該值將設定為0.使用以下語法宣告和初始化具有不同值的變數:

var a = 1

這裡變數自動指定為int。我們可以使用變數宣告的簡寫定義:

message := "hello world"

我們還可以在同一行中宣告多個變數:

var b,c int = 2,3

資料型別

與任何其他程式語言一樣,Golang支援各種不同的資料結構。讓我們探討一下:

NUMBER, STRING, AND BOOLEAN

一些INT型別的有int,int8,int16,int32,int64,
uint,uint8,uint16,uint32,uint64,uintptr ......

字串型別儲存一系列位元組。它用關鍵字string表示和宣告。

使用關鍵字bool儲存布林型別。

Golang還支援複雜的數字型別資料型別,可以用complex64complex128

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)

ARRAYS, SLICES, AND MAPS

陣列是相同資料型別的元素序列。陣列具有在宣告中定義的固定長度,因此不能進行擴充套件。陣列宣告為:

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

對於條件語句,我們可以使用if-else語句,如下例所示。確保花括號與條件位於同一行。

 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")
     }
 }

SWITCH CASE

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")
     }
 }

Looping

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/ 並檢視我們剛建立的包的文件。

GO中的一些內建包

FMT

該包實現了格式化的I / O功能。我們已經使用該包列印到stdout。

JSON

Golang中另一個有用的包是json包。這有助於編碼/解碼JSON。讓我們舉一個例子來編碼/解碼一些json:

encode

package main

 import (
     "encoding/json"
     "fmt"
 )

 func main() {
     mapA := map[string]int{"apple": 5, "lettuce": 7}
     mapB, _ := json.Marshal(mapA)
     fmt.Println(string(mapB))
 }

Decode

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

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

Defer總是在函式結束時執行。

在上面的例子中,我們使用panic()來恐慌地執行程式。正如您所注意到的,有一個延遲語句,它將使程式在程式執行結束時執行該行。當我們需要在函式結束時執行某些操作時,也可以使用Defer,例如關閉檔案。

併發

Golang在構建時考慮了併發性。Golang中的併發可以通過輕量級執行緒的Go例程來實現。

Go routine

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為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!

為什麼**GOLANG成功了?

Simplicity… — Rob-pike

Great!

我們瞭解了Golang的一些主要元件和功能。

  1. 變數,資料型別
  2. 陣列切片和Map
  3. 函式
  4. 迴圈和條件語句
  5. 指標
  6. 方法,結構和介面
  7. 錯誤處理
  8. 併發-Goroutines和channels

恭喜你,你現在對Go有了不錯的認識。

One of my most productive days was throwing away 1,000 lines of code.
--Ken Thompson

不要停在這裡。繼續前進。考慮一個小應用程式並開始構建。

相關文章