【GO】《GO HANDBOOK》讀書筆記

發表於2024-02-20

Go

引言

非常入門的一本書?準確來說是一篇GO的入門文章,用於學學英語和簡單瞭解GO還不錯,並且詞彙和語法都比較簡潔易懂。

話不多說,下面就來看看這篇文章的翻譯。

原文

https://www.freecodecamp.org/news/go-beginners-handbook/

1.介紹GO

它是由谷歌工程師建立的,設計這門語言的主要目標是:

  • 提高專案的編譯和執行速度
  • 入門門檻足夠低,但是也要避免過於簡單和低階。
  • 具備可移植(編譯後的 Go 程式是二進位制檔案,不需要其他檔案就能執行,而且是跨平臺的,因此可以很容易地分發)
  • 單調、穩定、可預測,犯錯的機會少 。
  • 能充分發揮多執行緒優勢

2.如何開始?

在深入瞭解該語言的具體內容之前,您應該瞭解以下幾件事。

首先,https://go.dev 是該語言的主頁。

下面是首選的入門資源:

3.安裝 GO

安裝GO的教程網上一抓一大把,個人使用的是Window環境,Windows 環境下只需要安裝exe應用程式即可,簡單無腦就不演示了。

4.開發 IDE 安裝和配置

NOTE: you might have to open a new terminal before you can run the program, as the installer added the Go binaries folder to the path.

注意:由於安裝程式在路徑中新增了 Go 二進位制資料夾,執行程式前可能需要開啟一個新的終端。

在使用IDE之前,請先確保自己的環境變數可以正常使用GO。

本書使用的是 VS Code,具體可以閱讀此網站了解:Go with Visual Studio Code

[[【Go】Go in Visual Studio Code]]

VS Code安裝之後,需要安裝GO相關的擴充套件:marketplace.visualstudio.com/items?itemName=golang.go

image.png

5.你好,世界

  1. 建立一個新的資料夾,比如下面的 hello。

image.png

  1. 建立一個hello.go的檔案,並且在檔案內寫入下面的內容:
package main 

import "fmt" 

func main() { 
    fmt.Println(" Hello, World!") 
}
  1. 在檔案所在路徑,執行go run hello.go,如果結果如下說明執行成功:
xander@LAPTOP-47J243NL MINGW64 /e/adongstack/go/hello

$ go run hello.go
 Hello, World!

下面來解釋下上面的入門程式:

  1. 每個 .go 檔案首先宣告它是哪個軟體包的一部分
  2. 一個軟體包可以由多個檔案組成,也可以只由一個檔案組成。一個程式可以包含多個軟體包。
  3. main函式是程式的入口點,也是可執行程式的標識。
  4. 我們使用 import 關鍵字來匯入程式包。

fmt is a built-in package provided by Go that provides input/ output utility functions.

fmt 是 Go 提供的一個內建包,提供輸入/輸出實用功能。

GO官方擁有龐大的標準庫,從網路連線到數學、密碼、影像處理、檔案系統訪問等,我們都可以使用它。

Standard library - Go Packages

比如,有關fmt 這個包的文件介紹可以檢視這個地址瞭解:fmt package - fmt - Go Packages,根據文件,該函式功能是 "根據格式說明符進行格式化,並寫入標準輸出"。

我們使用 "點 "語法 fmt.Println()來指定該函式由該程式包提供。

當程式碼執行完主函式後,就沒有其他事情可做了,執行結束。

在main函式中,我們定義了fmt.Println(" Hello, World!") 這樣一串程式碼。

6.編譯並執行 Go 程式

本部分接著上一章節的入門程式介紹,解釋如何編譯並且執行go程式

go run hello.go

go run 工具首先編譯,然後執行指定的程式。

我們可以使用 go build 建立二進位制檔案:

go build hello.go

個人電腦上執行的結果如下:

PS E:\adongstack\go\hello> go build .\hello.go
PS E:\adongstack\go\hello> dir


    目錄: E:\adongstack\go\hello


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----         2023/9/19     13:33        1897472 hello.exe
-a----         2023/9/19     13:32             84 hello.go

Go 是可移植的,因為本質編譯之後本質就是二進位制檔案,這樣每個人都可以按原樣執行程式,並且二進位制檔案已經打包供執行。

程式將在我們構建它的相同架構上執行。

我們可以使用 GOOSGOARCH 環境變數為不同的架構建立不同的二進位制檔案:

GOOS = windows GOARCH = amd64 go build hello.go

這將為 64 位 Windows 機器建立一個 hello.exe 可執行檔案:

image.png

64 位 macOS(英特爾或蘋果)的設定為

GOOS = darwin GOARCH = amd64

Linux 為 :

GOOS = linux GOARCH = amd64

這是 Go 的最佳功能之一。

7.工作空間

Go有一個特別之處就是我們所說的工作空間。

工作區是 Go 的 "大本營"。

預設情況下,Go 會選擇 $ HOME/ go 路徑,因此你會在主目錄中看到一個 go 資料夾。

例如,當我在 VS Code 中載入 hello.go 檔案時,它提示我安裝 gopls 命令、Delve 偵錯程式 (dlv) 和 staticcheck linter

它們已自動安裝在 $ HOME/ go 下:

image.png

當你使用 go install 安裝軟體包時,它們將被儲存在這裡。

這就是我們所說的 GOPATH。

你可以更改 GOPATH 環境變數來改變 Go 安裝包的位置。

當你同時在不同的專案中工作,並希望隔離你使用的庫時,這一點非常有用。

8.深入語言

既然我們已經有了初步的概念,並執行了第一個 Hello, World! 程式,那麼我們就可以深入研究這門語言了。

這種語言沒有語義上重要的空白。

就像 C、C + +、Rust、Java 和 JavaScript。與 Python 不同的是,Python 的空白是有意義的,它被用來建立塊而不是大括號。

Go 非常重視縮排和視覺順序。安裝 Go 時,我們還可以使用 gofmt 命令列工具來格式化 Go 程式。

VS Code 在引擎蓋下使用該工具來格式化 Go 原始檔。

這是一個非常有意思的特性,語言建立者定義了規則,每個人都會使用這些規則。

這非常適合大型團隊的專案。

本書建議在VS Code 當中設定 “Format on Save” 以及 “Format on Paste”:

image.png

Go 中的註釋使用常見的 C / C + + / JavaScript / Java 語法:

9.變數

在 GO 裡面可以是用 var關鍵字定義變數:

var age = 20

var 關鍵字可以定義包級別變數:

package main 

import "fmt" 

var age = 20 

func main() { 
    fmt.Println(" Hello, World!") 
}

或者定義在函式內部:

package main 

import "fmt" 



func main() { 

    var age = 20 
    
    fmt.Println(" Hello, World!") 
}

在軟體包級別定義的變數在組成軟體包的所有檔案中都是可見的。

一個軟體包可以由多個檔案組成,只需建立另一個檔案,並在頂部使用相同的軟體包名稱即可。

在函式內部定義的變數,一個變數只能在函式中可見。

這使得 Go 判斷變數 age 的型別是 int。

我們稍後會看到更多關於型別的內容,但你應該知道有許多不同的型別,首先是 int、string 和 bool。

我們也可以宣告一個沒有現存值的變數,但在這種情況下,我們必須像這樣設定型別:

var age int 
var name string 
var done bool

當您知道變數值時,通常使用帶有 := 運算子的短變數宣告:

age := 10 
name := "Roger"

注意變數名稱是區分大小寫的,如果名稱較長,通常使用駝峰字母大寫,因此我們使用 carName 來表示汽車的名稱。

您可以使用賦值運算子 = 為變數賦值。

var age int 
age = 10 
age = 11

如果一個變數確定不會改變,可以使用const關鍵字定義:

const age = 10

你可以在一行定義多個變數:

var age, name = 10, "Roger" 

// or 

age, name := 10, "Roger"

如果定義了變數但是沒有使用,在VS Code 中會有相關提示。

image.png

這些錯誤資訊實際是編譯

image.png

如果您宣告一個變數,但沒有將其初始化為一個值,那麼它會自動分配一個值,這個值取決於變數的型別,例如整數為 0,字串為空字串。

10.基本型別

Go 的基本型別有

  • 整數(int、int8、int16、int32、rune、int64、uint、uintptr、uint8、uint16、uint64)
  • 浮點數(float32、float64),用於表示小數
  • 複數型別(complex64、complex128),用於數學運算
  • 位元組(byte),表示單個 ASCII 字元
  • 字串(string),一組位元組
  • 布林型別(bool),表示真或假

我們有很多不同的型別來表示interger,大部分時間你都會使用int,你可能會選擇一個更專業的型別來進行最佳化(剛開始學習時不需要考慮這些)。

int 型別針對 32位機器和64位的機器做了型別適配,

uint 是一個無符號的 int,如果知道數值不會是負數,就可以用它來加倍儲存數值。

11.字串

Strings 在 Go 語言中是一串位元組陣列。

我們可以使用下面的語法定義一串字串:

var name = "test"

值得注意的是,與其他語言不同,字串的定義只能使用雙引號,而不能使用單引號(比如 JS)。

可以使用len 函式獲取字串長度

len( name) // 4

您可以使用方括號訪問單個字元,同時傳遞您要獲取的字元的索引:

name[ 0] //" t" (indexes start at 0) 
name[ 1] //" e"

可以使用下面的語法像Python語言一樣對於字串進行“切片”:

name[ 0: 2] //" te" 
name[: 2] //" te" 
name[ 2:] //" st"

使用下面的語法可以複製一個字串:

var newstring = name[:]

您可以將一個字串賦值給一個新變數:

var first = "test" 
var second = first

Strings 是不可變變數,意味著你不能更新一個Strings,即使使用賦值運算子為第一項賦值,第二項的值仍然是 "test":

var first = "test" 

var second = first 

first = "another test" 

first //" another test" 
second //" test"

字串是引用型別,這意味著如果將字串傳遞給函式,複製的將是字串的引用,而不是其值。

但由於字串是不可變的,在這種情況下,與傳遞 int 等型別的字串在實際操作中並無太大區別。

可以使用 + 運算子連線兩個字串:

var first = "first" 

var second = "second" 

var word = first + " " + second //" first second"

Go 在字串包中提供了多個字串實用程式。

我們已經在 "Hello, World!"示例中看到了如何匯入包,下面介紹如何匯入字串:

以下是匯入字串的方法:

package main 

import ( "strings" )

12.陣列

陣列是由單一型別的專案組成的序列。

我們這樣定義一個陣列:

var myArray [3] string // 包含 3 個字串的陣列

你可以使用以下值初始化陣列:

var myArray = [3] string{" First", "Second", "Third"}

在這種情況下,你也可以讓 Go 來幫你計數:

var myArray = [...] string{" First", "Second", "Third"} 

注意,陣列只能包含相同型別的值。

陣列不能調整大小,必須在 Go 中明確定義陣列的長度。

陣列不能調整大小,必須在 Go 中明確定義陣列的長度。

這是陣列型別的一部分。此外,也不能使用變數來設定陣列的長度。

由於這種限制,在 Go 中很少直接使用陣列,而是使用 Slice(稍後會詳細介紹)。

注意 Slice 底層也是陣列實現的,所以瞭解陣列是基礎。

陣列可以透過方括號加下標值獲取陣列特定位置,然後針對特定位置設定新值。

myArray[ 0] // indexes start at 0 

myArray[ 1]

陣列也可以使用len() 函式獲取陣列長度:

陣列是值型別。這意味著可以複製陣列:

anotherArray := myArray

將陣列傳遞給函式,或從函式中返回陣列,都會建立原始陣列的副本

這與其他程式語言不同。

讓我們舉一個簡單的例子,在複製一個陣列項後,給它賦一個新值。

請看,複製並沒有改變:

var myArray = [3] string{" First", "Second", "Third"} 

myArrayCopy := myArray 

myArray[ 2] = "Another" 

myArray[ 2] //" Another"
myArrayCopy[ 2] //" Third"

請記住,你只能在陣列中新增單一型別的項,因此設定 myArray[ 2] = 2 會引發錯誤。

底層元素在記憶體中連續儲存。

低階元素持續儲存在記憶體中。

13.分片

分片是一種類似於陣列的資料結構,但它的大小可以改變。

切片使用陣列,是建立在陣列之上的一種抽象結構,它使切片更靈活、更有用(將陣列視為低階結構)。

如果你知道需要對切片執行操作,你可以要求它擁有比最初需要的更大容量,這樣當你需要更多空間時,空間就會隨時可用(而不是找到切片並將其移動到一個有更多空間的新記憶體位置,然後透過垃圾回收處理舊位置)。

我們可以為 make() 新增第三個引數來指定容量:

newSlice := make([] string, 0, 10) // an empty slice with capacity 10

多個切片可以使用同一個陣列作為底層陣列:

myArray := [3] string{" First", "Second", "Third"} mySlice = myArray[:]

與字串一樣,使用該語法可以獲取片段的一部分:

mySlice := [] string{" First", "Second", "Third"} newSlice := mySlice[: 2] // get the first 2 items newSlice2 := mySlice[ 2:] // ignore the first 2 items newSlice3 := mySlice[ 1: 3] // new slice with items in position 1-2

14.Maps

map 是go語言的常用資料型別。

agesMap := make( map[ string] int)

您無需設定地圖可容納的物品數量。您可以透過這種方式向map新增新內容:

agesMap[" flavio"] = 39

您也可以使用以下語法直接用值初始化對映:

agesMap := map[ string] int{" flavio": 39}

透過下面的語法可以獲取map的key的值:

age := agesMap[" flavio"]

使用delete() 函式可以刪除Map中對應的Key。

delete( agesMap, "flavio")

15.迴圈

Go 的迴圈語句關鍵字也是for:

for i := 0; i < 10; i + + { 

    fmt.Println( i) 
}

我們首先初始化一個迴圈變數,然後設定每次迭代時要檢查的條件,以決定迴圈是否應該結束,最後在每次迭代結束時執行 post 語句,在本例中,該語句會遞增 i。

i++ 使 i 變數遞增。

< 運算子用於將 i 與數字 10 進行比較,並返回 true 或 false,以決定是否執行迴圈體。

與 C 或 JavaScript 等其他語言不同,我們不需要在該程式碼塊周圍使用括號。

此外還需要注意,Go 沒有 while這樣的迴圈語法,如果想要實現類似的功能,可以使用for進行模擬。

i := 0 for i < 10 { 

    fmt.Println( i) i + + 
    
}

此外可以透過break語句跳出迴圈。

for { 
    fmt.Println( i) 
     
     if i < 10 { 
         break 
     } 
     i ++ 
}

我在迴圈體中使用了 if 語句,但我們還沒有看到條件!我們下一步再做。

現在我要介紹的是範圍。

我們可以使用 for 語法遍歷陣列:

numbers := [] int{ 1, 2, 3} 

for i, num := range numbers { 
    fmt.Printf("% d: %d\ n", i, num) 
} 

// 0: 1 

// 1: 2 

// 2: 3
注:我使用了 fmt.Printf(),它允許我們使用表示十進位制整數的 %d 和表示新增行結束符的 \n 來向終端列印任何值。

當不需要使用index時,使用這種語法很常見:

for _, num := range numbers { 
    //... 
}

使用表示 "忽略這個 "的特殊 _ 字元,以避免 Go 編譯器出現 "你沒有使用 i 變數!"的錯誤。

16.條件式

條件語句的語法和其他語言類似:

if age < 12 { // child 
} else if age < 18 { 
// teen 

} else { 

// adult 

}

和其他語言一樣,在if或者else程式碼塊中定義變數,變數的可見範圍等同於程式碼塊的範圍。

如果要使用多個不同的 if 語句來檢查一個條件,最好使用 switch

switch age 
{ 
case 0: fmt.Println(" Zero years old") 
case 1: fmt.Println(" One year old") 
case 2: fmt.Println(" Two years old") 
case 3: fmt.Println(" Three years old") 
case 4: fmt.Println(" Four years old") 
default: fmt.Println( i + " years old") 
}

與 C、JavaScript 和其他語言相比,您不需要在每種情況後都有一個分隔符。

17.運算子

到目前為止,我們在程式碼示例中使用了一些運算子,如 =、:= 和 <。

我們使用賦值運算子 = 和 := 來宣告和初始化變數:

var a = 1 

b := 1

我們有比較運算子 == 和 != ,它們接受 2 個引數並返回布林值。

var num = 1 

num == 1 // true 
num != 1 // false

當然還有下面的內容:

var num = 1 

num > 1 // false 
num >= 1 // true 
num < 1 // false 
num <= 1 // true

我們有二進位制(需要兩個引數)算術運算子,如 + - * / %

1 + 1 // 2 
1 - 1 // 0 
1 * 2 // 2 
2 / 2 // 1 
2 % 2 // 0

+ 運算子可以連線字串:

"a" + "b" //" ab"

我們有一元運算子 ++-- 來遞增或遞減數字:

var num = 1 
num++ // num == 2 
num-- // num == 1

請注意,與 C 或 JavaScript 不同的是,我們不能像 ++num 那樣將它們前置到數字上。

此外,該操作不會返回任何值。

我們有布林運算子幫助我們根據真假值做出決定:&&||!

true && true // true 
true && false // false 
true || false // true 
false || false // false 
!true // false 
!false // true

18.結構

結構體是一種包含一個或多個變數的型別。它就像是變數的集合。我們稱之為欄位。它們可以有不同的型別。

下面是定義結構體的程式碼:

type Person struct { 
    Name string 
    Age int 
}

請注意,我使用了大寫字母作為欄位名稱,否則這些欄位將成為軟體包的私有欄位,當您將結構體傳遞給另一個軟體包提供的函式(如我們用於處理 JSON 或資料庫的函式)時,就無法訪問這些欄位。

定義結構體後,我們就可以用該型別初始化變數:

flavio := Person{" Flavio", 39}

可以使用下面的方式獲取結構體的欄位資料:

flavio.Age // 39 

flavio.Name //" Flavio"

結構體非常有用,因為它可以將不相關的資料分組,並將其傳遞給函式或從函式中傳遞出來,還可以儲存在片段中,等等。

一旦定義,結構體就是一種類似 int 或字串的型別,這意味著你也可以在其他結構體內部使用它:

type FullName struct { 

FirstName string 
LastName string 

} 

type Person struct { 

Name FullName 
Age int 

}

19.Functions

函式是一個程式碼塊,它被賦予一個名稱,幷包含一些指令。

在 "你好,世界!"示例中,我們建立了一個 main 函式,它是程式的入口。

package main import "fmt" func main() { fmt.Println(" Hello, World!") }

通常,我們用自定義名稱定義函式:

func doSomething() { 

然後透過下面的方式呼叫函式:

doSomething()

函式可以接受引數,我們必須這樣設定引數的型別:

func doSomething( a int, b int) { 

} 

doSomething( 1, 2)

a 和 b 是函式內部引數的名稱。

函式可以返回一個值,就像下面這樣:

func sumTwoNumbers( a int, b int) int {

    return a + b 

} 

result := sumTwoNumbers( 1, 2)
請注意,這裡指定了返回值型別

GO 語言的return 可以返回超過一個值:

func performOperations( a int, b int) (int, int) { 
    return a + b, a - b 
} 

sum, diff := performOperations( 1, 2)

這很有趣,因為許多語言只允許一個返回值。函式內部定義的任何變數都是函式的區域性變數。

函式也可以接受數量不限的引數,在這種情況下,我們稱之為變數函式:

func sumNumbers( numbers ... int) int { 
    sum := 0 
    for _, number := range numbers 
    { 
        sum + = number 
    } 
    return sum 
} 

total := sumNumbers( 1, 2, 3, 4)

20.指標

GO語言支援使用指標,假設使用下面的變數定義:

age := 20

有了變數指標後,就可以使用 * 運算子獲取其指向的值:

age := 20 
ageptr = &age 
agevalue = *ageptr

這在呼叫函式並將變數作為引數傳遞時非常有用。

Go 預設將變數值複製到函式內部,因此不會改變 age 的值:

func increment(a int) { 

    a = a + 1 
                      
} 

func main() { 
    age := 20 
    increment(age) // age is still 20 
    
}

為此,您可以使用指標:

func increment( a *int) { 
    
    *a = *a + 1
    
} 

func main() { 

age := 20 
             
 increment(& age) // age is now 21 
 
 }

21.函式

一個函式可以分配給一個結構體,在這種情況下,我們稱之為方法。

type Person struct { 

    Name string Age int 
    
} 

func (p Person) Speak() { 

    fmt.Println(" Hello from " + p.Name) 
    
} 


func main() { 

    flavio := Person{ 
        Age: 39, 
        Name: "Flavio"
    } 
    
    flavio.Speak() 

}

方法引數可以指定指標型別或者值型別,這將是一個指標接收器,用於接收指向結構例項的指標:

func (p *Person) Speak() { fmt.Println(" Hello from " + p.Name) }

22.介面

介面是一種定義了一個或多個方法簽名的型別。方法沒有實現,只有簽名:名稱、引數型別和返回值型別。類似於這樣:

type Speaker interface { Speak() }

現在,你可以讓函式接受任何型別,並實現介面定義的所有方法:

func SaySomething( s Speaker) { 
    s.Speak() 
}

我們可以將實現這些方法的任何結構傳遞給它:

type Speaker interface { 
    Speak() 
    
} 

type Person struct { 
    Name string Age int 
} 

func (p Person) Speak() { 
    fmt.Println(" Hello from " + p.Name) 
} 

func SaySomething( s Speaker) { 
    s.Speak() 
}

func main() { 

    flavio := Person{ Age: 39, Name: "Flavio"} 
    SaySomething( flavio) 
}

23. 下一步行動

本手冊介紹 Go 程式語言。除了這些基礎知識,現在還有很多東西需要學習。

垃圾回收、錯誤處理、併發和網路、檔案系統 API 等等。學習無止境。

我的建議是,選擇一個你想構建的程式,然後開始學習你需要的東西。這會很有趣,也很有收穫。

相關

【Linux】《The Command Line Handbook》 讀書筆記(上半部分)
【Linux】《The Command Line Handbook》 讀書筆記(下半部分)

相關文章