學程式先學基礎,就跟學武功先蹲馬步一樣,不容易出錯,出錯了也容易找到問題,所以就先從 Go 的基礎開始吧

這篇筆記包含了套件(Packages),變數(Variables)和函式(Functions),原則上瞭解了這些項目,就能看懂別人寫的Go程式了,事不宜遲,我們就開始吧

套件(Packages)

在Go語言裡,可以把套件(Package)視為物件導向裡的類別(Class),同時在同一個資料夾裡的所有go檔案,都會在第一行宣告package xxx,然後套件名稱則會使用該資料夾的名稱

例如,我們在/Users/papajames/go/src/hello下面有三個go檔案,它們都會在第一行有著下面的程式碼

1
package hello

那如果我們使用了不同的套件名稱的話,在建置時會看到下面的錯誤訊息

1
2
can't load package: package hello: found packages hello (hello.go)
and world (world.go) in /Users/papajames/go/src/hello

不過有個例外就是,假設我們需要建置一個可執行檔時,那就需要在go檔裡宣告套件main,同時在同一個檔案裡加上函式main,如下

1
2
3
package main
func main() {}

這樣在建置時,Go Tools就會幫我們建置一個可執行檔,然後檔名是所在資料夾的名稱

匯入(Imports)

當我們想要使用別人寫好的套件時,就需要用到匯入(import)這個指示詞,我們可以用小括號一次匯入多個套件,如下:

1
2
3
4
import (
"fmt"
"math"
)

也可以一個一個匯入,如下:

1
2
import "fmt"
import "math"

官方的教學是建議使用第一個,一次匯入多個套件的語法

公用名稱(Exported names)

Exported names就字典翻譯來說,是"導出的名稱",但因為我是學了物件導向幾多年了,所以更喜歡用"公用名稱"來解讀它

但我們在一個套件裡宣告了變數跟函式時,Go用了一個很簡單的方式來決定它們的存取能力,成員名稱是以大寫的英文字母為開頭的話,表示它可以在套件以外的範圍存取,但如果成員名稱用小寫的英文字母為開頭的話,則它只能在套件內被存取,很簡單的表達方式,也表示我們在寫Go的程式時,不需要為變數跟函式指定存取修飾詞(public/protected/private)給它們了

1
2
func hello() {}
func World() {}

透過上面的程式碼,我們可以知道函式hello()只能在套件裡存取,等同於私有函式,而函式World()則可以在套件之外被存取,等同於公有函式

函式(Functions)

Go的函式是用func來宣告,同時接受零到多個參數,不過比較特別的是,參數型別是宣告在參數名稱之後,另外該函式有回傳值的話,則在最後加上回傳型別,如下

1
2
3
func add(x int, y int) int {
return x + y
}

對於熟悉C#的我來說,真的是很特別的陳述式,想知道為什麼要這麼設計的人,可以參考這個連結,https://blog.golang.org/gos-declaration-syntax

另外官方文件有提到另外一個更特別的陳述式,當有兩個以上的連續參數擁有相同的型別時,我們只需要在最後的連續參數宣告型別即可,所以可以把上面的程式碼改寫成

1
2
3
func add(x, y int) int {
return x + y
}

也太特別了吧,沒看過的人我想一時也不知道它在陳述什麼東西吧

多個結果(Multiple results)

Go支援一個函式可以同時回傳多個結果,範例如下

1
2
3
4
5
6
7
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("hello", "world")
}

可以看到當我們要一次回傳多個結果時,在宣告回傳型別時就定義多個型別,同時在回傳時就把要回傳的變數依照順序排好即可,另外在呼叫函式時,也把要接收的變數依序排好,這樣就能收到多個結果

嘖嘖,這可是C# 7.0才有的功能呢

有名稱的回傳值(Named return values)

Go的回傳值是可以被命名的,如果我們使用了這個特性,那這些回傳值會在函式的一開始就會先被定義好,範例如下

1
2
3
4
5
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}

我們可以看到上面的return陳述式沒有包含任何一個有名稱的回傳值,這樣的方式官方稱為Naked Return,不過為了降低它對程式碼可讀性的影響,只建議用在短一點的函式,如上面這一個,長一點的函式就不適合了

變數(Variables)

在Go語言裡面,使用var來宣告一到多個變數,同時跟函式參數一樣,型別在名稱後面,連續參數相同型別的話只要在最後一個宣告即可,如下

1
2
3
4
5
6
var a int, b string
var c, d, e bool
func main() {
var i int
}

變數分為套件範圍與函式範圍,有著存取範圍的限制,函式可以存取套件變數但不能存取另一個函式的變數,同時適用於公用名稱(Exported names)的規則

另外變數的宣告可以包含初始值,每個變數都要有,同時如果有指定初始值的話,我們可以忽略變數的型別宣告,Go會幫我們從初始值找到變數的型別

1
2
3
4
5
var a, b int = 1, 2
func main() {
var c, d, e = true, false, "oh"
}

透過上面的程式碼可以看到,Go幫我們宣告變數cd的型別為bool,而變數e則為string

### 簡潔變數宣告(Short variable declarations)

在函式裡,當變數型別為隱含型別可以被推論時,我們可以使用簡潔指定陳述式:=來取代var

1
2
3
4
5
func main() {
var a, b int = 1, 2
x := 3
c, d, e := true, false, "oh"
}

而在函式之外的話,簡潔指定陳述式:=是不被允許的

### 基本型別(Basic types)

Go的基本型別有

  • bool
  • string

以下為數字型別

  • int / int8 / int16 / int32 / int64
  • uint / uint8 / uint16 / uint32 / uint64 / uintptr
  • byte (與uint8相同)
  • rune (與int32相同, 用來表示Unicode代碼位置)
  • float32 / float64
  • complex64 / complex128

常見的就不多說明了,而rune算是個很特別的型別,它是用來儲存文字的Unicode代碼位置,至於float32float64的區別,可以把它們依序視為singledouble,至於complex64complex128和uintptr的話,不是很清楚它們適用在什麼狀況下,未來有機會遇到再記下來吧

另外int,uintuintptr則會視所在的系統而有所不同,在32位元的系統下,它們的大小為32位元,而如果在64位元下,大小則為64位元

官方文件裡有提到,一般來說當我們需要用到整數時,建議直接使用int就可以,除非有特殊需求,不然不需要特別指定有明確大小的整數或無符號整數的型別

預設值(Zero values)

當我們宣告變數但沒有給它們初始值時,Go會幫我們指定預設值,如下

  • 數字型別,指定0
  • 布林型別,指定false
  • 字串型別,指定空字串""

型別轉換

我們可以使用表達式T(v)將變數v轉換成型別T,如下

1
2
3
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

或者在函式裡,我們可以用更簡潔的陳述式

1
2
3
i := 42
f := float64(i)
u := uint(f)

在Go語言裡,型別轉換需要明確指定型別,無法做到像其它語言的隱含型別轉換,例如JavaScript

型別推論(Type inference)

當宣告變數但沒有明確宣告型別時,Go語言會幫我們從陳述式的右邊推論出適合的型別,如下

1
2
3
4
5
var a int
b := a // 型別為 int
c := 12 // 型別為 int
d := 1.2 // 型別為 float64
e := 1.2 + 1i // 型別為complex128

定數(Constants)

定數需要使用const來宣告,可以是字元,字串,布林值或數字,但不能使用:=語法,如下

1
2
3
const Pi = 3.14
const World = "世界"
const Truth = true

數字定數(Numeric constants)

數字定數是高精度的值,任何沒有明確型別的數字定數會依照它的本文進行型別轉換,如下

1
2
3
4
5
6
7
8
const x = 1
func needInt(a int) {}
func needFloat(b float64) {}
func main() {
needInt(x) // 轉換成int
needFloat(x) // 轉換成float64
}

完成

呼~完成了基礎的內容了,想來應該很多跟原本我們熟悉的程式語言有很多不一樣的地方吧,尤其像我熟悉C#的人,遇到這個還真的要好好的適應一下

接下來將會進行流程控制的部份,一樣敬請期待啦