這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
第2章:順序編程
GO語言被稱為"更好的C語言"
1. 變數
1) 變數的聲明GO語言引入了關鍵字 var,而類型資訊放在變數名之後
例如: var v1 int
可以將多個變數聲明放在一起,例如: var ( v1 int v2 string )
2) 變數初始化有3種方式: var v1 int = 10 var v2 = 10 v3 := 10
GO語言引入了新的符號(冒號和等號的組合 :=),用來表達同時進行變數聲明和初始化的工作 但如果 := 左側的變數已經被聲明過了,再使用這個符號編譯會出錯
var i int i := 2 //這樣編譯會報錯
指定類型已經不再是必須的,GO編譯器可以從初始設定式的右值推匯出該變數應該聲明為哪種類型,這讓GO語言看起來有點像動態類型語言(GO語言實際上是不折不扣的靜態類型語言)
3) 聲明變數之後的賦值過程 var v10 int v10 = 123
GO提供了多重賦值功能 比如交換i和j變數的語言,之前的語言都需要引入一個中間變數,但GO可以這樣做: i, j = j, i
4) 匿名變數
2. 常量
在GO語言中,常量是指編譯期間就已知且不可改變的值。常量可以是數實值型別,布爾類型,字串類型。
GO語言的字面常量更接近我們自然語言中的常量概念,是無類型的。只要這個常量在相應類型的範圍範圍內,就可以作為該類型的常量。比如-12,可以賦值給int, uint, int32, float32...等
1) 常量定義 和C一樣,定義常量也是使用 const關鍵字
如果定義常量時沒有指定類型,那麼它與字面常量一樣,是無類型常量。
2) 預定義常量 GO語言預定義了這些常量: true, false和iota iota比較特殊,可以被認為是一個可被編譯器修改的常量,在每一個const關鍵字出現時被重設為0,然後在下一個const出現之前,每出現一次iota,其所代表的數字會自動增1
如果兩個const的賦值語言的運算式是一樣的,那麼可以省略後一個賦值運算式
上面兩個聲明是一樣
3) 枚舉 枚舉指一系列相關的常量,GO語言不支援眾多其他語言支援的enum關鍵字 在const後跟一對圓括弧的方式定義一組常量,這種定義法在GO語言中通常用於定義枚舉值。
GO語言中,以大寫字母開頭的常量在包外可見,numberOfDays為包內私人,其他符號則可被其他包訪問。
3.類型
GO語言內建以下基礎類型: 布爾類型:bool 整型:int8, byte, int16, int, uint, uintptr 浮點類型:float32, float64 複數類型:complex64, complex128 字串:string 字元類型:rune 錯誤類型:error
GO語言支援以下複合類型:
指標(pointer),數組(array),切片(slice),字典(map), 通道(chan) 結構體(struct), 介面(interface)
在這些基礎類型之上GO還封裝了下面幾種類型:int, uint和uintptr等。這些類型的特點在於使用方便,但使用者不能對這些類型的長度做任何假設。對於常規的開發人員來說,用int和uint就可以了,沒必要用int8之類明確指定長度的類型,以免導致移植困難。
1) 布爾類型 布爾類型不能接受其他類型的賦值,不支援自動或強制的類型轉換。
2) 整型 整型是所有程式設計語言裡最基礎的資料類型。 int 和 int32在GO語言裡被認為是兩種不同的類型。編譯器也不會幫你自動做類型轉換
使用強制類型轉換可以解決這個編譯錯誤:
數值運算 GO語言支援下面的常規整數運算:+, -, *, /和% %和在C語言中一樣是求佘運算,比如: 5 % 3 //結果是2
比較運算 GO語言支援以下的幾種比較子:>, <, ==, >=, <=和 !=
兩個不同類型的整型數不能直接比較,比如int8 類型的數和int類型的數不能直接比較,但各種類型的整型變數都可以直接與字面常量進行比較
位元運算 GO語言的大多數位元運算符與C語言都比較類似,除了取反在C語言中是~x,而在GO語言中是^x
3) 浮點型 浮點型用於表示包含小數點的資料。 GO語言定義了兩個類型float32, float64,其中float32等價於C語言中的float類型,float64等價於C語言的double類型 如果這樣定義浮點型: fvalue2 := 12.0 其類型將被自動設為float64,而不管賦給他的數字是不是用32位長度表示的
因為浮點數不是一種精確的表達方式,所以像整型那樣直接用==來判斷兩個浮點數是否相等是不可行的,這可能會導致不穩定的結果。替代方案就是相減後差小於0.00001
4) 字串 在GO語言中,字串也是一種基本類型。
字串的內容可以用類似於數組下標的方式擷取, 字串的內容不能在初始化後被修改
GO語言中的Printf()函數的用法與C語言運行庫中的printf()函數如出一轍。
GO語言只支援UTF-8和Unicode編碼。對於其他編碼,GO語言標準庫並沒有內建的編碼轉換支援。不過,所幸的使我們可以很容易基於iconv庫用Cgo封裝一個。
a. 字串常用的操作有:字串串連,字串長度,取字元
b. 字串遍曆 GO語言支援兩種方式遍曆字串。
以Unicode字元方式遍曆時,每個字元的類型是rune,而不是byte
4. 字元類型 在GO語言中支援兩個字元類型,一個是byte,代表UTF-8字串的單個位元組的值;另一個是rune,代表單個Unicode字元
5. 數組
數組是GO語言中最常用的資料結構之一。
在GO語言中,數組的長度在定義後就不可更改,在聲明時間長度度可以為一個常量或者一個常量運算式。 數組的長度是該數群組類型的一個內建常量,可以用GO語言的內建函數len()來擷取。
a. 元素訪問 使用數組下標來訪問數組中的元素,下標從0開始。 len(array) - 1則表示最後一個元素的下標。 GO語言提供了一個關鍵字 range,用於便捷地遍曆容器中的元素。數組也是range支援的範圍。 range具有兩個傳回值,第一個傳回值是元素的數組下標,第二個傳回值是元素的值。
b. 實值型別 在GO語言中數組是一個實值型別,所有的實值型別變數在賦值和作為參數傳遞時都將產生一次複製動作。如果將數組作為函數的參數類型,則在函數調用時該參數將發生資料賦值。因此,在函數體中無法修改傳入的數組的內容,因為函數內操作的只是所傳入數組的一個副本。
modify()內操作的那個數組跟main()中傳入的數組是兩個不同的執行個體。 如果要在函數內操作外部的資料結構,可以使用數組切片來達成這個目標。
6. 數組切片 數組的特點:數組的長度在定義之後無法再次修改;數組是實值型別,每次傳遞都將產生一份副本。
GO語言另外提供了數組切片(slice)來彌補數組的不足。初看起來,數組切片就像一個指向數組的指標,實際上它擁有自己的資料結構,而不僅僅是個指標。數組切片的資料結構可以抽象為以下3個變數:
- 一個指向原生數組的指標
- 數組切片中的元素個數
- 數組切片已指派的儲存空間
從底層實現的角度看,數組切片實際上仍然使用數組來管理元素。基於數組,數組切片添加了一系列管理功能,可以隨時動態擴充存放空間,並且可以被隨意傳遞而不會導致所管理的元素被重複復制。
a. 建立數組切片 建立數組切片的方法主要有兩種—基於數組和直接建立。
數組的建立如下:
GO語言支援用 myArray[first:last]這樣的方式來基於數組產生一個數組切片,而且這個用法還很靈活,比如以下都是合法的: mySlice = myArray[:] mySlice = myArray[:5] mySlice = myArray[5:]
直接建立: 並非一定要事先準備一個數組才能建立數組切片。GO語言提供的內建函數make()可以用於靈活地建立數組切片。 例子:
當然,事實上還會有一個匿名數組被建立出來,只是不需要我們操心而已。
b. 元素遍曆 運算元組元素的所有方法都適用於數組切片,比如數組切片也可以按下標讀寫元素,用len()函數擷取元素個數,並支援使用range關鍵字來快速遍曆所有元素。
c. 數組切片 可動態增減元素是數組切片比數組切片更為強大的功能。與數組相比,數組切片多了一個儲存能力的概念,即元素個數和分配的空間可以是兩個不同的值。合理的設定儲存能力的值,可以大幅降低數組切片內部重新分配記憶體和搬送記憶體塊的頻率,從而大大提高程式效能。
假設你明確知道當前建立的數組切片最多可能需要儲存的元素個數為50,那麼如果你設定的儲存能力小於50,比如20,那麼元素在超過20時,底層將會發生至少一次這樣的動作:重新分配一塊"夠大"的記憶體,並且需要把內容從原來的記憶體塊複製到新分配的記憶體塊,這會產生比較明顯的開銷。給"夠大"這兩個字加上引號的原因是系統並不知道多大才是夠大,所以只是一個簡單的猜測。比如,將原有的記憶體空間擴大兩倍,但兩倍並不一定夠,所以之前提到的記憶體重新分配和內容複寫的過程很有可能發生多次,從而明顯降低系統的整體效能。但如果你知道最大是50並且一開始就設定儲存能力為50,那麼之後就不會發生這樣非常消耗CPU的動作,從而達到空間換時間的效果。
數組切片支援GO語言內建的cap()函數和len()函數,cap()函數返回的是數組切片分配的空間大小,而len()函數返回的是數組切片中當前所儲存的元素個數。
在mySlice已包含的5個元素的後面繼續新增元素,可以使用append()函數
函數append()的第二個參數是一個不定參數,我們可以按自己需求添加若干個元素,甚至可以直接將一個數組切片追加到另一個數組切片的末尾:
需要注意的是,在第二個參數mySlice2後面加了3個點,即一個省略符號,如果沒有這個省略符號的話,會有編譯錯誤,因為按append()的語義,從第二個參數起所有的參數都是待附加的元素。因為mySlice中的元素類型是int,所以直接傳遞mySlice2是行不通的。加上省略符號相當於把mySlice2包含的所有元素打散後傳入。
上述調用等同於: mySlice = append(mySlice, 8, 9, 10)
數組切片會自動處理儲存空間不足的問題。如果追加的內容長度超過當前已指派的儲存空間,數組切片會自動分配一塊足夠大的記憶體。
d. 基於數組切片建立數組切片 數組切片也可以基於另一個數組切片建立。 有意思的是,選擇的oldSlice元素範圍甚至可以超過所包含的元素個數,比如newSlice可以基於oldSlice的前6個元素建立,雖然oldSlice只包含5個元素。只要這個選擇的範圍不超過oldSlice儲存能力(即cap()返回的值),那麼這個建立程式就是合法的。newSlice中超出oldSlice元素的部分都會填上0
e. 內容複寫 數組切片支援GO語言的另一個內建函數copy(),用於將內容從一個數組切片複製到另一個數組切片。如果加入的兩個數組切片不一樣大,就會按其中較小的那個數組切片的元素個數進行複製。
7. map 在GO語言中,使用map不需要引入任何庫,並且用起來很方便。
a. 變數聲明 var myMap map[string] PersonInfo myMap是聲明的map變數名,string是鍵的類型,PersonInfo則是其中所存放的實值型別。
b. 建立 使用GO語言內建的函數make()來建立一個新的map myMap = make(map[string] PersonInfo) 也可以選擇是否在建立時指定該map的初始儲存能力,例如 myMap = make(map[string] PersonInfo, 100) //初始儲存能力為100
也可以建立並初始化map myMap = map[string] PersonInfo{ "1234": PersonInfo{"1", "Jack", "Room 101, ..."}, }
c. 元素賦值 myMap["1234"] = PersonInfo {"1", "Jack", "Room 101, ..."}
d. 元素刪除 Go語言提供了一個內建函數delete(),用於刪除容器內的元素 delete(myMap, "1234") //從myMap中刪除鍵為"1234"的索引值對,如果"1234"鍵不存在,這個調用將什麼都不發生。如果傳入的map變數值是nil,調用將導致程式拋出異常。
e. 元素尋找 在GO語言中,map的尋找功能設計的很精巧。 判斷能否從map中擷取一個值的常規做法是: (1) 聲明並初始化一個變數為空白 (2) 試圖從map中擷取相應鍵的值到該變數中 (3) 判斷該變數是否依舊為空白,如果為空白則表示map中沒有包含該變數
這種做法比較囉嗦。在GO語言中,從map中尋找一個特定的鍵,可以這樣實現: value, ok := myMap["1234"] if ok { //找到了 //處理找到的value值 }
判斷是否成功找到特定的鍵,不需要檢查取到的值是否為nil,只需查看第二個傳回值ok。
配合 := 操作符, 讓你的代碼沒有多餘的成分,看起來非常清晰易懂。