1. 程式人生 > 實用技巧 >《Go語言開發實戰》筆記

《Go語言開發實戰》筆記

1 基礎部分

Go語言基本結構及說明

package main
import (
	"fmt"
	"runtime"
)
func main()  {
	fmt.Println("hello world")
	// 檢視版本
	fmt.Println(runtime.Version())
}

func main()是程式入口。所有Go函式以關鍵字func開頭,每一個可執行程式都必須包含main()函式,通常是程式啟動後第一個執行的函式,如果有init()函式則會先執行init()函式。

除此之外,還有以下幾點值得注意。

  • (1)只有package名稱為main的包可以包含main()函式。
  • (2)一個可執行程式有且僅有一個main包。
  • (3)通過import關鍵字來匯入其他非main包。
  • (4)可以通過import關鍵字單個匯入,也可以同時匯入多個。

2 基本語法

2.1 變數

變數的本質是計算機分配的一小塊記憶體,專門用於存放指定資料,在程式執行過程中該數值可以發生改變;變數的儲存往往具有瞬時性,或者說是臨時儲存,當程式執行結束,存放該資料的記憶體就會釋放,該變數就會隨著記憶體的釋放而消失。就像日常生活中存放水的水杯,當水杯損壞的時候,裝在裡面的水也會流失掉。

變數又分為區域性變數和全域性變數。

  • 區域性變數,是定義在大括號({})內部的變數,大括號的內部也是區域性變數的作用域。
  • 全域性變數,是定義在函式和大括號({})外部的變數。

Go 語言的變數名由字母、數字、下畫線組成,首個字元不能為數字;Go 語法規定,定義的區域性變數若沒有被呼叫會發生編譯錯誤。

變數宣告

未初始化變數的預設值有如下特點:

  • 整型和浮點型變數預設值:0。
  • 字串預設值為空字串。
  • 布林型預設值為false。
  • 函式、指標變數、切片預設值為nil。

初始化變數的標準格式如下:

var a int = 199 //初始化變數的標準格式
var b = 100 //初始化變數的編譯器自動推斷型別格式
c := 123 //初始化變數的簡短宣告格式

使用 := 賦值操作符可以高效地建立一個新的變數,稱為初始化宣告。宣告語句省略了 var 關鍵字,變數型別將由編譯器自動推斷。這是宣告變數的首選形式,但是它只能被用在函式體內,而不可以用於全域性變數的宣告與賦值。該變數名必須是沒有定義過的變數,若定義過,將發生編譯錯誤。

package main
func main(){
  // 這樣寫是錯誤的
	var a = 123
	a := 222
}
./variable.go:7:4: no new variables on left side of :=

多個短變數宣告和賦值中,至少有一個新宣告的變量出現在左側,那麼即便其他變數名可能是重複宣告的,編譯器也不會報錯。情況如下所示:

package main
import "fmt"
func main(){
  // 這種情況不會報錯
	var a = 666
	a, b := 1,2
	fmt.Println("a>>>",a) // 1
	fmt.Println("n>>>",b) // 2
}

雖然這種方法不會報錯,但是在使用過程中應儘量避免。

變數多重賦值

變數多重賦值是指多個變數同時賦值。

Go語法中,變數初始化和變數賦值是兩個不同的概念。Go語言的變數賦值與其他語言一樣,但是Go提供了其他程式設計師期待已久的多重賦值功能,可以實現變數交換。多重賦值讓Go語言比其他語言減少了程式碼量。

go語言中使用多重賦值進行變數交換的例子:

package main
import "fmt"
func main(){
	var a = 666
	a, b := 111,222
	// 變數交換
	b, a = a, b
	fmt.Println("a>>>",a) // 222
	fmt.Println("n>>>",b) // 111
}

需要注意的是,多重賦值時,左值和右值按照從左到右的順序賦值。這種方法在錯誤處理和函式當中會大量使用。

匿名變數

Go語言的函式可以返回多個值,而事實上並不是所有的返回值都用得上。那麼就可以使用匿名變數,用下畫線“_”替換即可。

例如,定義一個函式,功能為返回兩個int型變數,第一個返回10,第二個返回20,第一次呼叫捨棄第二個返回值,第二次呼叫捨棄第一個返回值,具體語法格式如下所示。

package main
import "fmt"

func getData()(int, string, bool){
	return 666,"whw",false
}

func main(){
	var a = 666
	a, b, _ := getData()
	fmt.Println("a>>>",a) // 666
	fmt.Println("n>>>",b) // whw
}

匿名變數既不佔用名稱空間,也不會分配記憶體。

2.2 資料型別

在Go語言中,有以下幾種資料型別:

基本資料型別(原生資料型別):整型、浮點型、複數型、布林型、字串、字元(byte、rune)。

複合資料型別(派生資料型別):陣列(array)、切片(slice)、對映(map)、函式(function)、結構體(struct)、通道(channel)、介面(interface)、指標(pointer)。

整型

整型分兩大類。有符號整型:int8、int16、int32、int64、int。

無符號整型:uint8、uint16、uint32、uint64、uint。

其中uint8就是byte型,int16對應C語言的short型,int64對應C語言的long型。

![image-20201022114615738](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201022114615738.png)

浮點型

![image-20201022114652127](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201022114652127.png)

常量math.MaxFloat32表示float32能獲取的最大值,大約是3.4×1038;常量math.SmallestNonzeroFloat32表示float32能獲取的最小值,大約為1.4×10-45。

常量math.MaxFloat64表示float64能獲取的最大值,大約是1.8×10308;常量math.SmallestNonzeroFloat64表示float64能獲取的最小值,大約為4.9×10-324。

複數型

複數型用於表示數學中的複數,如1+2j、1-2j、-1-2j等。關於複數型的說明,如表2.3所示。

![image-20201022115103164](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201022115103164.png)

布林型

布林型用預定義識別符號bool表示。在C語言中,對於布林型的值定義,非0表示真,0表示假。而在Go語言中,布林型的值只可以是常量true或者false。

宣告方式如下所示。

var flag bool

布林型無法參與數值運算,也無法與其他型別進行轉換。

字串

字串在Go語言中是以基本資料型別出現的,使用字串就像使用其他原生基本資料型別int、float32、float64、bool一樣。

字串在C++語言中,以類的方式進行封裝,不屬於基本資料型別。

在go中使用字串:

var s1 string //定義名為s1的字串型別變數
s1 = "HelloWorld" //變數賦值

student1 := "火影whw" //以自動推斷方式初始化

有些字串沒有現成的文字代號,所以只能用轉義字元來表示。常用的轉義字元如表2.4所示:

![image-20201022115523155](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201022115523155.png)

定義多行字串的方法如下:

  • 雙引號書寫字串被稱為字串字面量(string literal),這種字面量不能跨行。
  • 多行字串需要使用反引號“`”,多用於內嵌原始碼和內嵌資料。
  • 在反引號中的所有程式碼不會被編譯器識別,而只是作為字串的一部分。
  • 多行字串定義方式如例2-1所示。
package main
import "fmt"

func getString()(string){
	s1 := `
		x := 123
		y := 666
		ss := "A Hero's Country!"
     `
	return s1
}

func main(){
	a := getString()
	fmt.Println("a>>>",a)
}

字元

字串中的每一個元素叫作“字元”,定義字元時使用單引號。Go語言的字元有兩種,如表2.5所示。

![image-20201022120100841](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201022120100841.png)

宣告示例如下(注意必須是單引號!):

package main
import "fmt"

func main(){
	// 注意必須是單引號!!!
	var a byte = 's'
	var b rune = '王'
	fmt.Println("a>>>",a) // 115
	fmt.Println("b>>>",b) // 29579
}

2.3 列印格式化

列印格式化通常使用fmt包,通用的列印格式如表:

![image-20201024100236018](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201024100236018.png)

具體的使用方法:

package main
import "fmt"
func main(){
	str := "wanghw"
	fmt.Printf("%T, %v \n",str,str) //string, wanghw

	var a rune = '王'
	fmt.Printf("%T, %v \n",a,a) //int32, 29579

	var b byte = 'b'
	fmt.Printf("%T, %v \n",b,b) //uint8, 98

	var c int32 = 123
	fmt.Printf("%T, %v \n",c,c) //int32, 123
}

通過上例可以看出,使用通用的格式列印,輸出的結果可能不是自己想要的,為了確保輸出結果與需求一致,還需要學習具體格式的列印方式。

布林型列印格式

![image-20201024100347282](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201024100347282.png)

package main

import "fmt"

func main(){
	var flag bool
	// flag預設是false
	fmt.Printf("%T, %t \n",flag,flag) //bool, false

	flag = true
	fmt.Printf("%T, %t \n",flag,flag) //bool, true
}

整型列印格式

![image-20201024100705972](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201024100705972.png)

package main
import "fmt"
func main(){
	fmt.Printf("%T, %d \n",123, 123) //int, 123
	fmt.Printf("%T, %5d \n",123, 123) //int,   123
	fmt.Printf("%T, %05d \n",123, 123) //int, 00123
	fmt.Printf("%T, %b \n",123, 123) //int, 1111011
	fmt.Printf("%T, %o \n",123, 123) //int, 173
	fmt.Printf("%T, %c \n",98, 98) //int, b
	fmt.Printf("%T, %q \n",98, 98) //int, 'b'
	fmt.Printf("%T, %x \n",123, 123) //int, 7b
	fmt.Printf("%T, %X \n",123, 123) //int, 7B
	fmt.Printf("%T, %U \n",'王', '王') //int32, U+738B
}

浮點型與複數型列印格式

![image-20201024101359915](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201024101359915.png)

package main
import "fmt"
func main(){
	fmt.Printf("%b \n",123.23433) //8671845041675824p-46
	fmt.Printf("%f \n",123.2) //123.200000
	fmt.Printf("%.2f \n",123.22222) //123.22
	fmt.Printf("%e \n",123.22222) //1.232222e+02
	fmt.Printf("%E \n",123.22222) //1.232222E+02
	fmt.Printf("%.1e \n",123.22222) //1.2e+02
	fmt.Printf("%F \n",123.22222) //123.222220
	fmt.Printf("%g \n",123.22222) //123.22222
	fmt.Printf("%G \n",123.22222) //123.22222 
}

關於複數的列印格式如下

package main
import "fmt"
func main(){
	var value complex64 = 2.2 + 22i
	value2 := complex(2.2,222)
	fmt.Println(real(value)) //2.2
	fmt.Println(imag(value)) //22
	fmt.Println(value2) //(2.2+222i)
}

字串列印與位元組陣列的列印格式

![image-20201024102229661](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201024102229661.png)

package main
import "fmt"
func main(){
	arr := []byte{'a','b','c','d'}
	fmt.Printf("%s \n","火影whw")//火影whw
	fmt.Printf("%q \n","火影whw")//"火影whw"
	fmt.Printf("%x \n","火影whw")//e781abe5bdb1776877
	fmt.Printf("%X \n","火影whw")//E781ABE5BDB1776877

	fmt.Printf("%T, %s \n",arr, arr)//[]uint8, abcd
	fmt.Printf("%T, %q \n",arr, arr)//[]uint8, "abcd"
	fmt.Printf("%T, %x \n",arr, arr)//[]uint8, 61626364
	fmt.Printf("%T, %X \n",arr, arr)//[]uint8, 61626364 
}

2.4 資料型別轉換

Go語言採用資料型別前置加括號的方式進行型別轉換,格式如:T(表示式)。T表示要轉換的型別;表示式包括變數、數值、函式返回值等。

型別轉換時,需要考慮兩種型別之間的關係和範圍,是否會發生數值截斷。就像將1000毫升的水倒入容積為500毫升的瓶子裡,餘出來500毫升的水便會流失。值得注意的是,布林型無法與其他型別進行轉換

package main
import "fmt"
func main(){
	a := 100
	b1 := float64(a)
	b2 := string(a)
	fmt.Println("b1>>>",b1)
	fmt.Println("b2>>>",b2)
}

浮點型與整型之間轉換

float和int的型別精度不同,使用時需要注意float轉int時精度的損失。

package main

import "fmt"

func main() {
	chinese := 90
	english := 98.9

	avg1 := (chinese + int(english)) / 2
	avg2 := (float64(chinese) + english) / 2

	fmt.Println("avg1>>>",avg1) // 94
	fmt.Println("avg2>>>",avg2) // 94.45
}

整型轉字串型別

這種型別的轉換,其實相當於byte或rune轉string。

int數值是ASCII碼的編號或unicode字符集的編號,轉成string就是根據字符集,將對應編號的字元查找出來。當該數值超出unicode編號範圍,則轉成的字串顯示為亂碼。例如,19968轉string,就是“一”。

備註:

  • ASCII字符集中數字的十進位制範圍是48~57;
  • ASCII字符集中大寫字母的十進位制範圍是65~90;
  • ASCII字符集中小寫字母的十進位制範圍是97~122;
  • unicode字符集中漢字的範圍是4e00~9fa5,十進位制範圍是19968~40869。

詳情如下:

![image-20201022140218438](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201022140218438.png)

具體的使用方法如下:

package main
import "fmt"
func main() {
	a := 97
	x := 19969

	ret1 := string(a)
	ret2 := string(x)
	fmt.Println("ret1>>>",ret1) // a
	fmt.Println("ret2>>>",ret2) // 丁
}

Go語言中不允許字串轉int

package main
import "fmt"
func main() {
	a := "丁"
  // 不能將字串轉為int
	ret1 := int64(a)
	fmt.Println("ret1>>>",ret1) // a
}

會上報下面的錯誤:

cannot convert a (type string) to type int64

2.5 常量

常量是一個簡單值的識別符號,在程式執行時,不會被修改。常量中的資料型別只可以是布林型、數字型(整型、浮點型和複數型)和字串。常量的定義格式如下:

const A string = "wanghw"

const B = "whw"

const C, D = "www", "waa"

常量定義後未被使用,不會在編譯時報錯。

常量用於列舉

Go語言現階段沒有提供列舉,可以使用常量組模擬列舉。

假設數字0、1和2分別代表未知性別、女性和男性。格式如例:

package main
import "fmt"
const (
	Unknown = 0
	Female = 1
	Male = 2
)
func main(){
	fmt.Println("ret>>",Unknown, Female, Male)
	// 結果
	// ret>> 0 1 2
}

常量組中如果不指定型別和初始值,則與上一行非空常量的值相同:

package main
import "fmt"
const (
	a = 10
	b
	c
)
func main(){
	fmt.Println("ret>>",a, b, c)
	// 結果
	// ret>> 10 10 10
}

iota

iota,特殊常量值,是一個系統定義的可以被編譯器修改的常量值。

iota只能被用在常量的賦值中,在每一個const關鍵字出現時,被重置為0,然後每出現一個常量,iota所代表的數值會自動增加1。

iota可以理解成常量組中常量的計數器,不論該常量的值是什麼,只要有一個常量,那麼iota就加1

package main
import "fmt"
const (
	a = 12
	b = iota
	c = iota
)
func main(){
	fmt.Println("ret>>",a, b, c)
	// 結果
	// ret>> 12 1 2
}

常量組中如果不指定型別和初始值,則與上一行非空常量的值相同:

package main
import "fmt"
const (
	a = iota
	b
	c
)
func main(){
	fmt.Println("ret>>",a, b, c)
	// 結果
	// ret>> 0 1 2
}

2.6 類型別名與型別定義

類型別名是Go1.9版本新增的新功能。說到類型別名,無非是給型別名取一個有特殊含義的外號而已,就像武俠小說中的東邪西毒。假如在教室中,有兩個同學叫張三,老師為了區分他們,通常會給他們起個別名:大張三、小張三。對於程式設計而言,類型別名主要用於解決相容性的問題。

![image-20201022201545626](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201022201545626.png)

該語句是將NewString定義為string型別。通過type關鍵字,NewString會形成一種新的型別。NewString本身依然具備string的特性。

type StringAliaa = string

該語句是將StringAlias定義為string的一個別名。使用StringAlias與string等效。別名型別只會在程式碼中存在,編譯完成時,不會有別名型別。

出於對程式效能的考慮,建議如下:

  • 儘可能地使用 := 去初始化宣告一個變數(在函式內部)。
  • 儘可能地使用字元代替字串。

2.7 Go語言運算子

運算子用於在程式執行時執行數學或邏輯運算。

Go語言內建的運算子包括算術運算子關係運算符邏輯運算子位運算子賦值運算子其他運算子

邏輯運算子

Go語言的邏輯運算子如表所示。假定A值為True,B值為False。

![image-20201022201853056](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201022201853056.png)

位運算子

位運算子對整數在記憶體中的二進位制位進行操作

位運算子比一般的算術運算子速度要快,而且可以實現一些算術運算子不能實現的功能。如果要開發高效率程式,位運算子是必不可少的。位運算子用來對二進位制位進行操作,包括:按位與(&)、按位或(|)、按位異或(^)、按位左移(<<)、按位右移(>>)。

Go語言支援的位運算子如表2.15所示。假定A為60,B為13:

![image-20201022202344812](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201022202344812.png)

(其他略)

其他運算子

![image-20201022202518777](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201022202518777.png)

運算子優先順序

![image-20201022202636361](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201022202636361.png)

當然,讀者可以通過使用括號來臨時提升某個表示式的整體運算優先順序。

3 Go語言的流程控制

3.1 流程控制概述

3.2 if條件判斷語句

![image-20201022203808845](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201022203808845.png)

先判斷if的布林表示式,如果為true,其後緊跟的語句塊執行,如果為false,再判斷else if的布林表示式,如果為true,其後緊跟的語句塊執行,如果為false,再判斷下一個else if的布林表示式,以此類推,當最後一個else if的表示式為false時,執行else語句塊。

在if語句的使用過程中,應注意以下細節:

  • 不需使用括號將條件包含起來。
  • 大括號{}必須存在,即使只有一行語句。
  • 左括號必須在if或else的同一行。
  • 在if之後,條件語句之前,可以新增變數初始化語句,使用“;”進行分隔。

![image-20201022203944476](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201022203944476.png)

if的特殊寫法

if語句還有一個變體。它的語法如下所示。

if statement; condition {
  // 程式碼塊
}
package main
import "fmt"

func main(){
	if num := 10; num %2 == 0{
		fmt.Println("偶數")
	} else{
		fmt.Println("奇數")
	}
}
// 結果
// 偶數

需要注意的是,num的定義在if裡,那麼只能夠在該if...else語句塊中使用,否則編譯器會報錯。

3.3 if巢狀語句

![image-20201022204550341](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201022204550341.png)

![image-20201022204716080](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201022204716080.png)

3.4 switch語句

![image-20201022204847524](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201022204847524.png)

switch語句執行的過程自上而下,直到找到case匹配項,匹配項中無須使用break,因為Go語言中的switch預設給每個case自帶break。因此匹配成功後不會向下執行其他的 case 分支,而是跳出整個 switch。可以新增fallthrough(中文含義是:貫穿),強制執行後面的case分支。fallthrough必須放在case分支的最後一行。如果它出現在中間的某個地方,編譯器就會報錯。變數var1可以是任何型別,而val1和val2則可以是同類型的任意值。型別不侷限於常量或整數,但必須是相同型別或最終結果為相同型別的表示式。case後的值不能重複,但可以同時測試多個符合條件的值,也就是說case後可以有多個值,這些值之間使用逗號分隔,例如:case val1, val2, val3。switch後的表示式可以省略,預設是switch true。

示例

![image-20201022205034536](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201022205034536.png)

接下來再看一個案例,判斷某年某月的天數

![image-20201022205101341](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201022205101341.png)

型別轉換

switch語句還可以被用於type switch(型別轉換)來判斷某個interface變數中實際儲存的變數型別。關於interface變數的知識將在後續的章節中介紹。下面演示type switch的語法。其語法結構如下所示。

![image-20201022205240516](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201022205240516.png)

![image-20201022205257559](/Users/wanghongwei/Library/Application Support/typora-user-images/image-20201022205257559.png)

3.5 for迴圈語句

迴圈語句表示當條件滿足時,可以反覆地執行某段程式碼。

for是Go語言中唯一的迴圈語句,Go沒有while、do...while迴圈。

按語法結構來分,Go語言的for迴圈有4種形式,只有第一種使用分號。

for迴圈中for關鍵字後不能加小括號。

語法結構一

for 初始預計init; 條件表示式condition; 結束語句post {
  // 迴圈體程式碼
}

先執行初始語句,對控制變數賦初始值。初始語句只執行一次。

其次根據控制變數判斷條件表示式的返回值,若其值為true,滿足迴圈條件,則執行迴圈體內語句,之後執行結束語句,開始下一次迴圈。

執行結束語句之後,將重新計算條件表示式的返回值,如果是true,迴圈將繼續執行,否則迴圈終止。然後執行迴圈體外語句。

package main
import "fmt"
func main(){
	for i:=0; i<10; i++{
		fmt.Printf("%d ", i)
	}
}
//0 1 2 3 4 5 6 7 8 9 

初始語句、條件表示式和結束語句3種組成部分都是可選的。因此這種基本的for迴圈語法結構又能演化出4種略有不同的寫法。

初始語句是在第一次迴圈前執行的語句,一般為賦值表示式,給控制變數賦初始值。如果控制變數在此處被宣告,其作用域將被侷限在這個for的範圍內——在for迴圈中宣告的變數僅在迴圈範圍內可用。初始語句可以省略不寫,但是初始語句之後的分號必須要寫。

省略初始語句的寫法:

package main
import "fmt"
func main(){
	a := 3
	for ; a<5; a++{
		fmt.Printf("%d ", a)
	}
}
//3 4

條件表示式(condition)是控制迴圈與否的開關:如果表示式為true,則迴圈繼續;否則結束迴圈。條件表示式可以省略不寫,之後的分號必須要寫。省略條件表示式預設形成無限迴圈。

省略條件表示式的寫法:

package main
import "fmt"
func main(){
	a := 3
	for ; ; a++ {
		if a > 5 {
			fmt.Printf("%d ", a)
			break
		}
	}
}
//6

結束語句(post),一般為賦值表示式,使控制變數遞增或者遞減。post語句將在迴圈的每次成功迭代之後執行。

語法結構二

for關鍵字後只有1個條件表示式,效果類似其他程式語言中的while迴圈。其語法結構如下所示。

for 迴圈條件condition{
  // 迴圈體程式碼
}
package main
import "fmt"
func main(){
	var a int
	for a<10{
		fmt.Print(a)
		a ++
	}
}
//0123456789

語法結構三

for關鍵字後無表示式,效果與其他程式語言的for(;