Study for Go ! Chapter two - Expression

slowlydance2me發表於2023-03-03

Study for Go ! Chapter two - Expression

1. Keyword

 

 

 

  • Golang僅有 25 個保留關鍵字,體現了 golang 語法規則的簡潔性

  • 保留關鍵字不能用作常量、變數、函式名、以及結構欄位等識別符號

 

2. Operator

 

 

 

  • Golang 中沒有乘冪和絕對值運算子,對應的是標準庫 math 裡的 Pow、Abs 函式實現

  • 一元運算子的優先順序最高

  • 二元運算子有五個級別從高到低分別是:

 

 

 

  • 相同優先順序的二元運算子,從左到右依次計算

  • 使用二元運算子,除了位移操作以外,運算元型別必須相同,如果其中一個是無顯式型別宣告的常量,那麼該常量運算元會自動轉型

  • 位移右運算元必須是無符號整數,或者可以轉換的無顯式型別常量

  • 如果是非常量位移表示式,那麼會優先將無顯式型別的常量左運算元轉型

  • 位運算子

     

     

     

Attention:

  • 在 golang 中自增、自減不再是運算子,而是作為獨立語句存在,不能用於表示式使用

  • 表示式通常式 求值程式碼,可以作為右值或者引數使用,而語句完成一個行為,比如 if、 for 程式碼塊,表示式可以作為語句使用,但是語句卻不能當作表示式

Pointer:

  • 不能將記憶體地址與指標混為一談

  • 記憶體地址是記憶體中每個位元組單元的唯一編號,而指標則是一個實體

  • 指標會被分配記憶體空間,相當於一個專門用來儲存地址的整型變數

  • 取值運算子 “ & ” 用於獲取物件地址

  • 指標運算子 “ * ” 用於間接引用目標物件

  • 二級指標 **T, 如果包含包名則寫成 *package.T

  • 並非所有物件都能進行取地址操作,但是變數總是能夠正確返回 (addressable),指標運算子為左值時,我們可更新目標物件狀態,為右值時,我們可以獲取目標物件狀態

  • 指標支援相等運算子,但不能做加減法運算和型別轉換,如果兩個指標指向同一地址,或者都為nil,那麼它們相等

Attention:

  • 可以透過 unsafe.Pointer 將指標轉換為 uintptr 後進行加減法運算,但可能會導致非法訪問

  • Pointer 類似C 語言中的 void* 萬能指標, 可以用來轉換指標型別。他能安全持有物件和物件成員,但是 uintptr 不行。uintptr 僅是一種特殊型別, 並不用於目標物件, 無法組織垃圾回收器回收物件記憶體

  • Golang 中沒有專門指向成員的 " -> " 運算子, 統一使用 “ . ” 選擇表示式

Zero - size

  • 零長度 (zero - size)物件的地址是否相等和具體的實現版本有關,但是肯定不等於 nil

  • 即使長度為 0 ,可該物件依然時合法存在的,擁有合法的記憶體地址,這與 nil 語義完全不同

  • 在 runtime/malloc.go 裡有個 zerobase 全域性變數, 所有透過 mallocgc 分配的零長度物件都使用該地址

 

3. Initialization

  • 對複合型別(slice、array、map、struct)變數初試化時,有一些語法限制

  1. 初始化表示式必須包含型別標籤

  2. 左花括號必須在型別尾部,不能另起一行

  3. 多個成員初始值以逗號分割

  4. 允許多行,但每行以逗號或者右花括號結束

 

4. Flow Control

  • Golang 精簡(合併)了流控制語句,雖然某些時候不夠便捷,但是夠用

    1. if...else...

      • 條件表示式值必須是 bool 型別,可以省略括號,但是左花括號不能另起一行

      • 支援初始化語句,可定義塊區域性變數或執行初始化函式

        Attention:

         

         

上示例中,if 塊承擔了兩種邏輯:錯誤處理和後續正常操作。

基於重構原則,我們應該保持程式碼塊功能的單一性

 

 

如此,if 塊僅完成條件檢查和錯誤處理,相關正常邏輯保持在同一層次。

這樣做便於程式碼的閱讀,也提升了程式碼的可維護性,更利於拆分重構

如果需要在多個條件塊中使用區域性變數,那麼只能保留原層次,或者直接使用外部變數

對於某些過於複雜的組合條件,建議將其重構為函式

函式呼叫雖然有一些效能損失,但是卻讓主流程變得更加清爽,更易於測試,改善程式碼 可維護性

 

Notice

將流程和區域性細節分離是一個很好的習慣

不同的變化因素被分隔在各自獨立單元(函式或者模組)內,可以避免修改時造成關聯錯誤,減少患“ 肥胖症 ”的函式數量

 

  1. switch

  • 條件表示式支援非常量值,使得 golang 比C更加靈活。相比於if表示式, switch值列表更加簡潔

  • 編譯器對if、switch 生成的機器指令可能完全相同,所謂誰的效能更好看具體情況而定

  • switch 同樣支援初始化語句,按照從上到下,從左到右匹配case執行,只有全部匹配失敗時才會執行default塊

  • 無需顯式執行 break 語句, case執行完畢後自動中斷,如要繼續訪問後續 case (原始碼順序),需要執行 fallthrough 關鍵字,但是不再進行條件表示式匹配

    Attention:

    • fallthrough 必須放在 case 塊的結尾,可被 break 語句阻止

    • 如果 fallthrough 放在 default 後面,因為會按照原始碼執行順序執行所以不會執行 default 導致錯誤

     

    Notice:

    • 某些時候,switch 被用來替換 if 語句, 被省略的 switch 條件表示式預設為 true,繼而與 case 比較表示式結果匹配

    • switch 有時候也用於介面型別匹配

  1. for

  • golang 中僅有 for 這一種迴圈語句

  • 初始化語句僅能被執行一次,條件表示式中如果有函式呼叫,要先確認是否會被重複執行

  • 如果存在重複執行的函式,則可能會被編譯器最佳化掉,也可能是動態結果須每次執行確認

  • 處理上述問題的辦法是,建立臨時變數儲存該函式結果

  • 使用 for...range 完成資料迭代支援 string、array、pointer、array pointer、slice、map、channel等型別,返回索引、鍵值資料

  • 允許返回單指(次數),或用 “ _ ” 忽略

  • 無論是 for 還是 for...range 迭代,其中定義的區域性變數都會被重複使用(地址相同),但這樣會對閉包有一些影響

    Attention

    • 使用 range 時會先複製要迭代的目標資料,再在複製體中區取值,所以會影響陣列的 range 使用因此建議使用陣列指標或者切片型別去代替

    • 在 range 的複製問題中,string、slice 的基本結構都是很小的結構體,而 map、channel 本身時指標封裝,它們的複製成本都很小,無須專門最佳化

    • 如果 range 的目標表示式時函式呼叫,那也僅被執行一次

  1. goto、continue、break

  • 使用 goto 前,需要先定義標籤。標籤區分大小寫,且未使用的標籤會引發編譯錯誤

  • 不能使用 goto 跳轉到其他函式或者內層程式碼塊內

  • break 和 continue 用於終端程式碼塊的執行

 

 

 

  • 配合標籤, break 和 continue 可以在多層巢狀中 指定目標層級

 

相關文章