Go
引言
非常入門的一本書?準確來說是一篇GO的入門文章,用於學學英語和簡單瞭解GO還不錯,並且詞彙和語法都比較簡潔易懂。
話不多說,下面就來看看這篇文章的翻譯。
原文
https://www.freecodecamp.org/news/go-beginners-handbook/
1.介紹GO
它是由谷歌工程師建立的,設計這門語言的主要目標是:
- 提高專案的編譯和執行速度
- 入門門檻足夠低,但是也要避免過於簡單和低階。
- 具備可移植(編譯後的 Go 程式是二進位制檔案,不需要其他檔案就能執行,而且是跨平臺的,因此可以很容易地分發)
- 單調、穩定、可預測,犯錯的機會少 。
- 能充分發揮多執行緒優勢
2.如何開始?
在深入瞭解該語言的具體內容之前,您應該瞭解以下幾件事。
首先,https://go.dev 是該語言的主頁。
下面是首選的入門資源:
- 從 https://go.dev/doc/install 下載 Go 二進位制檔案(go 命令和其他相關工具)。
- 參考 Go 官方文件: https://go.dev/doc/
- 檢視所有 Go 軟體包: https://pkg.go.dev/
- 訪問 Go Playground: https://go.dev/play/
- ...
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
5.你好,世界
- 建立一個新的資料夾,比如下面的 hello。
- 建立一個
hello.go
的檔案,並且在檔案內寫入下面的內容:
package main
import "fmt"
func main() {
fmt.Println(" Hello, World!")
}
- 在檔案所在路徑,執行
go run hello.go
,如果結果如下說明執行成功:
xander@LAPTOP-47J243NL MINGW64 /e/adongstack/go/hello
$ go run hello.go
Hello, World!
下面來解釋下上面的入門程式:
- 每個 .go 檔案首先宣告它是哪個軟體包的一部分
- 一個軟體包可以由多個檔案組成,也可以只由一個檔案組成。一個程式可以包含多個軟體包。
main
函式是程式的入口點,也是可執行程式的標識。- 我們使用 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 是可移植的,因為本質編譯之後本質就是二進位制檔案,這樣每個人都可以按原樣執行程式,並且二進位制檔案已經打包供執行。
程式將在我們構建它的相同架構上執行。
我們可以使用 GOOS
和 GOARCH
環境變數為不同的架構建立不同的二進位制檔案:
GOOS = windows GOARCH = amd64 go build hello.go
這將為 64 位 Windows 機器建立一個 hello.exe 可執行檔案:
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
下:
當你使用 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”:
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 中會有相關提示。
這些錯誤資訊實際是編譯
如果您宣告一個變數,但沒有將其初始化為一個值,那麼它會自動分配一個值,這個值取決於變數的型別,例如整數為 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》 讀書筆記(下半部分)