Go 快速指南
Go - 概述
Go 是一種通用程式語言,其設計理念側重於系統程式設計。它最初由 Robert Griesemer、Rob Pike 和 Ken Thompson 於 2007 年在 Google 開發。它是一種強型別靜態語言,提供內建的垃圾回收支援,並支援併發程式設計。
程式使用包構建,以便有效管理依賴項。Go 程式設計實現使用傳統的編譯和連結模型來生成可執行二進位制檔案。Go 程式語言於 2009 年 11 月釋出,並用於 Google 的一些生產系統中。
Go 程式語言的特性
Go 程式語言最重要的特性如下:
支援類似於動態語言的環境採用模式。例如,型別推斷(x := 0 是 int 型別變數 x 的有效宣告)
編譯速度快。
內建併發支援:輕量級程序(透過 go 協程)、通道、select 語句。
Go 程式簡單、簡潔且安全。
支援介面和型別嵌入。
生成無需外部依賴的靜態連結本地二進位制檔案。
故意排除的特性
為了保持語言的簡單和簡潔,Go 中省略了其他類似語言中常見的以下特性:
不支援型別繼承
不支援方法或運算子過載
不支援包之間的迴圈依賴
不支援指標運算
不支援斷言
不支援泛型程式設計
Go 程式
Go 程式的長度可以從 3 行到數百萬行不等,它應該寫入一個或多個副檔名為“.go”的文字檔案中。例如,hello.go。
您可以使用“vi”、“vim”或任何其他文字編輯器將 Go 程式寫入檔案。
Go - 環境設定
本地環境設定
如果您仍然希望為 Go 程式語言設定您的環境,則需要您的計算機上有以下兩個軟體:
- 文字編輯器
- Go 編譯器
文字編輯器
您需要一個文字編輯器來鍵入您的程式。文字編輯器的示例包括 Windows 記事本、OS Edit 命令、Brief、Epsilon、EMACS 以及 vim 或 vi。
文字編輯器的名稱和版本在不同的作業系統上可能有所不同。例如,Notepad 用於 Windows,而 vim 或 vi 用於 Windows 以及 Linux 或 UNIX。
您使用文字編輯器建立的檔案稱為原始檔。它們包含程式原始碼。Go 程式的原始檔通常以副檔名“.go”命名。
在開始程式設計之前,請確保您已準備好文字編輯器,並且您有足夠的經驗來編寫計算機程式,將其儲存到檔案中,編譯它,最後執行它。
Go 編譯器
原始檔中編寫的原始碼是程式的人類可讀原始碼。它需要被編譯並轉換成機器語言,以便您的 CPU 能夠根據給定的指令實際執行程式。Go 程式語言編譯器將原始碼編譯成最終的可執行程式。
Go 發行版作為 FreeBSD(8 版及以上)、Linux、Mac OS X(Snow Leopard 及以上)和 Windows 作業系統的二進位制可安裝檔案提供,這些作業系統具有 32 位 (386) 和 64 位 (amd64) x86 處理器架構。
下一節將說明如何在各種作業系統上安裝 Go 二進位制發行版。
下載 Go 存檔
從Go 下載下載最新版本的 Go 可安裝存檔檔案。本教程中使用以下版本:go1.4.windows-amd64.msi。
它被複制到 C:\>go 資料夾中。
| 作業系統 | 存檔名稱 |
|---|---|
| Windows | go1.4.windows-amd64.msi |
| Linux | go1.4.linux-amd64.tar.gz |
| Mac | go1.4.darwin-amd64-osx10.8.pkg |
| FreeBSD | go1.4.freebsd-amd64.tar.gz |
在 UNIX/Linux/Mac OS X 和 FreeBSD 上安裝
將下載的存檔解壓縮到 /usr/local 資料夾中,在 /usr/local/go 中建立一個 Go 樹。例如:
tar -C /usr/local -xzf go1.4.linux-amd64.tar.gz
將 /usr/local/go/bin 新增到 PATH 環境變數。
| 作業系統 | 輸出 |
|---|---|
| Linux | export PATH = $PATH:/usr/local/go/bin |
| Mac | export PATH = $PATH:/usr/local/go/bin |
| FreeBSD | export PATH = $PATH:/usr/local/go/bin |
在 Windows 上安裝
使用 MSI 檔案並按照提示安裝 Go 工具。預設情況下,安裝程式在 c:\Go 中使用 Go 發行版。安裝程式應在 Window 的 PATH 環境變數中設定 c:\Go\bin 目錄。重新啟動任何開啟的命令提示符以使更改生效。
驗證安裝
在C:\>Go_WorkSpace中建立一個名為 test.go 的 go 檔案。
檔案:test.go
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
現在執行 test.go 以檢視結果:
C:\Go_WorkSpace>go run test.go
輸出
Hello, World!
Go - 程式結構
在我們學習 Go 程式語言的基本構建塊之前,讓我們首先討論 Go 程式的最低限度結構,以便我們可以在後續章節中將其作為參考。
Hello World 示例
Go 程式基本上包含以下部分:
- 包宣告
- 匯入包
- 函式
- 變數
- 語句和表示式
- 註釋
讓我們來看一個簡單的程式碼,它將列印“Hello World”字樣:
package main
import "fmt"
func main() {
/* This is my first sample program. */
fmt.Println("Hello, World!")
}
讓我們看一下上述程式的各個部分:
程式的第一行 package main 定義了該程式應該所在的包名稱。這是一個強制性語句,因為 Go 程式在包中執行。main 包是執行程式的起點。每個包都有與其關聯的路徑和名稱。
下一行 import "fmt" 是一個預處理器命令,它告訴 Go 編譯器包含位於 fmt 包中的檔案。
下一行 func main() 是程式執行開始的 main 函式。
下一行 /*...*/ 被編譯器忽略,它用於在程式中添加註釋。註釋也使用 // 表示,類似於 Java 或 C++ 註釋。
下一行 fmt.Println(...) 是 Go 中的另一個函式,它導致訊息“Hello, World!”顯示在螢幕上。這裡 fmt 包匯出了 Println 方法,用於在螢幕上顯示訊息。
注意 Println 方法的大寫 P。在 Go 語言中,如果名稱以大寫字母開頭,則該名稱會被匯出。匯出意味著函式或變數/常量可供相應包的匯入者訪問。
執行 Go 程式
讓我們討論如何將原始碼儲存到檔案中,編譯它,最後執行程式。請按照以下步驟操作:
開啟文字編輯器並新增上述程式碼。
將檔案儲存為 hello.go
開啟命令提示符。
轉到儲存檔案的目錄。
鍵入 go run hello.go 並按 Enter 鍵執行您的程式碼。
如果您的程式碼中沒有錯誤,則您將在螢幕上看到列印的 “Hello World!”。
$ go run hello.go Hello, World!
確保 Go 編譯器位於您的路徑中,並且您在包含原始檔 hello.go 的目錄中執行它。
Go - 基本語法
我們在上一章討論了 Go 程式的基本結構。現在,理解 Go 程式語言的其他基本構建塊將變得更容易。
Go 中的標記
Go 程式由各種標記組成。標記要麼是關鍵字、識別符號、常量、字串文字或符號。例如,以下 Go 語句包含六個標記:
fmt.Println("Hello, World!")
各個標記是:
fmt . Println ( "Hello, World!" )
換行符
在 Go 程式中,換行符鍵是語句終止符。也就是說,單個語句不需要像 C 中那樣的特殊分隔符“;”。Go 編譯器在內部放置“;”作為語句終止符,以指示一個邏輯實體的結束。
例如,看一下以下語句:
fmt.Println("Hello, World!")
fmt.Println("I am in Go Programming World!")
註釋
註釋就像 Go 程式中的幫助文字,它們會被編譯器忽略。它們以 /* 開頭,以 */ 結尾,如下所示:
/* my first program in Go */
您不能在註釋中巢狀註釋,它們不會出現在字串或字元文字中。
識別符號
Go 識別符號是用於標識變數、函式或任何其他使用者定義項的名稱。識別符號以字母 A 到 Z 或 a 到 z 或下劃線 _ 開頭,後跟零個或多個字母、下劃線和數字 (0 到 9)。
識別符號 = 字母 { 字母 | unicode_數字 }。
Go 不允許在識別符號中使用標點符號,例如 @、$ 和 %。Go 是一種區分大小寫的程式語言。因此,Manpower 和 manpower 在 Go 中是兩個不同的識別符號。以下是一些可接受的識別符號示例:
mahesh kumar abc move_name a_123 myname50 _temp j a23b9 retVal
關鍵字
以下列表顯示了 Go 中的保留字。這些保留字不能用作常量或變數或任何其他識別符號名稱。
| break | default | func | interface | select |
| case | defer | Go | map | struct |
| chan | else | goto | package | switch |
| const | fallthrough | if | range | type |
| continue | for | import | return | var |
Go 中的空白字元
在 Go 語言中,空白符指的是空格、製表符、換行符和註釋。僅包含空白符(可能還有註釋)的行被稱為空行,Go 編譯器會完全忽略它。
空白符用於分隔語句的不同部分,使編譯器能夠識別語句中一個元素(例如 int)的結束位置和下一個元素的開始位置。
var age int;
因此,在下面的語句中:
fruit = apples + oranges; // get the total fruit
必須在 int 和 age 之間至少有一個空白符(通常是空格),以便編譯器能夠區分它們。另一方面,在下面的語句中:
Go - 資料型別
fruit 和 = 之間,以及 = 和 apples 之間不需要空白符,儘管為了可讀性,您可以隨意新增一些空白符。
在 Go 程式語言中,資料型別指的是一個廣泛的系統,用於宣告不同型別的變數或函式。變數的型別決定了它在儲存中佔據的空間大小以及如何解釋儲存的位模式。
| Go 中的型別可以分類如下: | 序號 |
|---|---|
| 1 | 型別和描述 布林型別 |
| 2 | 它們是布林型別,包含兩個預定義的常量:(a)true (b)false 數值型別 |
| 3 | 它們也是算術型別,表示程式中的(a)整數型別或(b)浮點數。 字串型別 |
| 4 | 字串型別表示字串值的集合。它的值是一系列位元組。字串是不可變型別,一旦建立,就不能更改字串的內容。預宣告的字串型別是 string。 派生型別 |
它們包括:(a)指標型別,(b)陣列型別,(c)結構體型別,(d)聯合型別,(e)函式型別,(f)切片型別,(g)介面型別,(h)對映型別,(i)通道型別
陣列型別和結構體型別統稱為**聚合型別**。函式的型別指定了具有相同引數和結果型別的所有函式的集合。我們將在下一節討論基本型別,其他型別將在後續章節中介紹。
整數型別
| Go 中的型別可以分類如下: | 序號 |
|---|---|
| 1 | 預定義的與體系結構無關的整數型別如下: uint8 |
| 2 | 無符號 8 位整數(0 到 255) uint16 |
| 3 | 無符號 16 位整數(0 到 65535) uint32 |
| 4 | 無符號 32 位整數(0 到 4294967295) uint64 |
| 5 | 無符號 64 位整數(0 到 18446744073709551615) int8 |
| 6 | 有符號 8 位整數(-128 到 127) int16 |
| 7 | 有符號 16 位整數(-32768 到 32767) int32 |
| 8 | 有符號 32 位整數(-2147483648 到 2147483647) int64 |
有符號 64 位整數(-9223372036854775808 到 9223372036854775807)
浮點型別
| Go 中的型別可以分類如下: | 序號 |
|---|---|
| 1 | 預定義的與體系結構無關的浮點型別如下: float32 |
| 2 | IEEE-754 32 位浮點數 float64 |
| 3 | IEEE-754 64 位浮點數 complex64 |
| 4 | 實部和虛部為 float32 的複數 complex128 |
實部和虛部為 float64 的複數
n 位整數的值為 n 位,並使用二進位制補碼算術運算表示。
其他數值型別
| Go 中的型別可以分類如下: | 序號 |
|---|---|
| 1 | 還有一組大小取決於實現的數值型別: byte |
| 2 | 與 uint8 相同 rune |
| 3 | 與 int32 相同 uint |
| 4 | 32 位或 64 位 int |
| 5 | 與 uint 大小相同 uintptr |
Go - 變數
無符號整數,用於儲存指標值的未解釋位
變數只不過是程式可以操作的儲存區域的名稱。Go 中的每個變數都有一個特定型別,該型別決定變數記憶體的大小和佈局、儲存在該記憶體中的值的範圍以及可以應用於變數的操作集。
| 變數名可以由字母、數字和下劃線組成。它必須以字母或下劃線開頭。大小寫字母是不同的,因為 Go 區分大小寫。基於上一章解釋的基本型別,將存在以下基本變數型別: | 序號 |
|---|---|
| 1 |
還有一組大小取決於實現的數值型別: 型別和描述 |
| 2 |
32 位或 64 位 通常是一個單位元組(一個位元組)。這是一個位元組型別。 |
| 3 | 預定義的與體系結構無關的浮點型別如下: 機器最自然的整數大小。 |
單精度浮點數。
Go 程式語言還允許定義各種其他型別的變數,例如列舉、指標、陣列、結構體和聯合體,我們將在後續章節中討論這些變數。在本節中,我們將只關注基本變數型別。
Go 中的變數定義
var variable_list optional_data_type;
變數定義告訴編譯器在哪裡以及為變數建立多少儲存空間。變數定義指定資料型別,幷包含該型別的一個或多個變數列表,如下所示:
var i, j, k int; var c, ch byte; var f, salary float32; d = 42;
這裡,**optional_data_type** 是一個有效的 Go 資料型別,包括 byte、int、float32、complex64、boolean 或任何使用者定義的物件等,而 **variable_list** 可以包含一個或多個用逗號分隔的識別符號名稱。這裡顯示了一些有效的宣告:
語句 **“var i, j, k;”** 宣告並定義了變數 i、j 和 k;它指示編譯器建立名為 i、j 和 k 的 int 型別變數。
variable_name = value;
可以在變數宣告中初始化(賦值初始值)變數。變數的型別由編譯器根據傳遞給它的值自動判斷。初始化程式由一個等號後跟一個常量表達式組成,如下所示:
d = 3, f = 5; // declaration of d and f. Here d and f are int
例如:
對於沒有初始化程式的定義:具有靜態儲存期限的變數隱式初始化為 nil(所有位元組的值均為 0);所有其他變數的初始值都是其資料型別的零值。
Go 中的靜態型別宣告
靜態型別變數宣告向編譯器保證存在一個具有給定型別和名稱的可用變數,以便編譯器可以在不需要變數的完整詳細資訊的情況下繼續進行進一步的編譯。變數宣告只在編譯時才有意義,編譯器在程式連結時需要實際的變數宣告。
示例
package main
import "fmt"
func main() {
var x float64
x = 20.0
fmt.Println(x)
fmt.Printf("x is of type %T\n", x)
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
20 x is of type float64
編譯並執行上述程式碼後,將產生以下結果:
Go 中的動態型別宣告/型別推斷
靜態型別變數宣告向編譯器保證存在一個具有給定型別和名稱的可用變數,以便編譯器可以在不需要變數的完整詳細資訊的情況下繼續進行進一步的編譯。變數宣告只在編譯時才有意義,編譯器在程式連結時需要實際的變數宣告。
動態型別變數宣告要求編譯器根據傳遞給它的值來解釋變數的型別。編譯器不需要變數作為必要要求靜態地具有型別。
package main
import "fmt"
func main() {
var x float64 = 20.0
y := 42
fmt.Println(x)
fmt.Println(y)
fmt.Printf("x is of type %T\n", x)
fmt.Printf("y is of type %T\n", y)
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
20 42 x is of type float64 y is of type int
嘗試以下示例,其中變數未宣告任何型別。請注意,在型別推斷的情況下,我們使用 := 運算子初始化變數 **y**,而使用 = 運算子初始化 **x**。
Go 中的混合變數宣告
靜態型別變數宣告向編譯器保證存在一個具有給定型別和名稱的可用變數,以便編譯器可以在不需要變數的完整詳細資訊的情況下繼續進行進一步的編譯。變數宣告只在編譯時才有意義,編譯器在程式連結時需要實際的變數宣告。
package main
import "fmt"
func main() {
var a, b, c = 3, 4, "foo"
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Printf("a is of type %T\n", a)
fmt.Printf("b is of type %T\n", b)
fmt.Printf("c is of type %T\n", c)
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
3 4 foo a is of type int b is of type int c is of type string
可以使用型別推斷在一行中宣告不同型別的變數。
Go 中的左值和右值
Go 中有兩種表示式:
**左值** - 指向記憶體位置的表示式稱為“左值”表示式。左值可以出現在賦值的左側或右側。
**右值** - 右值指的是儲存在記憶體某個地址處的資料值。右值是一個不能為其賦值的表示式,這意味著右值可以出現在賦值的右側,但不能出現在左側。
變數是左值,因此可以出現在賦值的左側。數字文字是右值,因此不能賦值,不能出現在左側。
x = 20.0
以下語句有效:
10 = 20
Go - 常量
以下語句無效。它將生成編譯時錯誤:
常量是指程式在其執行期間可能不會更改的固定值。這些固定值也稱為**字面量**。
常量可以是任何基本資料型別,例如 _整數常量、浮點常量、字元常量或字串文字_。還有列舉常量。
常量與常規變數的處理方式相同,只是它們的 值在其定義後不能修改。
整數文字
整數文字可以是十進位制、八進位制或十六進位制常量。字首指定基數或基:十六進位制為 0x 或 0X,八進位制為 0,十進位制為無。
整數文字也可以有一個字尾,該字尾是 U 和 L 的組合,分別表示無符號和長整型。字尾可以是大寫或小寫,並且可以按任意順序排列。
212 /* Legal */ 215u /* Legal */ 0xFeeL /* Legal */ 078 /* Illegal: 8 is not an octal digit */ 032UU /* Illegal: cannot repeat a suffix */
以下是一些整數文字示例:
85 /* decimal */ 0213 /* octal */ 0x4b /* hexadecimal */ 30 /* int */ 30u /* unsigned int */ 30l /* long */ 30ul /* unsigned long */
以下是各種型別整數文字的其他示例:
浮點文字
浮點文字具有整數部分、小數點、小數部分和指數部分。您可以使用十進位制形式或指數形式表示浮點文字。
使用十進位制形式表示時,必須包含小數點、指數或兩者兼而有之;使用指數形式表示時,必須包含整數部分、小數部分或兩者兼而有之。帶符號的指數由 e 或 E 引入。
3.14159 /* Legal */ 314159E-5L /* Legal */ 510E /* Illegal: incomplete exponent */ 210f /* Illegal: no decimal or exponent */ .e55 /* Illegal: missing integer or fraction */
以下是一些浮點文字示例:
轉義序列
| 當某些字元前面帶有反斜槓時,它們在 Go 中將具有特殊含義。這些被稱為轉義序列程式碼,用於表示換行符(\n)、製表符(\t)、退格符等。這裡列出了一些這樣的轉義序列程式碼: | 轉義序列 |
|---|---|
| \\ | 含義 |
| \' | \ 字元 |
| \" | ' 字元 |
| \? | " 字元 |
| ? 字元 | \a |
| 警報或鈴聲 | \b |
| 退格 | \f |
| 換頁 | \n |
| 換行 | \r |
| 回車 | \t |
| 水平製表符 | \v |
| 垂直製表符 | \ooo |
| 一位到三位八進位制數 | \xhh . . . |
一位或多位十六進位制數
package main
import "fmt"
func main() {
fmt.Printf("Hello\tWorld!")
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
Hello World!
以下示例顯示如何在程式中使用 **\t**:
Go 中的字串文字
字串文字或常量用雙引號 "" 括起來。字串包含類似於字元文字的字元:普通字元、轉義序列和通用字元。
您可以使用字串文字將長行分成多行,並使用空格將它們分隔開。
"hello, dear" "hello, \ dear" "hello, " "d" "ear"
以下是一些字串文字示例。所有三種形式都是相同的字串。
const 關鍵字
const variable type = value;
您可以使用 **const** 字首宣告具有特定型別的常量,如下所示:
package main
import "fmt"
func main() {
const LENGTH int = 10
const WIDTH int = 5
var area int
area = LENGTH * WIDTH
fmt.Printf("value of area : %d", area)
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
value of area : 50
注意,使用大寫字母定義常量是良好的程式設計習慣。
Go - 運算子
運算子是一個符號,它告訴編譯器執行特定的數學或邏輯運算。Go語言富含內建運算子,並提供以下型別的運算子:
- 算術運算子
- 關係運算符
- 邏輯運算子
- 位運算子
- 賦值運算子
- 其他運算子
本教程將逐一解釋算術、關係、邏輯、位、賦值和其他運算子。
算術運算子
下表顯示了Go語言支援的所有算術運算子。假設變數A為10,變數B為20,則:
| 運算子 | 描述 | 靜態型別變數宣告向編譯器保證存在一個具有給定型別和名稱的可用變數,以便編譯器可以在不需要變數的完整詳細資訊的情況下繼續進行進一步的編譯。變數宣告只在編譯時才有意義,編譯器在程式連結時需要實際的變數宣告。 |
|---|---|---|
| + | 將兩個運算元相加 | A + B 結果為 30 |
| - | 從第一個運算元中減去第二個運算元 | A - B 結果為 -10 |
| * | 將兩個運算元相乘 | A * B 結果為 200 |
| / | 將分子除以分母。 | B / A 結果為 2 |
| % | 模運算子;給出整數除法後的餘數。 | B % A 結果為 0 |
| ++ | 自增運算子。它將整數值增加一。 | A++ 結果為 11 |
| -- | 自減運算子。它將整數值減少一。 | A-- 結果為 9 |
關係運算符
下表列出了Go語言支援的所有關係運算符。假設變數A為10,變數B為20,則:
| 運算子 | 描述 | 靜態型別變數宣告向編譯器保證存在一個具有給定型別和名稱的可用變數,以便編譯器可以在不需要變數的完整詳細資訊的情況下繼續進行進一步的編譯。變數宣告只在編譯時才有意義,編譯器在程式連結時需要實際的變數宣告。 |
|---|---|---|
| == | 檢查兩個運算元的值是否相等;如果相等,則條件為真。 | (A == B) 為假。 |
| != | 檢查兩個運算元的值是否相等;如果不相等,則條件為真。 | (A != B) 為真。 |
| > | 檢查左運算元的值是否大於右運算元的值;如果大於,則條件為真。 | (A > B) 為假。 |
| < | 檢查左運算元的值是否小於右運算元的值;如果小於,則條件為真。 | (A < B) 為真。 |
| >= | 檢查左運算元的值是否大於或等於右運算元的值;如果大於或等於,則條件為真。 | (A >= B) 為假。 |
| <= | 檢查左運算元的值是否小於或等於右運算元的值;如果小於或等於,則條件為真。 | (A <= B) 為真。 |
邏輯運算子
下表列出了Go語言支援的所有邏輯運算子。假設變數A為1,變數B為0,則:
| 運算子 | 描述 | 靜態型別變數宣告向編譯器保證存在一個具有給定型別和名稱的可用變數,以便編譯器可以在不需要變數的完整詳細資訊的情況下繼續進行進一步的編譯。變數宣告只在編譯時才有意義,編譯器在程式連結時需要實際的變數宣告。 |
|---|---|---|
| && | 稱為邏輯與運算子。如果兩個運算元均非零,則條件為真。 | (A && B) 為假。 |
| || | 稱為邏輯或運算子。如果兩個運算元中任何一個非零,則條件為真。 | (A || B) 為真。 |
| ! | 稱為邏輯非運算子。用於反轉其運算元的邏輯狀態。如果條件為真,則邏輯非運算子將使其為假。 | !(A && B) 為真。 |
下表顯示了Go語言支援的所有邏輯運算子。假設變數A為真,變數B為假,則:
| 運算子 | 描述 | 靜態型別變數宣告向編譯器保證存在一個具有給定型別和名稱的可用變數,以便編譯器可以在不需要變數的完整詳細資訊的情況下繼續進行進一步的編譯。變數宣告只在編譯時才有意義,編譯器在程式連結時需要實際的變數宣告。 |
|---|---|---|
| && | 稱為邏輯與運算子。如果兩個運算元都為假,則條件為假。 | (A && B) 為假。 |
| || | 稱為邏輯或運算子。如果兩個運算元中任何一個是真,則條件為真。 | (A || B) 為真。 |
| ! | 稱為邏輯非運算子。用於反轉其運算元的邏輯狀態。如果條件為真,則邏輯非運算子將使其為假。 | !(A && B) 為真。 |
位運算子
位運算子作用於位並執行逐位運算。&,| 和 ^ 的真值表如下:
| p | q | p & q | p | q | p ^ q |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 1 | 1 |
| 1 | 1 | 1 | 1 | 0 |
| 1 | 0 | 0 | 1 | 1 |
假設 A = 60;和 B = 13。以二進位制格式,它們將如下所示:
A = 0011 1100
B = 0000 1101
-----------------
A&B = 0000 1100
A|B = 0011 1101
A^B = 0011 0001
~A = 1100 0011
下表列出了C語言支援的位運算子。假設變數A為60,變數B為13,則:
| 運算子 | 描述 | 靜態型別變數宣告向編譯器保證存在一個具有給定型別和名稱的可用變數,以便編譯器可以在不需要變數的完整詳細資訊的情況下繼續進行進一步的編譯。變數宣告只在編譯時才有意義,編譯器在程式連結時需要實際的變數宣告。 |
|---|---|---|
| & | 二進位制與運算子:如果位同時存在於兩個運算元中,則將其複製到結果中。 | (A & B) 將得到 12,即 0000 1100 |
| | | 二進位制或運算子:如果位存在於任何一個運算元中,則將其複製。 | (A | B) 將得到 61,即 0011 1101 |
| ^ | 二進位制異或運算子:如果位在一個運算元中設定,但在另一個運算元中未設定,則將其複製。 | (A ^ B) 將得到 49,即 0011 0001 |
| << | 二進位制左移運算子。左運算元的值向左移動由右運算元指定的位數。 | A << 2 將得到 240,即 1111 0000 |
| >> | 二進位制右移運算子。左運算元的值向右移動由右運算元指定的位數。 | A >> 2 將得到 15,即 0000 1111 |
賦值運算子
下表列出了Go語言支援的所有賦值運算子:
| 運算子 | 描述 | 靜態型別變數宣告向編譯器保證存在一個具有給定型別和名稱的可用變數,以便編譯器可以在不需要變數的完整詳細資訊的情況下繼續進行進一步的編譯。變數宣告只在編譯時才有意義,編譯器在程式連結時需要實際的變數宣告。 |
|---|---|---|
| = | 簡單的賦值運算子,將右側運算元的值賦給左側運算元 | C = A + B 將 A + B 的值賦給 C |
| += | 加法賦值運算子,它將右運算元加到左運算元上並將結果賦給左運算元 | C += A 等價於 C = C + A |
| -= | 減法賦值運算子,它從左運算元中減去右運算元並將結果賦給左運算元 | C -= A 等價於 C = C - A |
| *= | 乘法賦值運算子,它將右運算元與左運算元相乘並將結果賦給左運算元 | C *= A 等價於 C = C * A |
| /= | 除法賦值運算子,它將左運算元除以右運算元並將結果賦給左運算元 | C /= A 等價於 C = C / A |
| %= | 模賦值運算子,它使用兩個運算元取模並將結果賦給左運算元 | C %= A 等價於 C = C % A |
| <<= | 左移賦值運算子 | C <<= 2 與 C = C << 2 相同 |
| >>= | 右移賦值運算子 | C >>= 2 與 C = C >> 2 相同 |
| &= | 按位與賦值運算子 | C &= 2 與 C = C & 2 相同 |
| ^= | 按位異或賦值運算子 | C ^= 2 與 C = C ^ 2 相同 |
| |= | 按位或賦值運算子 | C |= 2 與 C = C | 2 相同 |
其他運算子
Go語言還支援一些其他重要的運算子,包括sizeof和?:。
| 運算子 | 描述 | 靜態型別變數宣告向編譯器保證存在一個具有給定型別和名稱的可用變數,以便編譯器可以在不需要變數的完整詳細資訊的情況下繼續進行進一步的編譯。變數宣告只在編譯時才有意義,編譯器在程式連結時需要實際的變數宣告。 |
|---|---|---|
| & | 返回變數的地址。 | &a; 提供變數的實際地址。 |
| * | 指向變數的指標。 | *a; 提供指向變數的指標。 |
Go語言中的運算子優先順序
運算子優先順序決定了表示式中項的組合方式。這會影響表示式的計算方式。某些運算子的優先順序高於其他運算子;例如,乘法運算子的優先順序高於加法運算子。
例如 x = 7 + 3 * 2; 這裡,x 被賦值為 13,而不是 20,因為運算子 * 的優先順序高於 +,所以它首先計算 3*2,然後加到 7 中。
這裡,優先順序最高的運算子出現在表的上方,優先順序最低的出現在下方。在表示式中,將首先計算優先順序較高的運算子。
| 類別 | 運算子 | 結合性 |
|---|---|---|
| 字尾 | () [] -> . ++ - - | 從左到右 |
| 一元 | + - ! ~ ++ -- (type)* & sizeof | 從右到左 |
| 乘法 | * / % | 從左到右 |
| 加法 | + - | 從左到右 |
| 移位 | << >> | 從左到右 |
| 關係 | < <= > >= | 從左到右 |
| 相等 | == != | 從左到右 |
| 按位與 | & | 從左到右 |
| 按位異或 | ^ | 從左到右 |
| 按位或 | | | 從左到右 |
| 邏輯與 | && | 從左到右 |
| 邏輯或 | || | 從左到右 |
| 賦值 | = += -= *= /= %=>>= <<= &= ^= |= | 從右到左 |
| 逗號 | , | 從左到右 |
Go - 決策
決策結構要求程式設計師指定一個或多個要由程式評估或測試的條件,以及如果確定條件為真則要執行的語句(或語句組),以及可選的,如果確定條件為假則要執行的其他語句。
以下是大多數程式語言中常見的決策結構的一般形式:
Go程式語言提供以下型別的決策語句。點選以下連結檢視詳細資訊。
| 變數名可以由字母、數字和下劃線組成。它必須以字母或下劃線開頭。大小寫字母是不同的,因為 Go 區分大小寫。基於上一章解釋的基本型別,將存在以下基本變數型別: | 語句及描述 |
|---|---|
| 1 |
if語句
一個if語句由一個布林表示式和一個或多個語句組成。 |
| 2 | if...else語句
一個if語句可以跟一個可選的else語句,當布林表示式為假時執行。 |
| 3 |
巢狀if語句
你可以在另一個if或else if語句中使用一個if或else if語句。 |
| 4 |
switch語句
一個switch語句允許測試一個變數是否與一個值列表相等。 |
| 5 |
select語句
一個select語句類似於switch語句,不同之處在於case語句指的是通道通訊。 |
Go - 迴圈
可能存在需要多次執行程式碼塊的情況。一般來說,語句是順序執行的:函式中的第一個語句首先執行,然後是第二個,依此類推。
程式語言提供了各種控制結構,允許更復雜的執行路徑。
迴圈語句允許我們多次執行語句或語句組,以下是大多數程式語言中迴圈語句的一般形式:
Go程式語言提供以下型別的迴圈來處理迴圈需求。
| 變數名可以由字母、數字和下劃線組成。它必須以字母或下劃線開頭。大小寫字母是不同的,因為 Go 區分大小寫。基於上一章解釋的基本型別,將存在以下基本變數型別: | 迴圈型別及描述 |
|---|---|
| 1 |
for迴圈
它多次執行一系列語句,並縮寫管理迴圈變數的程式碼。 |
| 2 |
巢狀迴圈
這些是在任何for迴圈內部的一個或多個迴圈。 |
迴圈控制語句
迴圈控制語句改變執行的正常順序。當執行離開其作用域時,在該作用域中建立的所有自動物件都將被銷燬。
Go支援以下控制語句:
| 變數名可以由字母、數字和下劃線組成。它必須以字母或下劃線開頭。大小寫字母是不同的,因為 Go 區分大小寫。基於上一章解釋的基本型別,將存在以下基本變數型別: | 控制語句及描述 |
|---|---|
| 1 |
break語句
它終止for迴圈或switch語句,並將執行轉移到for迴圈或switch語句後的語句。 |
| 2 |
continue語句
它導致迴圈跳過其主體的其餘部分,並在重新迭代之前立即重新測試其條件。 |
| 3 |
goto語句
它將控制轉移到標記的語句。 |
無限迴圈
如果迴圈的條件永遠不會變為假,則迴圈將成為無限迴圈。for迴圈傳統上用於此目的。由於構成for迴圈的三個表示式都不是必需的,因此您可以透過將條件表示式留空或向其傳遞true來建立一個無限迴圈。
package main
import "fmt"
func main() {
for true {
fmt.Printf("This loop will run forever.\n");
}
}
當條件表示式不存在時,它被假定為true。您可能有一個初始化和增量表達式,但是C程式設計師更常用for(;;)結構來表示無限迴圈。
注意 - 您可以透過按Ctrl + C鍵終止無限迴圈。
Go - 函式
函式是一組一起執行任務的語句。每個Go程式至少有一個函式,即main()。您可以將程式碼分成單獨的函式。如何將程式碼劃分到不同的函式取決於您,但從邏輯上講,劃分應該使每個函式執行特定任務。
函式**宣告**告訴編譯器函式名、返回型別和引數。函式**定義**提供了函式的實際主體。
Go標準庫提供了許多內建函式,你的程式可以呼叫這些函式。例如,函式**len()**接受各種型別的引數並返回型別的長度。如果向其傳遞字串,則函式返回字串的位元組長度。如果向其傳遞陣列,則函式返回陣列的長度。
函式也稱為**方法、子例程**或**過程**。
定義函式
Go程式語言中函式定義的一般形式如下:
func function_name( [parameter list] ) [return_types]
{
body of the function
}
Go程式語言中的函式定義由一個函式頭和一個函式體組成。以下是函式的所有部分:
**Func** − 它開始函式的宣告。
**函式名** − 它是函式的實際名稱。函式名和引數列表一起構成函式簽名。
**引數** − 引數就像一個佔位符。呼叫函式時,你將值傳遞給引數。此值稱為實際引數或自變數。引數列表是指函式的引數的型別、順序和數量。引數是可選的;也就是說,函式可能不包含任何引數。
**返回型別** − 函式可以返回多個值。`return_types`是函式返回值的資料型別的列表。有些函式執行所需的操作而不返回值。在這種情況下,`return_type`不需要。
**函式體** − 它包含定義函式功能的語句集合。
靜態型別變數宣告向編譯器保證存在一個具有給定型別和名稱的可用變數,以便編譯器可以在不需要變數的完整詳細資訊的情況下繼續進行進一步的編譯。變數宣告只在編譯時才有意義,編譯器在程式連結時需要實際的變數宣告。
以下原始碼顯示了一個名為**max()**的函式。此函式接受兩個引數 num1 和 num2,並返回兩者之間的最大值:
/* function returning the max between two numbers */
func max(num1, num2 int) int {
/* local variable declaration */
result int
if (num1 > num2) {
result = num1
} else {
result = num2
}
return result
}
呼叫函式
建立Go函式時,你定義了函式必須執行的操作。要使用函式,你必須呼叫該函式來執行定義的任務。
當程式呼叫函式時,程式控制權將轉移到被呼叫的函式。被呼叫的函式執行定義的任務,當執行其return語句或到達其函式結束的閉合大括號時,它將程式控制權返回到主程式。
要呼叫函式,只需傳遞所需的引數及其函式名即可。如果函式返回值,則可以儲存返回值。例如:
package main
import "fmt"
func main() {
/* local variable definition */
var a int = 100
var b int = 200
var ret int
/* calling a function to get max value */
ret = max(a, b)
fmt.Printf( "Max value is : %d\n", ret )
}
/* function returning the max between two numbers */
func max(num1, num2 int) int {
/* local variable declaration */
var result int
if (num1 > num2) {
result = num1
} else {
result = num2
}
return result
}
我們將 max() 函式與 main() 函式一起保留,並編譯了原始碼。執行最終的可執行檔案時,它將產生以下結果:
Max value is : 200
從函式返回多個值
Go函式可以返回多個值。例如:
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("Mahesh", "Kumar")
fmt.Println(a, b)
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
Kumar Mahesh
函式引數
如果函式要使用引數,則必須宣告接受引數值的變數。這些變數稱為函式的**形式引數**。
形式引數在函式內部的行為類似於其他區域性變數,並在進入函式時建立,並在退出函式時銷燬。
呼叫函式時,引數可以透過兩種方式傳遞給函式:
| 變數名可以由字母、數字和下劃線組成。它必須以字母或下劃線開頭。大小寫字母是不同的,因為 Go 區分大小寫。基於上一章解釋的基本型別,將存在以下基本變數型別: | 呼叫型別 & 描述 |
|---|---|
| 1 |
按值呼叫
此方法將引數的實際值複製到函式的形式引數中。在這種情況下,對函式內部引數所做的更改不會影響引數。 |
| 2 |
按引用呼叫
此方法將引數的地址複製到形式引數中。在函式內部,該地址用於訪問呼叫中使用的實際引數。這意味著對引數所做的更改會影響引數。 |
預設情況下,Go 使用按值呼叫傳遞引數。一般來說,這意味著函式內的程式碼無法更改用於呼叫函式的引數。上面的程式在呼叫 max() 函式時使用了相同的方法。
函式用法
函式可以按以下方式使用
| 變數名可以由字母、數字和下劃線組成。它必須以字母或下劃線開頭。大小寫字母是不同的,因為 Go 區分大小寫。基於上一章解釋的基本型別,將存在以下基本變數型別: | 函式用法 & 描述 |
|---|---|
| 1 |
函式作為值
可以動態建立函式,並將其用作值。 |
| 2 |
函式閉包
函式閉包是匿名函式,可用於動態程式設計。 |
| 3 |
方法
方法是具有接收者的特殊函式。 |
Go - 作用域規則
在任何程式設計中,作用域都是程式的一個區域,其中可以定義變數,並且超出該區域就不能訪問該變數。在Go程式語言中,可以在三個地方宣告變數:
在函式或程式碼塊內(**區域性**變數)
在所有函式之外(**全域性**變數)
在函式引數定義中(**形式**引數)
讓我們瞭解一下什麼是**區域性**變數和**全域性**變數,以及什麼是**形式**引數。
區域性變數
在函式或程式碼塊內宣告的變數稱為區域性變數。只有函式或程式碼塊內的語句才能使用它們。區域性變數對於函式外部的函式是未知的。以下示例使用區域性變數。這裡所有變數 a、b 和 c 對 main() 函式都是區域性的。
package main
import "fmt"
func main() {
/* local variable declaration */
var a, b, c int
/* actual initialization */
a = 10
b = 20
c = a + b
fmt.Printf ("value of a = %d, b = %d and c = %d\n", a, b, c)
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
value of a = 10, b = 20 and c = 30
全域性變數
全域性變數在函式外部定義,通常位於程式頂部。全域性變數在程式的整個生命週期中保持其值,並且可以在為程式定義的任何函式內部訪問它們。
任何函式都可以訪問全域性變數。也就是說,全域性變數在其聲明後即可在整個程式中使用。以下示例使用全域性變數和區域性變數:
package main
import "fmt"
/* global variable declaration */
var g int
func main() {
/* local variable declaration */
var a, b int
/* actual initialization */
a = 10
b = 20
g = a + b
fmt.Printf("value of a = %d, b = %d and g = %d\n", a, b, g)
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
value of a = 10, b = 20 and g = 30
程式可以為區域性變數和全域性變數使用相同的名稱,但函式內部區域性變數的值優先。例如:
package main
import "fmt"
/* global variable declaration */
var g int = 20
func main() {
/* local variable declaration */
var g int = 10
fmt.Printf ("value of g = %d\n", g)
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
value of g = 10
形式引數
形式引數在該函式內被視為區域性變數,並且它們優先於全域性變數。例如:
package main
import "fmt"
/* global variable declaration */
var a int = 20;
func main() {
/* local variable declaration in main function */
var a int = 10
var b int = 20
var c int = 0
fmt.Printf("value of a in main() = %d\n", a);
c = sum( a, b);
fmt.Printf("value of c in main() = %d\n", c);
}
/* function to add two integers */
func sum(a, b int) int {
fmt.Printf("value of a in sum() = %d\n", a);
fmt.Printf("value of b in sum() = %d\n", b);
return a + b;
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
value of a in main() = 10 value of a in sum() = 10 value of b in sum() = 20 value of c in main() = 30
初始化區域性變數和全域性變數
區域性變數和全域性變數初始化為其預設值 0;而指標初始化為 nil。
| 資料型別 | 初始預設值 |
|---|---|
| 32 位或 64 位 | 0 |
| 預定義的與體系結構無關的浮點型別如下: | 0 |
| 指標 | nil |
Go - 字串
字串在 Go 程式設計中被廣泛使用,是隻讀的位元組切片。在 Go 程式語言中,字串是**切片**。Go 平臺提供了各種用於操作字串的庫。
- unicode
- regexp
- strings
建立字串
建立字串最直接的方法是編寫:
var greeting = "Hello world!"
每當編譯器在你的程式碼中遇到字串字面量時,它都會建立一個字串物件,其值為在這種情況下為“Hello world!”。
字串字面量包含稱為 rune 的有效 UTF-8 序列。字串包含任意位元組。
package main
import "fmt"
func main() {
var greeting = "Hello world!"
fmt.Printf("normal string: ")
fmt.Printf("%s", greeting)
fmt.Printf("\n")
fmt.Printf("hex bytes: ")
for i := 0; i < len(greeting); i++ {
fmt.Printf("%x ", greeting[i])
}
fmt.Printf("\n")
const sampleText = "\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98"
/*q flag escapes unprintable characters, with + flag it escapses non-ascii
characters as well to make output unambigous
*/
fmt.Printf("quoted string: ")
fmt.Printf("%+q", sampleText)
fmt.Printf("\n")
}
這將產生以下結果:
normal string: Hello world! hex bytes: 48 65 6c 6c 6f 20 77 6f 72 6c 64 21 quoted string: "\xbd\xb2=\xbc \u2318"
**注意** − 字串字面量是不可變的,因此一旦建立,就不能更改字串字面量。
字串長度
len(str) 方法返回字串字面量中包含的位元組數。
package main
import "fmt"
func main() {
var greeting = "Hello world!"
fmt.Printf("String Length is: ")
fmt.Println(len(greeting))
}
這將產生以下結果:
String Length is : 12
連線字串
strings 包包含一個用於連線多個字串的**join**方法:
strings.Join(sample, " ")
Join 將陣列的元素連線起來以建立一個單個字串。第二個引數是分隔符,它放在陣列的元素之間。
讓我們看下面的例子:
package main
import ("fmt" "math" )"fmt" "strings")
func main() {
greetings := []string{"Hello","world!"}
fmt.Println(strings.Join(greetings, " "))
}
這將產生以下結果:
Hello world!
Go - 陣列
Go 程式語言提供了一種稱為**陣列**的資料結構,它可以儲存大小固定的相同型別元素的順序集合。陣列用於儲存資料集合,但通常將陣列視為相同型別的變數集合更有用。
無需宣告單個變數,例如 number0、number1……和 number99,你宣告一個數組變數,例如 numbers,並使用 numbers[0]、numbers[1]……和 numbers[99] 來表示單個變數。陣列中的特定元素透過索引訪問。
所有陣列都由連續的記憶體位置組成。最低地址對應於第一個元素,最高地址對應於最後一個元素。
宣告陣列
要在 Go 中宣告陣列,程式設計師應指定元素的型別和陣列所需的元素數量,如下所示:
var variable_name [SIZE] variable_type
這稱為一維陣列。**arraySize**必須是一個大於零的整數常量,而**type**可以是任何有效的 Go 資料型別。例如,要宣告一個名為**balance**的 10 個元素的 float32 型別陣列,請使用以下語句:
var balance [10] float32
這裡,**balance**是一個變數陣列,最多可以容納 10 個浮點數。
初始化陣列
你可以使用以下方法在 Go 中逐個初始化陣列或使用單個語句:
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
大括號 {} 中的值的數量不能大於我們在方括號 [] 中為陣列宣告的元素數量。
如果省略陣列的大小,則會建立一個足夠大的陣列來容納初始化。因此,如果你編寫:
var balance = []float32{1000.0, 2.0, 3.4, 7.0, 50.0}
你將建立與在前面示例中建立的完全相同的陣列。以下是如何分配陣列單個元素的示例:
balance[4] = 50.0
上述語句將陣列中第 5 個元素賦值為 50.0。所有陣列的第一個元素的索引均為 0,也稱為基索引,陣列的最後一個索引將為陣列的總大小減 1。以下是我們上面討論的相同陣列的圖形表示:
訪問陣列元素
透過索引陣列名稱來訪問元素。這是透過在陣列名稱後方括號中放置元素的索引來完成的。例如:
float32 salary = balance[9]
上述語句將從陣列中獲取第 10 個元素並將該值賦給 salary 變數。以下示例將使用所有上述三個概念,即宣告、賦值和訪問陣列:
package main
import "fmt"
func main() {
var n [10]int /* n is an array of 10 integers */
var i,j int
/* initialize elements of array n to 0 */
for i = 0; i < 10; i++ {
n[i] = i + 100 /* set element at location i to i + 100 */
}
/* output each array element's value */
for j = 0; j < 10; j++ {
fmt.Printf("Element[%d] = %d\n", j, n[j] )
}
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
Element[0] = 100 Element[1] = 101 Element[2] = 102 Element[3] = 103 Element[4] = 104 Element[5] = 105 Element[6] = 106 Element[7] = 107 Element[8] = 108 Element[9] = 109
Go 陣列詳解
與陣列相關的重要的概念,Go 程式設計師應該清楚:
| 變數名可以由字母、數字和下劃線組成。它必須以字母或下劃線開頭。大小寫字母是不同的,因為 Go 區分大小寫。基於上一章解釋的基本型別,將存在以下基本變數型別: | 概念 & 描述 |
|---|---|
| 1 |
多維陣列
Go 支援多維陣列。多維陣列最簡單的形式是二維陣列。 |
| 2 | 將陣列傳遞給函式
你可以透過指定陣列的名稱而不指定索引來將指向陣列的指標傳遞給函式。 |
Go - 指標
Go 中的指標很容易學習,也很有趣。一些 Go 程式設計任務使用指標更容易完成,而其他任務(例如按引用呼叫)則必須使用指標才能完成。因此,學習指標成為一名完美的 Go 程式設計師是必要的。
如你所知,每個變數都是一個記憶體位置,並且每個記憶體位置都有其定義的地址,可以使用 ampersand (&) 運算子訪問它,該運算子表示記憶體中的地址。考慮以下示例,它將列印已定義變數的地址:
package main
import "fmt"
func main() {
var a int = 10
fmt.Printf("Address of a variable: %x\n", &a )
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
Address of a variable: 10328000
所以你瞭解了什麼是記憶體地址以及如何訪問它。現在讓我們看看什麼是指標。
什麼是指標?
指標是一個變數,其值是另一個變數的地址,即記憶體位置的直接地址。與任何變數或常量一樣,您必須在使用指標儲存任何變數地址之前宣告它。指標變數宣告的一般形式如下:
var var_name *var-type
這裡,type是指標的基本型別;它必須是有效的C資料型別,而var-name是指標變數的名稱。用於宣告指標的星號 * 與用於乘法的星號相同。但是,在此語句中,星號用於將變數指定為指標。以下是有效的指標宣告:
var ip *int /* pointer to an integer */ var fp *float32 /* pointer to a float */
所有指標的實際資料型別,無論是整數、浮點數還是其他型別,都是相同的,一個代表記憶體地址的長十六進位制數。不同資料型別指標之間的唯一區別在於指標指向的變數或常量的型別。
如何使用指標?
我們經常對指標執行一些重要的操作:(a) 定義指標變數,(b) 將變數的地址賦給指標,以及 (c) 訪問指標變數中儲存的地址處的值。
所有這些操作都是使用一元運算子 * 執行的,它返回位於其運算元指定的地址處的變數的值。下面的例子演示瞭如何執行這些操作:
package main
import "fmt"
func main() {
var a int = 20 /* actual variable declaration */
var ip *int /* pointer variable declaration */
ip = &a /* store address of a in pointer variable*/
fmt.Printf("Address of a variable: %x\n", &a )
/* address stored in pointer variable */
fmt.Printf("Address stored in ip variable: %x\n", ip )
/* access the value using the pointer */
fmt.Printf("Value of *ip variable: %d\n", *ip )
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
Address of var variable: 10328000 Address stored in ip variable: 10328000 Value of *ip variable: 20
Go語言中的空指標
如果您沒有確切的地址要賦值,Go編譯器會將空值賦給指標變數。這是在變數宣告時完成的。賦值為 nil 的指標稱為空指標。
空指標是一個常量,其值為零,在幾個標準庫中定義。考慮以下程式:
package main
import "fmt"
func main() {
var ptr *int
fmt.Printf("The value of ptr is : %x\n", ptr )
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
The value of ptr is 0
在大多數作業系統中,程式不允許訪問地址為 0 的記憶體,因為該記憶體由作業系統保留。但是,記憶體地址 0 具有特殊的意義;它表示指標並非意圖指向可訪問的記憶體位置。但是按照約定,如果指標包含 nil(零)值,則假定它不指向任何內容。
您可以使用 if 語句檢查空指標,如下所示:
if(ptr != nil) /* succeeds if p is not nil */ if(ptr == nil) /* succeeds if p is null */
Go 指標詳解
指標有很多但容易理解的概念,它們對 Go 程式設計非常重要。Go 程式設計師應該清楚指標的以下概念:
| 變數名可以由字母、數字和下劃線組成。它必須以字母或下劃線開頭。大小寫字母是不同的,因為 Go 區分大小寫。基於上一章解釋的基本型別,將存在以下基本變數型別: | 概念 & 描述 |
|---|---|
| 1 |
Go - 指標陣列
您可以定義陣列來儲存多個指標。 |
| 2 |
Go - 指向指標的指標
Go允許您擁有指向指標的指標,以此類推。 |
| 3 |
在 Go 中將指標傳遞給函式
透過引用或透過地址傳遞引數都可以使被呼叫函式在呼叫函式中更改傳遞的引數。 |
Go - 結構體
Go 陣列允許您定義可以儲存多個相同型別資料項的變數。結構體是 Go 程式設計中另一種使用者定義的資料型別,它允許您組合不同型別的資料項。
結構體用於表示記錄。假設您要跟蹤圖書館中的書籍。您可能想要跟蹤每本書的以下屬性:
- 標題
- 作者
- 主題
- 圖書 ID
在這種情況下,結構體非常有用。
定義結構體
要定義結構體,必須使用type和struct語句。struct 語句定義了一種新的資料型別,其中包含程式的多個成員。type 語句將名稱與型別繫結,在本例中型別為 struct。struct 語句的格式如下:
type struct_variable_type struct {
member definition;
member definition;
...
member definition;
}
定義結構體型別後,可以使用以下語法宣告該型別的變數。
variable_name := structure_variable_type {value1, value2...valuen}
訪問結構體成員
要訪問結構體的任何成員,我們使用成員訪問運算子 (.)。成員訪問運算子被編碼為結構體變數名稱和我們想要訪問的結構體成員之間的句點。您將使用struct關鍵字定義結構體型別的變數。下面的例子解釋瞭如何使用結構體:
package main
import "fmt"
type Books struct {
title string
author string
subject string
book_id int
}
func main() {
var Book1 Books /* Declare Book1 of type Book */
var Book2 Books /* Declare Book2 of type Book */
/* book 1 specification */
Book1.title = "Go Programming"
Book1.author = "Mahesh Kumar"
Book1.subject = "Go Programming Tutorial"
Book1.book_id = 6495407
/* book 2 specification */
Book2.title = "Telecom Billing"
Book2.author = "Zara Ali"
Book2.subject = "Telecom Billing Tutorial"
Book2.book_id = 6495700
/* print Book1 info */
fmt.Printf( "Book 1 title : %s\n", Book1.title)
fmt.Printf( "Book 1 author : %s\n", Book1.author)
fmt.Printf( "Book 1 subject : %s\n", Book1.subject)
fmt.Printf( "Book 1 book_id : %d\n", Book1.book_id)
/* print Book2 info */
fmt.Printf( "Book 2 title : %s\n", Book2.title)
fmt.Printf( "Book 2 author : %s\n", Book2.author)
fmt.Printf( "Book 2 subject : %s\n", Book2.subject)
fmt.Printf( "Book 2 book_id : %d\n", Book2.book_id)
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
Book 1 title : Go Programming Book 1 author : Mahesh Kumar Book 1 subject : Go Programming Tutorial Book 1 book_id : 6495407 Book 2 title : Telecom Billing Book 2 author : Zara Ali Book 2 subject : Telecom Billing Tutorial Book 2 book_id : 6495700
結構體作為函式引數
您可以像傳遞任何其他變數或指標一樣傳遞結構體作為函式引數。您將像在上面的例子中一樣訪問結構體變數:
package main
import "fmt"
type Books struct {
title string
author string
subject string
book_id int
}
func main() {
var Book1 Books /* Declare Book1 of type Book */
var Book2 Books /* Declare Book2 of type Book */
/* book 1 specification */
Book1.title = "Go Programming"
Book1.author = "Mahesh Kumar"
Book1.subject = "Go Programming Tutorial"
Book1.book_id = 6495407
/* book 2 specification */
Book2.title = "Telecom Billing"
Book2.author = "Zara Ali"
Book2.subject = "Telecom Billing Tutorial"
Book2.book_id = 6495700
/* print Book1 info */
printBook(Book1)
/* print Book2 info */
printBook(Book2)
}
func printBook( book Books ) {
fmt.Printf( "Book title : %s\n", book.title);
fmt.Printf( "Book author : %s\n", book.author);
fmt.Printf( "Book subject : %s\n", book.subject);
fmt.Printf( "Book book_id : %d\n", book.book_id);
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
Book title : Go Programming Book author : Mahesh Kumar Book subject : Go Programming Tutorial Book book_id : 6495407 Book title : Telecom Billing Book author : Zara Ali Book subject : Telecom Billing Tutorial Book book_id : 6495700
指向結構體的指標
您可以像定義指向任何其他變數的指標一樣定義指向結構體的指標,如下所示:
var struct_pointer *Books
現在,您可以將結構體變數的地址儲存在上面定義的指標變數中。要查詢結構體變數的地址,請在結構體名稱前加上 & 運算子,如下所示:
struct_pointer = &Book1;
要使用指向該結構體的指標訪問結構體的成員,必須使用 "." 運算子,如下所示:
struct_pointer.title;
讓我們使用結構體指標重寫上面的例子:
package main
import "fmt"
type Books struct {
title string
author string
subject string
book_id int
}
func main() {
var Book1 Books /* Declare Book1 of type Book */
var Book2 Books /* Declare Book2 of type Book */
/* book 1 specification */
Book1.title = "Go Programming"
Book1.author = "Mahesh Kumar"
Book1.subject = "Go Programming Tutorial"
Book1.book_id = 6495407
/* book 2 specification */
Book2.title = "Telecom Billing"
Book2.author = "Zara Ali"
Book2.subject = "Telecom Billing Tutorial"
Book2.book_id = 6495700
/* print Book1 info */
printBook(&Book1)
/* print Book2 info */
printBook(&Book2)
}
func printBook( book *Books ) {
fmt.Printf( "Book title : %s\n", book.title);
fmt.Printf( "Book author : %s\n", book.author);
fmt.Printf( "Book subject : %s\n", book.subject);
fmt.Printf( "Book book_id : %d\n", book.book_id);
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
Book title : Go Programming Book author : Mahesh Kumar Book subject : Go Programming Tutorial Book book_id : 6495407 Book title : Telecom Billing Book author : Zara Ali Book subject : Telecom Billing Tutorial Book book_id : 6495700
Go - 切片
Go 切片是對 Go 陣列的抽象。Go 陣列允許您定義可以儲存多個相同型別資料項的變數,但它沒有提供任何內建方法來動態增加其大小或獲取其自身的子陣列。切片克服了這個限制。它提供了陣列上所需的許多實用函式,並且廣泛用於 Go 程式設計。
定義切片
要定義切片,您可以宣告它為陣列而不指定其大小。或者,您可以使用make函式建立切片。
var numbers []int /* a slice of unspecified size */
/* numbers == []int{0,0,0,0,0}*/
numbers = make([]int,5,5) /* a slice of length 5 and capacity 5*/
len() 和 cap() 函式
切片是對陣列的抽象。它實際上使用陣列作為底層結構。len()函式返回切片中存在的元素,而cap()函式返回切片的容量(即它可以容納多少個元素)。下面的例子解釋了切片的使用:
package main
import "fmt"
func main() {
var numbers = make([]int,3,5)
printSlice(numbers)
}
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
len = 3 cap = 5 slice = [0 0 0]
空切片
如果切片宣告時沒有輸入,則預設情況下將其初始化為空。其長度和容量為零。例如:
package main
import "fmt"
func main() {
var numbers []int
printSlice(numbers)
if(numbers == nil){
fmt.Printf("slice is nil")
}
}
func printSlice(x []int){
fmt.Printf("len = %d cap = %d slice = %v\n", len(x), cap(x),x)
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
len = 0 cap = 0 slice = [] slice is nil
子切片
切片允許指定下界和上界,以使用[下界:上界]獲取其子切片。例如:
package main
import "fmt"
func main() {
/* create a slice */
numbers := []int{0,1,2,3,4,5,6,7,8}
printSlice(numbers)
/* print the original slice */
fmt.Println("numbers ==", numbers)
/* print the sub slice starting from index 1(included) to index 4(excluded)*/
fmt.Println("numbers[1:4] ==", numbers[1:4])
/* missing lower bound implies 0*/
fmt.Println("numbers[:3] ==", numbers[:3])
/* missing upper bound implies len(s)*/
fmt.Println("numbers[4:] ==", numbers[4:])
numbers1 := make([]int,0,5)
printSlice(numbers1)
/* print the sub slice starting from index 0(included) to index 2(excluded) */
number2 := numbers[:2]
printSlice(number2)
/* print the sub slice starting from index 2(included) to index 5(excluded) */
number3 := numbers[2:5]
printSlice(number3)
}
func printSlice(x []int){
fmt.Printf("len = %d cap = %d slice = %v\n", len(x), cap(x),x)
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
len = 9 cap = 9 slice = [0 1 2 3 4 5 6 7 8] numbers == [0 1 2 3 4 5 6 7 8] numbers[1:4] == [1 2 3] numbers[:3] == [0 1 2] numbers[4:] == [4 5 6 7 8] len = 0 cap = 5 slice = [] len = 2 cap = 9 slice = [0 1] len = 3 cap = 7 slice = [2 3 4]
append() 和 copy() 函式
可以使用append()函式增加切片的容量。使用copy()函式,源切片的內容將複製到目標切片。例如:
package main
import "fmt"
func main() {
var numbers []int
printSlice(numbers)
/* append allows nil slice */
numbers = append(numbers, 0)
printSlice(numbers)
/* add one element to slice*/
numbers = append(numbers, 1)
printSlice(numbers)
/* add more than one element at a time*/
numbers = append(numbers, 2,3,4)
printSlice(numbers)
/* create a slice numbers1 with double the capacity of earlier slice*/
numbers1 := make([]int, len(numbers), (cap(numbers))*2)
/* copy content of numbers to numbers1 */
copy(numbers1,numbers)
printSlice(numbers1)
}
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
len = 0 cap = 0 slice = [] len = 1 cap = 2 slice = [0] len = 2 cap = 2 slice = [0 1] len = 5 cap = 8 slice = [0 1 2 3 4] len = 5 cap = 16 slice = [0 1 2 3 4]
Go - 範圍
range關鍵字用於for迴圈中迭代陣列、切片、通道或對映的項。對於陣列和切片,它返回項的索引作為整數。對於對映,它返回下一個鍵值對的鍵。Range 要麼返回一個值,要麼返回兩個值。如果 range 表示式的左側只使用一個值,則它是下表中的第一個值。
| Range 表示式 | 第一個值 | 第二個值(可選) |
|---|---|---|
| 陣列或切片 a [n]E | 索引 i int | a[i] E |
| 字串 s string 型別 | 索引 i int | rune int |
| map m map[K]V | 鍵 k K | 值 m[k] V |
| 通道 c chan E | 元素 e E | 無 |
靜態型別變數宣告向編譯器保證存在一個具有給定型別和名稱的可用變數,以便編譯器可以在不需要變數的完整詳細資訊的情況下繼續進行進一步的編譯。變數宣告只在編譯時才有意義,編譯器在程式連結時需要實際的變數宣告。
以下段落顯示瞭如何使用 range:
package main
import "fmt"
func main() {
/* create a slice */
numbers := []int{0,1,2,3,4,5,6,7,8}
/* print the numbers */
for i:= range numbers {
fmt.Println("Slice item",i,"is",numbers[i])
}
/* create a map*/
countryCapitalMap := map[string] string {"France":"Paris","Italy":"Rome","Japan":"Tokyo"}
/* print map using keys*/
for country := range countryCapitalMap {
fmt.Println("Capital of",country,"is",countryCapitalMap[country])
}
/* print map using key-value*/
for country,capital := range countryCapitalMap {
fmt.Println("Capital of",country,"is",capital)
}
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
Slice item 0 is 0 Slice item 1 is 1 Slice item 2 is 2 Slice item 3 is 3 Slice item 4 is 4 Slice item 5 is 5 Slice item 6 is 6 Slice item 7 is 7 Slice item 8 is 8 Capital of France is Paris Capital of Italy is Rome Capital of Japan is Tokyo Capital of France is Paris Capital of Italy is Rome Capital of Japan is Tokyo
Go - 對映
Go 提供了另一種重要的資料型別,稱為對映,它將唯一的鍵對映到值。鍵是您以後用來檢索值的物件。給定一個鍵和一個值,您可以將值儲存在 Map 物件中。儲存值後,您可以使用其鍵檢索它。
定義對映
必須使用make函式建立對映。
/* declare a variable, by default map will be nil*/ var map_variable map[key_data_type]value_data_type /* define the map as nil map can not be assigned any value*/ map_variable = make(map[key_data_type]value_data_type)
靜態型別變數宣告向編譯器保證存在一個具有給定型別和名稱的可用變數,以便編譯器可以在不需要變數的完整詳細資訊的情況下繼續進行進一步的編譯。變數宣告只在編譯時才有意義,編譯器在程式連結時需要實際的變數宣告。
下面的例子說明了如何建立和使用對映:
package main
import "fmt"
func main() {
var countryCapitalMap map[string]string
/* create a map*/
countryCapitalMap = make(map[string]string)
/* insert key-value pairs in the map*/
countryCapitalMap["France"] = "Paris"
countryCapitalMap["Italy"] = "Rome"
countryCapitalMap["Japan"] = "Tokyo"
countryCapitalMap["India"] = "New Delhi"
/* print map using keys*/
for country := range countryCapitalMap {
fmt.Println("Capital of",country,"is",countryCapitalMap[country])
}
/* test if entry is present in the map or not*/
capital, ok := countryCapitalMap["United States"]
/* if ok is true, entry is present otherwise entry is absent*/
if(ok){
fmt.Println("Capital of United States is", capital)
} else {
fmt.Println("Capital of United States is not present")
}
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
Capital of India is New Delhi Capital of France is Paris Capital of Italy is Rome Capital of Japan is Tokyo Capital of United States is not present
delete() 函式
delete() 函式用於從對映中刪除條目。它需要對映和要刪除的相應鍵。例如:
package main
import "fmt"
func main() {
/* create a map*/
countryCapitalMap := map[string] string {"France":"Paris","Italy":"Rome","Japan":"Tokyo","India":"New Delhi"}
fmt.Println("Original map")
/* print map */
for country := range countryCapitalMap {
fmt.Println("Capital of",country,"is",countryCapitalMap[country])
}
/* delete an entry */
delete(countryCapitalMap,"France");
fmt.Println("Entry for France is deleted")
fmt.Println("Updated map")
/* print map */
for country := range countryCapitalMap {
fmt.Println("Capital of",country,"is",countryCapitalMap[country])
}
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
Original Map Capital of France is Paris Capital of Italy is Rome Capital of Japan is Tokyo Capital of India is New Delhi Entry for France is deleted Updated Map Capital of India is New Delhi Capital of Italy is Rome Capital of Japan is Tokyo
Go - 遞迴
遞迴是重複項以自相似方式的過程。同樣的概念也適用於程式語言。如果程式允許在同一個函式內呼叫一個函式,那麼它被稱為遞迴函式呼叫。請看下面的例子:
func recursion() {
recursion() /* function calls itself */
}
func main() {
recursion()
}
Go 程式語言支援遞迴。也就是說,它允許函式呼叫自身。但在使用遞迴時,程式設計師需要注意定義函式的退出條件,否則它將變成無限迴圈。
Go 語言中遞迴的例子
遞迴函式對於解決許多數學問題非常有用,例如計算數字的階乘、生成斐波那契數列等。
示例 1:使用 Go 語言中的遞迴計算階乘
下面的例子使用遞迴函式計算給定數字的階乘:
package main
import "fmt"
func factorial(i int)int {
if(i <= 1) {
return 1
}
return i * factorial(i - 1)
}
func main() {
var i int = 15
fmt.Printf("Factorial of %d is %d", i, factorial(i))
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
Factorial of 15 is 1307674368000
示例 2:使用 Go 語言中的遞迴生成斐波那契數列
下面的例子顯示瞭如何使用遞迴函式生成給定數字的斐波那契數列:
package main
import "fmt"
func fibonaci(i int) (ret int) {
if i == 0 {
return 0
}
if i == 1 {
return 1
}
return fibonaci(i-1) + fibonaci(i-2)
}
func main() {
var i int
for i = 0; i < 10; i++ {
fmt.Printf("%d ", fibonaci(i))
}
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
0 1 1 2 3 5 8 13 21 34
Go - 型別轉換
型別轉換是一種將變數從一種資料型別轉換為另一種資料型別的方法。例如,如果您想將長整型值儲存到簡單的整型中,那麼您可以將長整型型別轉換為整型。您可以使用強制轉換運算子將值從一種型別轉換為另一種型別。其語法如下:
type_name(expression)
靜態型別變數宣告向編譯器保證存在一個具有給定型別和名稱的可用變數,以便編譯器可以在不需要變數的完整詳細資訊的情況下繼續進行進一步的編譯。變數宣告只在編譯時才有意義,編譯器在程式連結時需要實際的變數宣告。
考慮下面的例子,其中強制轉換運算子導致一個整型變數除以另一個整型變數的操作作為浮點數運算來執行。
package main
import "fmt"
func main() {
var sum int = 17
var count int = 5
var mean float32
mean = float32(sum)/float32(count)
fmt.Printf("Value of mean : %f\n",mean)
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
Value of mean : 3.400000
Go - 介面
Go 程式設計提供了一種稱為介面的另一種資料型別,它表示一組方法簽名。struct 資料型別實現這些介面以對介面的方法簽名具有方法定義。
語法
/* define an interface */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}
/* define a struct */
type struct_name struct {
/* variables */
}
/* implement interface methods*/
func (struct_name_variable struct_name) method_name1() [return_type] {
/* method implementation */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* method implementation */
}
靜態型別變數宣告向編譯器保證存在一個具有給定型別和名稱的可用變數,以便編譯器可以在不需要變數的完整詳細資訊的情況下繼續進行進一步的編譯。變數宣告只在編譯時才有意義,編譯器在程式連結時需要實際的變數宣告。
package main
import (
"fmt"
"math"
)
/* define an interface */
type Shape interface {
area() float64
}
/* define a circle */
type Circle struct {
x,y,radius float64
}
/* define a rectangle */
type Rectangle struct {
width, height float64
}
/* define a method for circle (implementation of Shape.area())*/
func(circle Circle) area() float64 {
return math.Pi * circle.radius * circle.radius
}
/* define a method for rectangle (implementation of Shape.area())*/
func(rect Rectangle) area() float64 {
return rect.width * rect.height
}
/* define a method for shape */
func getArea(shape Shape) float64 {
return shape.area()
}
func main() {
circle := Circle{x:0,y:0,radius:5}
rectangle := Rectangle {width:10, height:5}
fmt.Printf("Circle area: %f\n",getArea(circle))
fmt.Printf("Rectangle area: %f\n",getArea(rectangle))
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
Circle area: 78.539816 Rectangle area: 50.000000
Go - 錯誤處理
Go 程式設計提供了一個相當簡單的錯誤處理框架,其中內建了以下宣告的錯誤介面型別:
type error interface {
Error() string
}
函式通常將錯誤作為最後一個返回值返回。使用errors.New構造基本的錯誤訊息,如下所示:
func Sqrt(value float64)(float64, error) {
if(value < 0){
return 0, errors.New("Math: negative number passed to Sqrt")
}
return math.Sqrt(value), nil
}
使用返回值和錯誤訊息。
result, err:= Sqrt(-1)
if err != nil {
fmt.Println(err)
}
靜態型別變數宣告向編譯器保證存在一個具有給定型別和名稱的可用變數,以便編譯器可以在不需要變數的完整詳細資訊的情況下繼續進行進一步的編譯。變數宣告只在編譯時才有意義,編譯器在程式連結時需要實際的變數宣告。
package main
import "errors"
import "fmt"
import "math"
func Sqrt(value float64)(float64, error) {
if(value < 0){
return 0, errors.New("Math: negative number passed to Sqrt")
}
return math.Sqrt(value), nil
}
func main() {
result, err:= Sqrt(-1)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(result)
}
result, err = Sqrt(9)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(result)
}
}
嘗試以下示例,其中變數已在 main 函式內聲明瞭型別並進行了初始化:
Math: negative number passed to Sqrt 3