Go 語言筆記(2) — Program Structure(下)

「就如同畫畫前要先描繪骨幹⋯⋯」

難易度:★☆☆☆☆(咖啡館請每位冒險家吃的一塊蛋糕)

實用度:★★★★★(每踏入一個新語言,都該學好這個!)

Photo by Alain Pham on Unsplash

【小導言】

這篇文章是上一篇文章的續集,若要對Go語言的program structure有更好的理解,可以先看看上集再來逛本欄~


【伍、Type Declarations】

在【貳、Declarations】中我們已經有簡短的提到,現在讓我們更深入來看看吧!一個變數的type定義了實際操作時,其value的一些特性。舉例來說:

  • 大小(幾個bits或幾個elements等)
  • 內部用甚麼值來表示(用int、float或string等)
  • 有哪些built-in functions可以用

起手式

// Type declaration:
// " type name underlying-type "
// For example:
type fruit string
type animal string
type Calories float64
type Joules float64

可以注意到上面的例子們,明明都是相同的representation(float64),可是表達的意思有所不同(卡路里和焦耳),之後應用的場域也就會不一樣。另外,type declaration最常出現在package level,如此一來,整個package 都能使用,如果naming時用開頭大寫來做type declaration,更能export到其他的package中使用(例子中的fruit和animal都不能被export)。

正式上菜

好的,這是我們的heatconv包,阿玉定義了兩個type,分別代表卡路里和焦耳,接著兩個functions可以用來實現兩者間物理意義上的轉換。更底下的則是兩個type associated methods(目前還沒有介紹過,大家可以把它想像成OOP中class裡頭的function,例如:Calories這個東東可以去call自己的String ),方便我們之後用fmt包來print出其值。

做type declaration後,兩個浮點數不再是同個type(儘管他們內部都是float64),於是arithmetic上的combination不再可行、也不能做comparison,好處是:「程式設計師不能再將兩個不同意義(type)的變數做結合啦!」

容易搞混的conversion術語

當我們在go中提到conversion時,指的不是 CalToJ(c)這種轉換的function,而是 Calories(t)Joules(t) (小心!他們並不是function~他們就叫做conversion),他們不像CalToJ(c)會回傳變數轉換後的值,conversion是回傳改變type後的t值。

而且呀,所有的type T都有自己的conversion T(x),其又能依照「會不會改變內部值」分為兩種:

🌝 不會改變值的conversion:

  • 如果兩個變數的underlying-type相同,例如上面的卡路里和焦耳。
  • 如果兩個unnamed pointer types指到相同underlying-type的變數。
  • 常常是redundant conversion。

🌚 會改變值的conversion:

  • 如果都是numeric types,例如:從float轉到int,會將小數部分都丟掉。
  • 如果是string到某些slice types(有點超出要討論的範圍,之後學囉)。

小練習

先不要看註解的答案,猜猜看每行code會有什麼結果?(再次叮嚀,在下面的例子中,Calories(j)是一個conversion,在type上把j從Joules轉成Calories,但是並非物理意義歐)

var c Calories                    // c = 0
var j Joules // j = 0
fmt.Println(c==j) // compile error: type mismatch
fmt.Println(c==Calories(j)) // true

【陸、Packages and Files】

Go語言中的Packages就是其他語言中的libraries或modules啦!可以:

  • modularity(模組化,將大系統拆開、重複組合)
  • encapsulation(防止外界濫用內部package的資訊)
  • separate compilation(協作方便、節省recompile time、容易維護等)
  • reuse(一個字,爽,重複打一樣的code是CS界最慘的事呀)

起手式

// Package heatconv performs ...     <- 在每個package前打上註解是好習慣歐
package heatconv <- 標明這是哪一個package
import (                             <- 這部份標明引用哪些packages
"fmt" <- 官方內建的pkg會放在這
"..."

"github.com/for/example" <- 來自其他地方的pkg則會與官配的分開
)

正式上菜

記得上面【伍】的範例heatconv.go嗎?可以把它們拆成兩個檔案,如下:

heat.go
conv.go

注意到上面的兩個檔案名都可以任意取名(小寫開頭),只要在檔案開頭標明package,完全不影響功能的實現(因為都定義在heatconv這個package中),還可以將程式整理清楚,讓你的程式碼的可讀性更高~是很實用的技能。

Package如何initialize

很多人下意識會認為package是依照次序來initialize裡頭的變數,然而有時候會有例外,例如:

在zodiac這個package中,initialize的順序是dragon、rabbit、mouse、cow、tiger,可見dragon和rabbit是照著order來,不過,剩下的就不是,原因是tiger需要用到cow和mouse的值,看向cow,發現getCow()也需要用到mouse的值,所以,就從mouse開始initialize。至此我們學到initialization的次序是同時看order和dependence的!

寫好init function能說是一件藝術,決定了你的code有沒有效率(例如在init時先建好table方便查詢等),不過已經有點偏離了program structure要談的核心內容,我們之後再花比較多時間深入吧~


【柒、Scope】

Scope和lifetime

前面在扯到garbage collection時有稍稍提到過lifetime,請注意它和Scope是兩個不一樣的概念。Scope (of declaration)講的是「程式中的元素在哪個範圍中能被用」是個Compile-time property;lifetime (of variables)講的是「在程式運行過程中,一個variable在變成unvisible前能存活多久」是個Run-time property。

容易犯的小錯誤

如果沒有小心注意scope的話,可能就會打出這種code:

注意到 f 在 if 處被宣告,scope只在 if 的範圍內,因此底下執行ReadByte()和Close()會吐出 " compile error: undefined f ",至於要怎麼樣才能改成對的code呢?請各位躍躍欲試的大大們自己try try see囉~

(小提示:可能你希望 f 不要離開if else的範圍,也可能你覺得 f 可以存在於 if 之外,這兩種想法各會得到不同的答案。)


【結論】

呼~大功告成!到這兒我們已經了解packages、files、declarations等元素如何組合出一個go語言程式,也就是說我們學會了go語言program structure的核心概念,接下來的幾道新菜,阿玉將帶各位客倌更深入的看看go語言中各種data的內部structure,敬請期待!


餐後點心

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

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