Go 語言筆記(3) — Data types(上)

「如果program structure是程式之骨,那麼,Data就是鑲嵌其間的肉」

實用度:★★★★★

Photo by Kristian Strand on Unsplash

【導言】

電腦的底層是在一連串的fixed-size numbers(words)上執行,這些words會被編譯成intergers、floating-point numbers等basic data types,而這些可以再進一步aggregate,用來代表pixels、packets等。

在Go語言中大致可以分為四種data types:

  • basic types
  • aggregate types
  • reference types
  • interface types

而本篇文章主要在整理basic types的部分,至於其他部分,

就稍晚再上新菜囉!


【開發環境】

  • 任何作業系統都行,建議使用Unix-like system。
  • 也推薦大家一個線上compiler,點此

【六道菜吃到飽】

  • 壹、整數們
  • 貳、浮點數們
  • 參、複數們
  • 肆、布林值們
  • 伍、字串們
  • 陸、常數們

【壹、Integers】

Go支援signed和unsigned兩種整數運算,更細來看有8、16、32、64 bits的表示方式,對n bit的數字來說,signed interger的範圍是[ -2^(n-1), 2^(n-1)-1],unsigned interger的範圍則是[0, 2^n-1],底下是幾個常見的表示方式:

  • int8, int16, int32, int64:表示成unsigned,就寫成uint8(以此類推囉)
  • int, uint:可能是32 bit或64 bit,不同compiler可能導致不一樣的結果!
  • rune:和int32等價,但通常用rune,是拿來表示一個Unicode code point
  • byte:和int8等價,會用byte,表示該資料是raw data,而非數量表示。
  • uintptr:在Go與C互動的底層才會看到,之後進階的課程中,再談吧~

如果遇到兩個不同data type的integer要做運算怎麼辦呢?很簡單~可以參考前一篇講到的Conversion來做type的轉換,這樣就不會噴compile error囉!

Precedence(Operator的優先權)

Go語言中的Operators共有以下五種優先級(由上至下是優先順序,如果不太清楚這些符號的意思,歡迎參照Go Operaotors),如果一個式子中先後出現兩個同樣優先級的Operators,那麼優先算左側的Operator;另外,如果不太確定,或是有其他需要,也可以用 (、) 括號來括起要運算的變數,將優先級提升。

*      /      %      <<       >>     &       &^
+      -      |      ^
==     !=     <      <=       >      >=
&&
|| 

另外,+、-、*、/ 這些算數運算符也能用在浮點數和複數上,例如:在integer的運算上,5/4 = 1;在floating-point number中,5.0/4.0 = 1.25。

Overflow / Underflow

和許多其他的語言一樣,由於數值的範圍會因為宣告而有變動,如果我們嘗試存取超過範圍的數值,則會發生Overflow或Underflow歐。

// Example of overflow
var i int8 = 127   // 01111111, bigest number of int8
fmt.Println(i+1)   // 10000000, "-128"
fmt.Println(i*i)   // 00000001, "1"

用signed或unsigned的時機

✔️ 當我們使用bitwise operator時,例如:127>>1(127右移1 bit),若一個8 bit的integer左移,則右邊空出的bit會由0來補,聰明的客官們想必想到了!如果我們用的是signed integer,這時正負數值可能會被動到,因此這種情況下,我們比較prefer用unsigned integer

✔️ 大多數的情況下,像是:

medals := []string{"gold", "silver", "bronze"}
for i := len(medals) - 1; i >= 0; i-- {
    fmt.Println(medals[i]) // "bronze", "silver", "gold"
}

要是i是一個unsigned integer,事情可就糟糕啦!(大家可以想想看為什麼)所以,我們通常只有在非常特殊的cases中會用unsigned integer,一般來說,請多多使用signed interger歐。(Go 默認情況下是用signed)


【貳、Floating-point numbers】

Go語言中提供兩種精度的浮點數,分別是:

  • float32
  • float64

其範圍極限值可以在math這個library中取用(以下皆是近似值):

  • math.MaxFloat64 ~1.8e308
  • math.SmallestNonzeroFloat64 ~ 4.9e-324
  • math.MaxFloat32 ~ 3.4e38
  • math.SmallestNonzeroFloat32 ~ 1.4e-45

一般我們喜歡用float64,原因是float64提供的精度較多位,比較不易因累計計算誤差而出錯。

More about “math” library

math中也提供了NaN來表示「非數」,如下範例

var z1 float64 // 還記得嗎?此時z1=0歐 
var z2 float64 = 1
fmt.Println(z1, -z1, z2/z1, -z2/z1, z1/z1, math.Sqrt(-z2))
// 0 -0 +Inf -Inf NaN NaN
// z1 == -z1 是true歐

要小心的是,最好不要用NaN來檢驗運算的結果,因為, math.NaN()不等於任何數,連自己也不等於自己(false, math.NaN() == math.NaN())。

取而代之,可以用內建的math.IsNaN()來判別,最好呢,是自己標註運算無效,例如:

func compute() (value float64, ok bool) {
    // ...
    if failed {
        return 0, false
    }
    return result, true
}

【參、Complex Numbers】

Go語言中提供兩種精度的複數,分別是:

  • complex64
  • complex128

咦?怎麼看起來似曾相識,沒錯!因為其分別對應於上面講過的float32和float64~

起手式

var x complex128 = complex(4, 3) // 4+3i
y := 2 + 1i                      // 2+1i          y默認是complex128
fmt.Println(x*y)                 // "(5+10i)"
fmt.Println(real(x*y))           // "5"
fmt.Println(imag(x*y))           // "10"
// 更多關於complex的運算,請import math/cmplx 包~

【肆、Booleans】

Go語言中的type bool,只有兩種可能的值:truefalse(廢話XD)有幾個超級簡單的小重點,和其他常見的語言都是一樣的:

  • forif中的conditions就是boolean type。
  • <==等比較運算符輸出的結果也是boolean。
  • &&的優先權比||還要高(回頭複習一下【壹】講過的內容吧~)。

【伍、Strings】

string的定義是:

immutable sequence of bytes

通常用來表示人類可讀的文本,畢竟是人搞出來的東西,柴犬懂的語言就不支援囉,”汪”

起手式

s1 := "hello, world"               
s2 := "hello, 世界"                 
fmt.Println(len(s1))               // "12"
fmt.Println(len(s2))               // "13"
fmt.Println(s1[0], s1[8])          // "104 111"
fmt.Println(s2[0], s2[8])          // "104 184"
fmt.Println(s1[:])                 // "hello, world"
fmt.Println(s2[:5])                // "hello"
fmt.Println(s2[7:])                // "世界"

String Literal

String literal就是長"hello"這樣用兩個雙引號括起來的字串編寫方式,如果要在其中插入一些特別的符號,就像其他語言一樣使用\即可,例如\n就是換行的意思、\r是回車號等等。

Raw String Literal

Raw string literal則長成這樣:(用``括起來就對了)

const greeting = `Hello, I'm
RAW 
STRING 
Literal!!! `

各位可以把上面的文字拿去print出來看看,會發現退格、換行等都如字面上的本來模樣老實印出來,這在html模板、JSON格式文件、正則表達式中都很常出現歐。

Unicode和UTF-8

很久以前,當時大概只有美國人在computer世界裡,因此使用當時的ASCII標準,用7bit來表示128個字符(包括英文字母大小寫、數字、標點符號等)就足夠了,但後來網路發達起來,懂得用電腦的人愈來愈多,因此大家開始使用Unicode來處理各種語言的文本,甚至是一些 卍 特別的符號 卍。

(小複習:一個Unicode code point的data type就是int32,在go語言中,我們用rune來對應,所以int32和rune是等價的歐,只是rune通常用在這兒)

現在最常見的的Unicode編碼方式是UTF-8,方式如下:

0xxxxxxx                             runes 0-127    (ASCII)
110xxxxx 10xxxxxx                    128-2047       常用
1110xxxx 10xxxxxx 10xxxxxx           2048-65535     次常用
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx  65536-0x10ffff 其他字符

其實上面這張表,對於大部分的碼農來說不用去記沒關係,放在這只是想問大家注意到了嗎?UTF-8完全兼容ASCII!

所以在上面「起手式」中出現的”世界”長甚麼德行呢?搭啷~

"世界"
"\xe4\xb8\x96\xe7\x95\x8c"
"\u4e16\u754c"              // 16bit representation
"\U00004e16\U0000754c"      // 32bit representation

我們的go語言對UTF-8的處理,非常在行,有完善的library可供使用,詳細的使用說明,如果有進一步的需求,請各位到utf8 library深入學習囉

Strings和Byte Slices

在standard library中有四個library對字串的處理特別重要,分別是:

  • strings:字串查詢、替換、比較、拆解、合併等功能。
  • bytes:提供了許多和strings類似(甚至是一樣)的functions,跟strings之間的差別是:string type都被換成了[]byte type,由於string是immutable data,所以在對其做處理時,勢必會複製、重新分配記憶體,而相對的,bytes中的slice的元素都可以任意修改,並且bytes提供的bytes.Buffer variable可以動態增長,方便我們擴增、編輯字串,所以,使用bytes是較有效率的作法歐~
  • strconv:提供各種string和其他data type之間的轉換。
  • unicode:分辨Digits或Letter、大小寫轉換等。

Conversions between Strings and Numbers

strconv和fmt library都是我們的好幫手~

首先是strconv:

a := 111
fmt.Println(strconv.Itoa(a)) // "111"
fmt.Println(strconv.FormatInt(int64(a), 2)) // 要先轉int64! "1101111"b, err := strconv.Atoi("123")             // b is an int 123
c, err := strconv.ParseInt("123", 10, 64) // base 10, up to 64 bits

再來是fmt(Sprintf主要是拿來格式化用,所需時間較strconv長歐):

b := fmt.Sprintf("a=%b", a)
c := fmt.Sprintf("%d", a)
fmt.Println(b, c) // "a=1101111 111"

【陸、Constants】

Constant 的值在compile time就已經計算好了,在run time並不會做任何改變,所以一些數學常數就很適合用constant來表示,如下:

起手式

// const yourConstant (type) = value// Just approximate value
const e = 2.7182818284
// or just in one const declaration...
const (
    e  = 2.7182818284
    pi = 3.1415926535
    phi = 1.6180339887
)

iota: constant 產生器

這類似於其他語言中的enumerator,在每一個const的declaration中,iota在第一個宣告中,會被設為0,接下來,陸續加一,例子如下:

簡單的:

type Month intconst (
    January Month = iota + 1 // 1 
    February                 // 2 
    March                    // 3, and so on
    April                    
    May
    June
    July
    August
    September
    October
    November
    December
)

更複雜的也可以:

const (
    Byte = 1 << (10 * iota)
    KiB // 1024
    MiB // 1048576
    GiB // 1073741824
    TiB // 1099511627776             
    PiB // 1125899906842624
    EiB // 1152921504606846976
    ZiB // 1180591620717411303424                (exceeds 1 << 64)
    YiB // 1208925819614629174706176
)

Untyped constants

在Go語言中,constant其實不一定要有一個確定的data type(variables則一定要有),像是int32或int64,舉例來說,上面的YiB就大到沒有任何int variable能容納。

另外像是math library中的math.Pi也是這樣的例子,在實務上,我們可以視情況用在任何變數上,如下:

var a float32 = math.Pi
var b complex64 = math.Pi
// ...

【結論】

今天各位和阿玉一同看了Go語言聖經中最基礎的Data types,有許多部份跟其他語言差不多,想必是難不倒各位的!這篇文章的份量很夠,但本次的文章都沒有整份能run的code,挑戰度不高,或許吃得不是很飽,不過不過,先別急著走嘛~下一節講完複合型Data後,我們就要開始挑戰Go語言的functions、methods等概念,就可以真的來做一些有趣的應用囉,進一步的,Go語言最著名的Concurrency也離我們不遠啦!耐著性子,蹲馬步,蹲得愈低,之後就能跳得愈高勒!


餐後點心

非常感謝網柴(Jack Shiba)幫忙試吃,讓我拙劣的手藝能夠有稍稍進步~以下是今天的餐後點心:

如果你也喜歡我們的文章,幫我們動動手部肌肉,按下掌聲Clap,讓我們有動力繼續煮下一頓料理!

整個程式都是我的咖啡館

Python和Golang的教學Blog

Jade Chen

Written by

Jade Chen

NTUEE119 | Blockchain TA | NTU Sunrise

整個程式都是我的咖啡館

Python和Golang的教學Blog