簡介
Go語言 Go是一種開放源碼的程式設計語言,它意在使得人們能夠方便地構建簡單、可靠、高效的軟體。 Go有時被稱為“類C語言”或“21世紀的C”。從C中,Go整合了運算式文法、控制流程語句、基礎資料型別 (Elementary Data Type)、按值調用的形參傳遞、指標,但比這些更重要的是,繼承了C所強調的程式要編譯成高效的機器碼,並自然地與所處的作業系統提供的抽象機制相配合。
程式結構 一個Go程式儲存在一個或多個以尾碼為 .go 的檔案當中。每個檔案在最開頭包含包(package)聲明,來指定該檔案屬於哪個包。包聲明後面的是匯入(import)聲明,用來匯入程式中用到的庫。匯入聲明後面是包層級(package-level)的聲明,包括類型、變數、常量以及函數的聲明,包層級的聲明在相同檔案和相同包中的其他檔案都可見。在函數中聲明的變數為局部變數,只在函數中可見。 main函數是程式的入口。
例子:
package main // 包聲明import "fmt" // 匯入聲明const pi = 3.14 // 包層級的聲明func main() { // main 同樣是包層級的聲明 var f = 123.456 // 局部聲明 fmt.Printf("f = %g\n", f)}
變數和常量
標識符 Go語言中的命名和其他語言無異:字母、數字、底線的組合,但第一個字元不能是數字。 Go語言中的名字區分大小寫。 不能用關鍵字作為名字。 命名長度沒有限制,但在Go的傳統裡越短越好。可見度越廣,定義越長的名字就越不容易起衝突。 Go習慣用駝峰式命名。例如:parseRequestLine 如果名稱中包含首字母縮減詞,則在命名時不用駝峰式命名。例如:HTMLEscape,escapeHTML而不是escapeHtml
關鍵字
Go有25個關鍵字:
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 |
變數
變數聲明 用var聲明定義變數以及它的類型和初值:var name type = expression
類型或者= expression部分可以省略,但不能兩者同時省略。 如果類型省略,則編譯器根據expression推斷類型。 如果省略= expression,則該變數初始為0值:
- 數字初始為0
- 布爾值初始為false
- 字串初始為空白
- 介面和引用初始為nil
- 數組和結構體將其中的元素或欄位初始為0值
一次聲明多個變數:
var i, j, k int // int, int, intvar b, f, s = true, 2.3, "four" // bool float64 string
多個變數聲明為函數的傳回值:
var f, err = os.Open(name) // os.Open returns a file and an error
如果聲明了一個變數,卻沒有使用它,則會報錯。 簡短的變數聲明 在函數裡可以使用簡短的變數聲明:name := expression,name的類型根據expression推斷。 初始化多個變數:i, j := 0, 1
多個變數聲明為函數傳回值:
f, err := os.Open(name)
多變數聲明時不必所有變數都是新建立的變數,但必須至少有一個是新建立的變數。多變數聲明中已經建立過的變數視為賦值:
// 假設in,err和out都沒有被聲明過in, err := os.Open(infile) // ok, 聲明in和err//...out, err := os.Create(outfile) // ok, 聲明out,給err賦值
// 假設f,err沒有被聲明過f, err := os.Open(infile)//...f, err := os.Create(outfile) // 編譯錯誤,沒有新變數被建立
變數賦值 賦值運算子:= 任何算數運算子或者位元運算符後面接=則組成複合賦值運算子,例如:+=,*=
數值變數可以使用自增和自減運算子是自身的值加1或減1。沒有前置自增。
v := 1v++ // same as v = v + 1; v becomes 2v-- // same as v = v - 1; v becomes 1 again
多重賦值
多重賦值允許一次賦值多個變數:
i, j, k = 2, 3, 5x, y = y, x // 交換兩個變數的值
若函數或操作符返回多個值,則在賦值左側必須用多個變數,個數必須與傳回值的個數一致:
f, err = os.Open("foo.txt") // function call return two values
如果不想用多個函數傳回值中的某個或某幾個,則可以用_代替:
_, err = io.Copy(dst, src)
隱式賦值 隱式賦值發生在函數參數傳遞,函數傳回值等情況。
指標 指標儲存一個變數的地址。指標用*後面加類型的方式表示,例如:*int
用&取一個變數的地址。用*訪問指標所指的變數,例如:
x := 0p := &x*p = 1 // x now is 1
指標的0值為nil
可以返回函數內的局部變數的地址:
var p = f()func f() *int { v := 1 return &v // ok}
new函數
new函數建立一個匿名對象並返回它的地址,這個對象的初始值設為0值:
p := new(int) // p is *int, *p is 0
返回局部變數的地址,與返回new建立的變數等效:
func newInt() *int { return new(int)}// 等效於:func newInt() *int { var dummy int return &dummy}
空標識
空標識 _,用於在文法上需要一個變數,但是邏輯上不需要變數的時候。比如 range for 中不需要的變數, 函數傳回值列表中不需要的傳回值。
a := [3]string{"hello","go","world"}for _, v := range a { // 不需要索引值,以_代替 fmt.Print(v, " ")}_, x := func() (int, int) { // 不需要第一個傳回值,以_代替 return 1, 2}()
變數的可見度 如果一個變數定義在函數裡,那麼它是局部的。 如果一個變數定義在所有函數外,則它在包裡的所有檔案中都可見。並且,如果名稱首字母是大寫,則在包外亦可見。(包的名字總是小寫)
變數的生命期 包變數(全域變數)的生命期與程式的生命期一致。局部變數的生命期從生命開始到該變數不可見為止。 Go語言有記憶體回收機制,因此不必擔心記憶體的開闢和釋放問題。 變數是在堆上還是在棧上建立取決於該變數的生命期。
若函數返回局部變數的地址,則該變數被分配在堆上。 若變數僅在局部(函數體內,迴圈體內,if語句內等等)使用,則該變數分配在棧上,即使是用new()函數建立的變數也是如此。 函數是被建立在棧上還是堆上由編譯器決定,無需程式員幹預。
詞塊 範圍表示一個名字的作用範圍。 詞塊(lexical block)表示一個作用範圍,該範圍內限定了名字的範圍。以下範圍都稱為詞塊:整個原始碼(又稱為全域塊(universe block))、包、檔案、函數、for語句內、if語句內、switch語句內、case語句內,每一對大括弧內。 在不同詞塊內可聲明同一個名字,內部詞塊中的名字會隱藏外部詞塊中的名字,但這是一種不好的風格。
常量
常量的聲明 用const聲明常量:const pi = 3.14159
聲明多個常量用括弧括起:
const ( e = 2.718281 pi = 3.14159)
常量可用作數組的維度:
const len = 4var p [len]byte
const聲明一組常量時除第一常量以外可以分配預設值,該值與上一個常量一致:
const ( a = 1 b // b is 1 c = 2 d // d is 2)
常量產生器iota
在聲明一組常量時,可以使用iota 組成的運算式給第一個常量賦值,第一個常量之後的每一個常量都應用這個運算式,但iota的值從0開始,每到下一個常量便加一,通常以這種方式賦值的常量又叫枚舉。
const ( a = iota // a is 0 b // b is 1 c // c is 2 d // d is 3)const ( flag1 = 1 << iota // flag1 is 1 (1 << 0) flag2 // flag2 is 2 (1 << 1) flag3 // flag3 is 4 (1 << 2) flag4 // flag4 is 8 (1 << 3))
無類型常量
沒有冠以類型的常量為無類型常量,無類型常量在被賦予類型之前要比有類型常量有更大的精度(通常精度可高達256位),並且能參與精度更大的計算:
const ( a = 1 << (100 + iota) b // b為100位的整數,已經超過了最大的uint64的值 c // c為101位的整數 )fmt.Printf("%d", c/b) // print 2
無類型常量根據常量的字面形式,分為無類型整數,無類型浮點數,無類型bool值,無類型rune,無類型複數:
const ( a = 100 // 無類型整數 b = 1.0 // 無類型浮點數 c = true // 無類型bool值 d = '\u1234' // 無類型rune e = 3 + 2i // 無類型複數)
基礎資料型別 (Elementary Data Type)
整數
整數類型
類型 |
符號 |
位元 |
範圍 |
說明 |
int8 |
有符號 |
8 |
−2n−1−1∼2n−1−1 -2^{n-1}-1 \sim 2^{n-1}-1 |
|
int16 |
16 |
int32 |
32 |
rune |
32 |
rune是int32的別名, 用來表示Unicode碼點(code point)。 |
int64 |
64 |
|
uint8 |
無符號 |
8 |
0∼2n−1 0 \sim 2^n-1 |
uint16 |
16 |
uint32 |
32 |
uint64 |
64 |
int |
有符號 |
。 |
。 |
不同的編譯器根據不同的平台設定不同的大小, 以達到最高效的利用硬體。 |
uint |
無符號 |
uintptr |
無符號 |
|
|
uintptr是一個無符號整型, 僅用來相容低級語言(比如C)。 |
整型除了可以用10進位表示外,還可以用8進位和16進位表示。8進位前加0,16進位前加0x或者0X。
操作符
可用於整型的操作符和優先順序如下(優先順序從高到低,每行優先順序相同):
* |
/ |
% |
<< |
>> |
& |
&^ |
+ + |
− - |
| |
^ |
|
== |
!= |
< |
<= |
> |
>= |
|
&& |
|
|| |
|
前兩行的操作符都有一個對應的複合賦值操作符,比如+對應+= 算術運算子+ - * /可用於整型、浮點型、複數。 模數運算子(%)僅可用於兩個整型。如果模數時有負數,則最後的符號由被除數決定,比如:-5%3 與 -5%-3 結果都為 -2 算術運算子的結果可能超出運算元的類型的範圍。這就引發了溢出。運算時應注意選用合適的類型做運算,以免溢出。 比較子 == != < <= > >=可應用於兩個類型相同的基基本類型(整型、浮點型、字串),結果為bool類型。 + - 亦可作為一元運算子,表示正負。
Go提供了如下的位元運算符:
& |
按位與 |
| |
按位或 |
^ |
異或 |
&^ |
與非 |
<< |
左位移 |
>> |
右位移 |
^亦可作為一元運算子,表示按位取反。 &^表示與非,例如:z = x &^ y,如果y的某一位為1,則z的該位為0,否則z的該位和x的該位一致。 << 與 >>的右運算元必須為無符號數。>>的左運算元若為有符號數,則向右移的時候用符號位填補空位。
列印整型
在使用fmt.Printf()列印整型時,10進位、8進位、16進位分別用 %d %o %x(或%X表示大寫) :
o := 0123fmt.Printf("%d %[1]o %#[1]o\n", o) // 83 123 0123x := int64(0xABC)fmt.Printf("%d %[1]x %#[1]x %#[1]X\n", x) // 2748 abc 0xabc 0XABC
# 表示列印8進位或16進位開頭的字元 []內的數字表示要從列印序列中取出第幾個運算元
列印rune類型用 %c(不帶引號)或者 %q(帶引號),列印出的是Unicode字元:
ch := '中'ch2 := '國'fmt.Printf("%d %[1]c %d %[2]q\n", ch, ch2) // 20013 中 22269 '國'
浮點數
浮點數類型
類型 |
精度 |
最大值 |
最小值 |
float32 |
6位 |
math.MaxFloat32 |
math.MinFloat32 |
float64 |
15位 |
math.MaxFloat64 |
math.MinFloat64 |
列印浮點數 用%f列印浮點數
特殊值:
特殊值 |
意義 |
例子 |
+Inf |
正無窮 |
5.0/0.0 |
-Inf |
負無窮 |
-5.0/0.0 |
NaN |
Not a Number |
0/0 |
math.IsNaN 測試一個值是否為NaN math.NaN 返回NaN NaN與NaN比較為false
複數 Go語言內建複數類型。有兩種複數類型:complex64 complex128,complex64的實部和虛部位float32,complex128的實部和虛部是float64。 用complex()建立一個複數類型,real()和imag()返回實部和虛部。虛部的字面值以數字後面加i表示。 Go支援複數的四則運算。比較操作符支援:== 和 !=
布爾值 Go語言的bool值僅有true和false。 以下操作產生bool值: 比較操作符:> >= < <= == !=),邏輯非:! 兩個產生bool值的運算式可以用邏輯與(&&)和邏輯或(||)連在一起,併產生短路行為(貌似所有的語言都是如此,至少我所知的是如此)。
不能將數值或指標類型隱式轉換成布爾值,反之亦然。
字串 字串是一串byte序列。用string表示。 字串通常以UTF8編碼儲存Unicode碼點(rune)。 len()函數返回字串中byte的個數,
非Unicode字元的個數。假設s是字串,則下標操作符s[i]返回字串中第i個byte的值,
非第i個Unicode字元。 Go支援字串切片:s[i:j]返回從第i個byte開始到第j個byte結束(不包含第j個byte)的字串。切片時可以省略i和j當中的任意一個或全部。省略i則從0開始,省略j則到len(s)結束,都省略則表示從0到len(s)的字串(即本身)。 string支援+操作符以拼接字串。支援+=以拼接新的字串並賦值給變數自身。 string支援比較操作符。以byte為單位逐一比較。 字串是不能改變的。例如 s[0]='v'得到編譯錯誤。正是因為字串不能改變,切片所返回的字串可以和原字串共用同一段記憶體,以提高效率。
字串字面值 雙引號(”)或反引號( `)括起的byte序列為字串字面值。
雙引號字串中轉義以\開始的字元 反引號字串中不轉義以\開始的字元 逸出字元可以用16進位(以\x開頭,後面接兩個16進位字元,比如\xAB)或者8進位(以\開頭,比如\377)表示。
常用的逸出字元:
字元 |
意義 |
\n |
換行 |
\r |
斷行符號 |
\t |
定位字元 |
\’ |
單引號 |
\” |
雙引號 |
\\ |
反斜線 |
反引號括起的字串叫做原始字串(raw string),其中以\開始的字元不轉義。原始字串可以跨越多行,但是Go會自動去掉其中的斷行符號(\r),以使原始字串在Linux和Windows下保持一致。原始字串可以用在Regex中以避免過多的轉義。 因為Go程式源碼總是儲存為UTF8,而字串也通常也以UTF8解析。所以可以在字串字面值中寫任何Unicode碼點。
UTF-8 UTF-8以變長的方式編碼Unicode。 Go的原始碼以UTF-8方式儲存。源碼中的字串也是以UTF-8方式處理。 Unicode轉義:16位:\uhhhh;32位:\Uhhhhhhhh
只有小於256的單個16進位數可以轉義為一個rune,否則只能通過/u或/U的方式來轉換:
var a rune = '\x41' // oka = '\xe4\xb8\x96' // errora = '\u4e16' // ok
unicode/utf8包裡提供了UTF-8和rune相關的函數:
DecodeRuneInString接受一個字串,並返回該字串的第一個字元的rune值以及表示這個rune需要的byte數量:
s := "你好"r, size := utf8.DecodeRuneInString(s)fmt.Printf("%q is %d bytes", r, size) // 列印: '你' is 3 bytes
RuneCountInString返回字串中rune的個數
s := "你好"fmt.Printf("rune count: %d\nbyte count: %d", utf8.RuneCountInString(s), len(s))// 列印:// rune count: 2// byte count: 6
range for 自動解碼UTF-8字串為tune序列:
s := "你好"for i, r := range s { fmt.Printf("%d: %q\n", i, r)}// 列印:// 0: '你'// 3: '好'
Go的解碼器在解碼UTF-8時,如果遇到非法字元序列,則轉換為\uFFFD,這個字元一般是一個多邊形的黑塊中間有個。的字元:� rune與UTF-8字串互轉:
string轉為[]rune會自動解碼,得到相應的rune序列。 []rune轉為string會自動編碼,得到相應的UTF-8字串。 數字轉string會將數字先轉為rune,然後對該rune進行編碼產生相應的字串。
複合類型
數組 數組是一個或多個
同類型元素組成的
固定長度的序列。表示為[num]T,其中num是元素個數,T是類型,例如:[3]int 表示元素個數為3的int數組。
數組的聲明:
var a [3]int // 建立一個由3個int值組成的數組
數組初始化
用數組字面值初始化:
var a [3]int = [3]int{1, 2, 3}
未被顯式初始化的元素被隱式初始化為0值:
var a [3]int = [3]int{1, 2} // a[2] is 0var b [3]int // 所有元素都為0
若以 ... 指定數組長度,則由初始化列表中元素的個數決定數組真實長度:
q := [...]int{1, 2, 3} // q is [3]int
初始化時可以指定下標:
a = [...]string{1:"he