1、名字由來
語法糖(Syntactic sugar)的概念是由英國電腦科學家彼得·蘭丁提出的,用於表示程式語言中的某種型別的語法,這些語法不會影響功能,但使用起來卻很方便。
語法糖,也稱糖語法,這些語法不僅不會影響功能,編譯後的結果跟不使用語法糖也一樣。
語法糖,有可能讓程式碼編寫變得簡單,也有可能讓程式碼可讀性更高,但有時也會給你一個意外讓您的程式碼出問題。Golang中的語法糖語法有很多,本文將講解Golang中常用的語法糖。
2、Golang常用語法糖
2.1 簡短變數宣告 :=
規則:簡短變數宣告符這個語法糖使用起來很方便,導致你可能隨手就會使用它定義一個變數,往往程式的bug就是隨手寫出來的,在這裡說一下簡短變數宣告的原理和規則。
(1)多變數賦值可能會重新宣告
使用 :=
一次可以宣告多個變數,例如:
i, j := 0, 0 j, k := 1, 1
(1)使用舉例-不傳值
呼叫可變參函式時,可變參部分是可以不傳值的,例如:
func ExampleGreetingWithoutParameter() { sugar.Greeting("nobody") // OutPut: // Nobody to say hi. }
這裡沒有傳遞第二個引數。可變引數不傳遞的話,預設為nil。
(2)使用舉例-傳遞多個引數
呼叫可變參函式時,可變引數部分可以傳遞多個值,例如:
func ExampleGreetingWithParameter() { sugar.Greeting("hello:", "Joe", "Anna", "Eileen") // OutPut: // hello: Joe // hello: Anna // hello: Eileen }
可變引數可以有多個。多個引數將會生成一個切片傳入,函式內部按照切片來處理。
(3)使用舉例-傳遞切片
呼叫可變參函式時,可變引數部分可以直接傳遞一個切片。引數部分需要使用slice...
來表示切片。例如:
func ExampleGreetingWithSlice() { guest := []string{"Joe", "Anna", "Eileen"} sugar.Greeting("hello:", guest...) // OutPut: // hello: Joe // hello: Anna // hello: Eileen }
此時需要注意的一點是,切片傳入時不會生成新的切片,也就是說函式內部使用的切片與傳入的切片共享相同的儲存空間。說得再直白一點就是,如果函式內部修改了切片,可能會影響外部呼叫的函式。
2.3 new函式
在 Go 語言中,new 函式用於動態地分配記憶體,返回一個指向新分配的零值的指標。它的語法如下:
func new(Type) *Type
其中,Type 表示要分配的記憶體的型別,new 函式返回一個指向 Type 型別的新分配的零值的指標。但是需要注意的是,new 函式只分配記憶體,並返回指向新分配的零值的指標,而不會初始化該記憶體。
所謂零值,是指 Go 語言中變數在宣告時自動賦予的預設值。對於基本型別來說,它們的零值如下:
- 布林型:false
- 整型:0
- 浮點型:0.0
- 複數型:0 + 0i
- 字串:""(空字串)
- 指標:nil
- 介面:nil
- 切片、對映和通道:nil
因此,new 函式返回的指標指向新分配的零值,但不會將其初始化為非零值。如果需要將記憶體初始化為非零值,可以使用結構體字面量或者顯式地為其賦值。例如:
package main import "fmt" type Person struct { name string age int sex int } func main() { // 使用 new 函式分配記憶體,但不會將其初始化為非零值 p := new(Person) fmt.Println(p) // 輸出:&{ 0 0} // 使用結構體字面量初始化 p2 := &Person{name: "Tom", age: 18, sex: 1} fmt.Println(p2) // 輸出:&{Tom 18 1} // 顯式為欄位賦值 p3 := new(Person) p3.name = "Jerry" p3.age = 20 p3.sex = 0 fmt.Println(p3) // 輸出:&{Jerry 20 0} }
上面的程式碼中,使用 new 函式分配了一個新的 Person 結構體,但不會將其初始化為非零值,因此輸出結果是"空字串 0 0"。接下來,使用結構體字面量或者顯式為其賦值,將其初始化為非零值。
注意 1:p3 := new(Person) 返回是指向新分配的Person型別物件零值的指標,按照我們對指標語法的瞭解,基於p3顯示賦值的話需要使用如下語法進行賦值:
(*p3).name = "Jerry" (*p3).age = 20 (*p3).sex = 0而我們在對指標型別結構體物件賦值的時候一般都很少會帶著*,這也是Go指標語法糖為我們做的簡化,這部分在後文會詳細介紹。
注意 2:new函式更多細節介紹,請參見《Go語言new( )函式》這篇博文。
很明顯,new函式的設計同樣是為了方便程式設計師的使用。
2.4 宣告不定長陣列
我麼都知道陣列長度是固定的,所以在宣告陣列的時候都要指定長度,Go裡提供了一種偷懶的宣告方式,即使用...
運算子宣告陣列時,我們只管填充元素值,其他的由Go編譯器來處理。
// Go的實現:陣列長度是4,等同於 a := [4]{1, 2, 3, 4} a := [...]int{1, 2, 3, 4}
有時我們想宣告一個大陣列,但是某些index想設定特別的值也可以使用...運算子搞定:
a := [...]int{1: 20, 999: 10} // 陣列長度是100, 下標1的元素值是20,下標999的元素值是10,其他元素值都是0
2.5 init函式
Go語言提供了先於main函式執行的init函式,初始化每個包後會自動執行init函式,每個包中可以有多個init函式,每個包中的原始檔中也可以有多個init函式,載入順序如下:
從當前包開始,如果當前包包含多個依賴包,則先初始化依賴包,層層遞迴初始化各個包,在每一個包中,按照原始檔的字典序從前往後執行,每一個原始檔中,優先初始化常量、變數,最後初始化init函式,當出現多個init函式時,則按照順序從前往後依次執行,每一個包完成載入後,遞迴返回,最後在初始化當前包!
init函式實現了sync.Once,無論包被匯入多少次,init函式只會被執行一次,所以使用init可以應用在服務註冊、中介軟體初始化、實現單例模式等等,比如我們經常使用的pprof工具(Go效能分析工具),它就使用到了init函式,在init函式里面進行路由註冊:
//go/1.15.7/libexec/src/cmd/trace/pprof.go func init() { http.HandleFunc("/io", serveSVGProfile(pprofByGoroutine(computePprofIO))) http.HandleFunc("/block", serveSVGProfile(pprofByGoroutine(computePprofBlock))) http.HandleFunc("/syscall", serveSVGProfile(pprofByGoroutine(computePprofSyscall))) http.HandleFunc("/sched", serveSVGProfile(pprofByGoroutine(computePprofSched))) http.HandleFunc("/regionio", serveSVGProfile(pprofByRegion(computePprofIO))) http.HandleFunc("/regionblock", serveSVGProfile(pprofByRegion(computePprofBlock))) http.HandleFunc("/regionsyscall", serveSVGProfile(pprofByRegion(computePprofSyscall))) http.HandleFunc("/regionsched", serveSVGProfile(pprofByRegion(computePprofSched))) }
注意 1:Go中main函式和init函式的呼叫鏈關係圖可以參見《(轉)Go中的main函式和init函式》這篇博文。
2.6 忽略導包
Go語言在設計師有程式碼潔癖,在設計上儘可能避免程式碼濫用,所以Go語言的導包必須要使用,如果導包了但是沒有使用的話就會產生編譯錯誤,但有些場景我們會遇到只想導包,但是不使用的情況,比如上文提到的init函式,我們只想初始化包裡的init函式,但是不會使用包內的任何方法,這時就可以使用 _ 運算子號重新命名匯入一個不使用的包:
import _ "net/http/pprof" import _ "github.com/go-sql-driver/mysql"
注意 1:忽略導包的詳細使用可以參見《Golang中下劃線的使用》這篇博文。
2.7 忽略欄位
這個應該是最簡單的用途,比如某個函式返回三個引數,但是我們只需要其中的兩個,另外一個引數可以忽略,這樣的話程式碼可以這樣寫:
v1, v2, _ := function(...)