# [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/)