# [Golang] 初見Golang - 基礎篇 ###### tags: `Golang` :::info :bookmark: 筆者有寫過C、C++、C#的經驗,以下內容是與這三個語言進行的比較。**目標是為讓已經*有過其他語言經驗*的人更快上手** ::: ## 1. `{}`的宗教戰爭 想必各位看過在程式開發者發生過的「宗教戰爭」,`TAB`派 vs. `SPACE`派;大括號`{}`寫在函式的同一行?還是下一行。雖然聽起來很無聊,但程式設計師們真的會因為這是吵架,但是`Golang`是一門狠毒的語言,他連讓我們吵的機會都沒有,剝奪了我們的權利。 ==`Golang`的大括號**必須**寫在與函式同一行,不然強行報錯== ```go= // 價值正確 func() { } // ...價值不夠,需儲值 func() { } ``` ## 2. 「我的話已經說完了」 開頭有說過,最常寫的是`C`語言家族,在語句結束後需要加`;`表示結束,但`Golang`不需要。 PS 這不表示`Golang`沒有`;`,在下面第六點`for`迴圈時會講到 ## 3. 入口在哪裡 ```go= package main func main() { } ``` `function main`並不陌生,但它一定得在`package main`裡面,如果你像筆者一樣是由`C`語言家族來的,會認為它只有一個入口,編譯下去會把所有文件編譯完之後進到`main`,然後程式就動了起來,但`Golang`不一樣的地方在於,你應把它視為一個資料夾唯一個模塊(module),像`Python`那樣。 PS 一個資料夾為一個模塊,`Golang`並沒有硬性規定,但建議這麼做,會讓專案乾淨很多。 ## 4. `using namespace` 第三點提到`package main`中的`main`,可以把它想像成它就是在`C++`和`C#`中的`namspace` 如果要引用別的模塊: ```go= package main import "fmt" func main() { fmt.Println("Hello, Golang!") } ``` 假若是要引用多個module呢? ```go= package main import ( "fmt" "time" ) // time,表示time模塊裡的Now函式 // 如果轉換成C++類似: time::Now() func main() { fmt.Println("Hello, Golang! " + time.Now()) } ``` ## 5. 變數宣告 變數宣告有三種: ```cpp= var name type = val // #1 var name = val // #2 name := val // #3 ``` 這裡的`var`是`Golang`的關鍵字,`type`是指類型,不是內建的關鍵字,怎麼使用`type`關鍵字會在下面講到 第一種是合法的,但是有點多此一舉,通常會是這樣用: ```go= // 宣告一個變數,但不初始化他 var name type //如果你要需很多變數 var ( name1 type1 name2 type2 name3 type3 ) ``` 如果宣告一堆變數,但型別都相同 ```go= // 我覺得可讀性不佳 var name1, name2, name3 type ``` 第二種和第三種都是開發者宣告變數,然後進行賦值,判斷型別就給編譯器處理。 **Q:** `=`跟`:=`的差別 **A:**`=`是賦值,`:=`是宣告變數並賦值 [[原文點這裡]](https://segmentfault.com/q/1010000007160096) ## 6. 常量與enum ### 常量(const) ```go= // 跟變數宣告一樣,但宣告後就必須賦值 const ( Monday, Tuesday, Wednesday = 1, 2, 3 Thursday, Friday, Saturday = 4, 5, 6 ) ``` 對於enum,嗯?!**`Golang`沒有提供enum**,可以用常量代替enum ## 7. Unused variable 在`C`、`C++`、`C#`中,如果有沒有使用的變數,編譯器會給出warning(當然你也可以把級別往上調),但在`Golang`中,編譯器查到有沒有用的變數會直接報錯,對!它就是這麼摳,一個多的變數都不准出現。 ## 8. `if` `for` `while` `switch`流程控制 ```go= // if...else... im := "handsome" if you := im; you == "handsome" { fmt.Println("You say you are " + you + ", 不要臉") } else if you == "ugly" { fmt.Println("You say you are " + you + ", 真誠實") } else { fmt.Println("你甚麼都不是") } // for for i := 0; i < 10; i++ { fmt.Println("I'm genius.") } // switch today := Friday switch today { case Monday: case Tuesday: case Wednesday: case Thursday: case Friday: fmt.Println("Work, Work, Work") case Saturday: case Sunday: fmt.Println("Play, play, play") default: fmt.Println("You are alien, right?") } ``` 發現到`if`、`for`、`switch`的條件句沒有小括號包起來。 等一下!那`while`呢?還有最常用到的`while(true)`無限loop呢? `Golang`沒有提供`while`關鍵字,至於無限loop,請見: ```go= for { // 我想要出去!FREE ME!!! break } ``` 當然`Golang`也有`break`和`continue`關鍵字 ## 8. 函式的模樣 長這樣 :point_down: ```go= func MyFunctionName(para type) type { type name // process data return name } ``` ## 10. 可以回傳「不只一個」值 在C語言家族中,如果你想回傳多個值會把你要的類型再包成一個結構,或是用`tuple`,但在`Goalng`你可以直接回傳N個值: ```go= func MyFunctionName(para type) (type1, typ2) { type1 name1 type2 name2 // process data return name1, name2 } ``` 調用函式長這樣 :point_down: ```go= a1, b1 := MyFunction(some_para) ``` ## 11. 你回傳的,我不要 根據上述的一點,如果定義出來的變數沒有使用,會是error,於是`Golang` 使用底線(`_`)代表不使用的回傳值,一樣我們使用上面的例子: ```go= a1, _ := MyFunction(some_para) // 你不可以這樣 _, _ := MyFunction(some_para) ``` ## 12. 資料包起來 這樣寫 :point_down: ```go= type Vec2 struct { x float64 y float64 } type Entity struct { name string pos Vec2 } // ... 宣告並定義結構 point := Vec2{ x: 32.0, y: 45.0, } poka := Enity { name: "POKA", pos: point, } ``` 還有一個功能較匿名結構,我們假設一個匿名結構有一個`int`型別的`id`和一個`Enity` ```go= poka := struct { id uint32 e Entity }{ id: 23, e: Entity{ name: "POKA", pos: point, }, } ``` ## 13. 他有指標! 這聽起來很恐怖,但是比起在`C`和`C++`中作的奇淫邪技,`Golang`的指標簡單了不少。 第一`Golang`有GC的機制所以不是開發者主動釋放記憶體,第二指標不能做運算。 與`C`和`C++`一樣有分為一般指標和動態配置 ```go= // 一般指標 n := 100 nPtr := &n // 動態配置 nPtr := new(int) *nPtr = 100 ``` 通常動態配置對複合型別(自定義的結構) ```go= type User struct { id int name string } // ... user := new(User) ``` 除了`new`以外,還有一個`make`函式也是用在動態配置上: [詳情文章](https://medium.com/d-d-mag/golang-%E7%AD%86%E8%A8%98-make-%E8%88%87-new-%E7%9A%84%E5%B7%AE%E5%88%A5-68b05c7ce016) ### ~~Pass by value/reference~~ ==ERROR== ~~當然有指標函式的參數就會有傳值(pass by value)和傳址(pass by reference)~~ ```go= func ChangeNameByVal(u User, n string) { u.name = n } func ChangeNameByRef(u *User, n string) { u.name = n } func main() { user := User{ id: 999, name: "POKA", } // 不會變 ChangeNameByVal(user, "Charlie") fmt.Println(user.name) // 會變 ChangeNameByRef(&user, "Charlie") fmt.Println(user.name) } ``` :::warning :warning: 雖說Golang有指標,但是並們有所謂的「Pass By Reference」,跟C一樣只有「Pass By Value」,事實上仔細從函式傳遞參數的方式就略知一二,跟C的方法完全一模一樣。 > 2020.11.09 ::: ## 14. 在函式結束的時候 `defer`寫在函式調用前面表示該還是在`return`**之後**才會被執行 ```go= func main() { defer fmt.Println("World") fmt.Println("Hello") } // Output: // Hello // World ``` [這裡](https://stackoverflow.com/questions/52718143/is-golang-defer-statement-execute-before-or-after-return-statement)有更詳細的範例及回答 通常這會用在資源釋放的時候,例如:讀取IO文件、切斷連線、互斥鎖(Mutex) ## 參考資料 - [使用指標 (Pointer)](https://michaelchen.tech/golang-programming/pointer/#%E5%BB%BA%E7%AB%8B%E6%8C%87%E6%A8%99) - [Pass by pointer vs pass by value in Go](https://goinbigdata.com/golang-pass-by-pointer-vs-pass-by-value/) - [defer、panic、recover](https://openhome.cc/Gossip/Go/DeferPanicRecover.html) - [golang 匿名 struct 的使用方式](https://javasgl.github.io/go-anonymous-struct/)