iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 4
2
Software Development

啥物碗Golang? 30天就Go系列 第 4

基本類別與自訂類別

雖然每種程式語言提供的變數類別大同小異,但如果仔細檢視,就會發現實作邏輯上會有些微的不同。掌握這些不同之處,正是學習新語言的「眉角」所在。接下來介紹go所用到的一些基礎類別。

字串 string

任何語言一定會有字串,用來儲存或表示人類可讀的資料,例如姓名或地址。go預設使用utf8編碼,支援各國語言。以下是簡單的例子:

s := "hello, world"

字串有內建一些方法,例如len[]

fmt.Println(len(s))     // "12"
fmt.Println(s[0], s[7]) // "104 119" ('h' and 'w')

len會返回字串長度;[]則會返回字節的值。如果搭配:可以取得新的字串,:左右各搭配一個字節索引,但都可以省略,省略時則取到開頭或結尾。如果左右都省略,則返回字串本身。範例如下:

fmt.Println(s[0:5]) // "hello"
fmt.Println(s[:5]) // "hello"
fmt.Println(s[7:]) // "world"
fmt.Println(s[:])  // "hello, world"

整數 int

每種語言也一定都會有整數。go實際上提供了四種整數型態:int8int16int32int64,分別對應8、16、32、64bit有符號整數;與之相對的還有uint8uint16uint32uint64無符號整數。

但實務上最常使用的就是intuint,可能對應32或64bit,編譯時會自己決定,沒有辦法改變預設。如果你嘗試將不同位元的整數一起運算,會出現編譯錯誤:

var apples int32 = 1
var oranges int16 = 2
var sum int = apples + oranges // compile error

這種情況下需要先轉換才可以(所以沒事最好都使用int):

var sum = int(apples) + int(oranges)

布林值 bool

雖然我很想說每一種語言一定都有布林值,但我記得我之前學過一種語言,他的布林值其實是symbol的特例(:true:false)。不過go沒有symbol這個類別,九成九的語言也確實都有布林值。

go似乎沒有內建的布林轉換,如果需要的話,可以參考下面的例子:

// btoi returns 1 if b is true and 0 if false.
func btoi(b bool) int {
    if b {
        return 1
    }
    return 0
}

或是將布林值轉換為整數:

// itob reports whether i is non-zero.
func itob(i int) bool { return i != 0 }

雖然還有浮點數復數,但我就省略了,有興趣的朋友可以參考連結。接下來介紹陣列與雜湊:

陣列 Array & Slice

go的陣列有兩種(斯斯有兩種?!),分別是Array與Slice,後者的名稱在其他語言中比較少見,不過性質差不多。

Array

Array在go的定義下長度是固定的,而且類別需要相同,與Ruby相比喪失了許多的自由度(整個語言到處都給我這種感覺)。但利弊其實是一體的兩面,自我限制的同時,也增加了結構的嚴謹,使代碼不容易產生預料外的錯誤。(但我還是喜歡Ruby)。

以下我們來看看Array的例子:

var q [3]int = [3]int{1, 2, 3}

這樣的宣告代表了有三個整數的Array,不能增減,也不能放其他類別。所以實務上Slice比較常用(因為比較靈活)。假如宣告長度為三,但卻只有給兩個值,那麼第三個空位就會自動給初始值。

var r [3]int = [3]int{1, 2}
fmt.Println(r[2]) // "0"

長度可以省略宣告,由給值的長度決定,省略時使用...

q := [...]int{1, 2, 3}
fmt.Printf("%T\n", q) // "[3]int"

前面有說過,Array是不能增減的,所以如果給值的數量超過當初宣告,編譯時會錯:

q := [3]int{1, 2, 3}
q = [4]int{1, 2, 3, 4} // compile error: cannot assign [4]int to [3]int

Slice

Slice跟Array有九成像,但是長度可以變動,不過一樣需要是相同類別。我們來看個例子:

months := [...]string{1: "January", /* ... */, 12: "December"}

如果沒有給索引的話,就會從0開始。這邊索引定義從1~12。如果要取特定元素,只要放入索引值,例如months[1]會返回January,也可以取一個區段。

Q2 := months[4:7]
summer := months[6:9]
fmt.Println(Q2)     // ["April" "May" "June"]
fmt.Println(summer) // ["June" "July" "August"]

雜湊 Map

雜湊是無順序的key與value組合,所有key都必須不同。有兩種宣告方式:

ages := make(map[string]int)
// or
ages := map[string]int{
    "alice":   31,
    "charlie": 34,
}

上面這種寫法等同於:

ages := make(map[string]int)
ages["alice"] = 31
ages["charlie"] = 34

可以使用內建的刪除方法delete

delete(ages, "alice")

自訂類型

除了預設的基本類型之外,go還有自定類型的設計。可以將商業邏輯與變數型態做更深的綁定。結構如下:

type 自訂類別名稱 基礎類別

這邊用攝氏華氏溫度轉換的例子,就會比較容易理解:

package tempconv

import "fmt"

type Celsius float64    // 攝氏温度
type Fahrenheit float64 // 華氏温度

const (
    AbsoluteZeroC Celsius = -273.15 // 絕對零度
    FreezingC     Celsius = 0       // 結冰溫度
    BoilingC      Celsius = 100     // 沸點
)

func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }

func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }

攝氏與華氏溫度分別對應不同溫度單位,雖然底層都是float64,但卻是不同的資料類型,不能混雜計算。這個設計相當不錯,我認為可以算是go優於ruby的設計的地方。

var c Celsius
var f Fahrenheit
fmt.Println(c == 0)          // "true"
fmt.Println(f == 0)          // "true"
fmt.Println(c == f)          // compile error: type mismatch

我們可以看上面的例子更具體。雖然攝氏零度與華氏零度都等於零,但他們並不相等。自訂類型還可以綁定方法,許多的自訂類型都會覆寫string方法,類似Java裡面常常會覆寫toString一樣:

func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }

上面這個例子前面表示這是一個攝氏類型的String方法,會返回一個帶有攝氏符號°C的字串,使用的範例如下:

c := FToC(212.0)
fmt.Println(c.String()) // "100°C"

Reference


上一篇
變數、常數與命名
下一篇
條件執行與For循環
系列文
啥物碗Golang? 30天就Go30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
froce
iT邦大師 1 級 ‧ 2018-11-01 14:06:11

Slice那邊,給定 index,是產生最大index+1個元素。

months := [...]string{2: "Jan", 4: "Feb"}
// 這三個會是空白的。
fmt.Println(months[0], months[1], months[3])

// 這個會跳錯誤 invalid array index 5 (out of bounds for 5-element array)
fmt.Println(months[5])

看起來go的slice應該是以array為基本。

我要留言

立即登入留言