精通 Go 程式設計 - 程式結構 | 閱讀筆記

Fetching and reading files from S3 using Go

圖片來源:Fetching and reading files from S3 using Go

本文是《精通 Go 程式設計》(The Go Programming Language) 第 2 章「程式結構」(Program Structure) 的閱讀筆記。

#1 名稱

#2 宣告

共 4 種宣告 - var、const、type、func。

範例 2-1:輸出水的沸點

宣告 1 個常數 (boilingF)、 1 個函式 (main)、2 個變數 (fc)

package main

import "fmt"

const boilingF = 212.0

func main() {
  var f = boilingF
  var c = (f - 32) * 5 / 9
  fmt.Printf("boiling point = %g F or %gC\n", f, c)
}

得到輸出結果。

boiling point = 212 F or 100C

範例 2-2:函式封裝華氏與攝氏溫度轉換邏輯

fToc 函式封裝華氏與攝氏溫度轉換邏輯,以便重複使用。

package main

import "fmt"

const boilingF = 212.0

func main() {
  const freezingF, boilingF = 32.0, 212.0

  fmt.Printf("%g F = %g C\n", freezingF, fToc(freezingF))
  fmt.Printf("%g F = %g C\n", boilingF, fToc(boilingF))
}

func fToc(f float64) float64 {
  return (f - 32) * 5 / 9
}

得到輸出結果。

32 F = 0 C
212 F = 100 C

#3 變數

3-1 變數的宣告

宣告一個變數的通用形式,type 和 expression 可擇一省略。

var name type = expression

若省略 type,則由 expression 的運算結果決定型別。

例如:變數 happy 的值是字串 happy,印出型別為 string。

var happy = "happy"
fmt.Printf("happy: %s\n", reflect.TypeOf(happy)) // happy: string

若省略 expression,則初始值為該型別的零值,因此 Go 沒有 undefined 值,以減少不預期的錯誤。

例如:變數 hello 的型別是 int,沒有給定初始值,因此初始值會是 int 的零值,也就是數字 0。

var hello int;
fmt.Println(hello) // 0

型別的零值。

型別 數字 布林 字串 介面與參考型別* array 與 struct
零值 0 false ”” nil 所有元素或欄位皆為零值

*包含 slice、pointer、map、channel、func。

3-2 一串變數的宣告

一串變數的宣告 i、j 和 k 為 int。

var i, j, k int

fmt.Printf("i: %s\n", reflect.TypeOf(i))
fmt.Printf("j: %s\n", reflect.TypeOf(j))
fmt.Printf("k: %s\n", reflect.TypeOf(k))

得到輸出結果。

i: int
j: int
k: int

一串變數的宣告 b、f、s 和 result,初始值可為實字值或運算式。

var b, f, s, result = true, 2.3, "four", 2*3

fmt.Printf("b: %s\n", reflect.TypeOf(b))
fmt.Printf("f: %s\n", reflect.TypeOf(f))
fmt.Printf("s: %s\n", reflect.TypeOf(s))
fmt.Printf("result: %s\n", reflect.TypeOf(result))

得到輸出結果。

b: bool
f: float64
s: string
result: int

3-3 短變數的宣告

短變數的宣告用於宣告並初始化 local variable。

name := expression

改寫前例

package main

import "fmt"

const boilingF = 212.0

func main() {
  f := boilingF
  c := (f - 32) * 5 / 9
  fmt.Printf("boiling point = %g F or %gC\n", f, c)
}

得到輸出結果。

boiling point = 212 F or 100C

短變數的宣告可同時做變數宣告和指派,但至少有一個新的變數宣告。例如:最初先宣告兩個變數 hello 和 world,然後接下來宣告一個新的變數 hi 並且 assign 值給 world,這樣就最少有宣告一個新的變數。

hello, world := "hello", "world"
hi, world := "hi", "hello world"

fmt.Printf("hello = %s\n", hello) // hello
fmt.Printf("world = %s\n", world) // hello world
fmt.Printf("hi = %s\n", hi) // hi

得到輸出結果。

hello = hello
world = hello world
hi = hi

3-4 指標

指標指向某個變數,存的是變數的位址。

範例如下

package main

import "fmt"

func main() {
  x := 1
  p := &x // x 的位址,是 int
  fmt.Printf("p = %d\n", p)

  *p = 2
  fmt.Printf("x = %d\n", x) // x === 2
}

得到輸出結果。

p = 824633835680
x = 2

每次宣告變數都會存在不同的位址。

func f() *int {
  v := 1
  return &v
}

fmt.Println(f() == f()) // false

得到輸出結果。

false

利用變數的位址間接修改其值。

範例如下

v := 1
fmt.Println(incr(&v)) // 2
fmt.Println(incr(&v)) // 3

func incr(p *int) int {
  *p++
  return *p
}

得到輸出結果。

2
3

利用 flag 套件改寫命令列參數的設定值。

package main

import (
  "flag"
  "fmt"
  "strings"
)

var n = flag.Bool("n", false, "omit trailing newline")
var sep = flag.String("s", " ", "seperator")

func main() {
  flag.Parse() // 將預設值更新
  fmt.Print(strings.Join(flag.Args(), *sep)) // 將新的 seperator 帶入空白、組成字串

  if !*n {
    fmt.Println(); // 輸出換行
  }
}

執行。

$ ./echo a bc def

得到輸出結果。

a bc def

執行,seperator 設定為 /

$ ./echo -s / a bc def

得到輸出結果。

a/bc/def

備註:執行 ./echo -s / a bc def

執行。

$ ./echo -n=true a bc def

得到輸出結果,不換行。

a bc def$

執行。

./echo -h

得到說明訊息。

Usage of ./echo:
  -n    omit trailing newline
  -s string
        seperator (default " ")

3-5 new 函式

利用內建的 new 函式建立變數。

範例如下,new(T) 表示以 T 型別建立變數,預設值是 T 的零值,回傳 *T 作為位址。

p := new(T)
package main

import "fmt"

func main() {
  p := new(int)
  fmt.Println(*p) // 0
  *p = 1;
  fmt.Println(*p) // 1
}

得到輸出結果。

0
1

由以上範例可知,以下兩種表示方法是相同的。

func newInt1() *int {
  return new(int)
}
func newInt2() *int {
  var dummy int
  return &dummy
}

通常不同變數的位置會不同,但若變數沒有資訊且大小為零,則位置會相同。

arr1 := [1]int{0}
arr2 := [1]int{0}
fmt.Println(arr1 == arr2)

得到輸出結果。

true

3-6 變數的生命週期

變數的生命週期是指變數存在的時間。

global vs local

heap vs stack

var global *int

func f() {
  var x int
  x = 1
  global = &x
}

func g() {
  y := new(int)
  *y = 1
}

#4 指派

4-1 資料組指派

資料組指派是指一次指派多個變數。

範例:用於交換或變數同時出現在指派雙方

x, y = y, x

範例:精簡指派

i, j, k = 2, 3, 4

範例:指派不要的值給空識別符號

_, err = io.Copy(dst, src)
_, ok = x.(T)

[NOTE] 空識別符號 _ 是一個佔位符,用於賦值操作的時候,丟棄、忽略某個值。

4-2 可指派性

如下範例,slice 這種組合型別的實字運算式間接指派每個元素的值。

也就是說,當值與變數的型別是可指派的,才是合法的。

package main

import "fmt"

func main() {
  medals := []string{"gold", "silver", "bronze"}

  fmt.Println(medals[0])
  fmt.Println(medals[1])
  fmt.Println(medals[2])
}

得到輸出結果。

gold
silver
bronze

i 重新指派字串就是不合法的。

i := 1;
i = "hello world";

#5 型別宣告

具名型別

範例:型別轉換-攝氏與華氏的溫度轉換

說明:

package tempconv

import "fmt"

type Celsius float64
type Fahrenheit float64

const (
  AbsoluteZero 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)
}

說明:

fmt.Printf("%g\n", BoilingC-FreezingC) // 100
boilingF := CToF(BoilingC)
fmt.Printf("%g\n", boilingF-CToF(FreezingC)) // 180
fmt.Printf("%g\n", boilingF-FreezingC) // mismatched types Fahrenheit and Celsius - (1)

var c Celsius
var f Fahrenheit
fmt.Println(c == 0) // true
fmt.Println(f >= 0) // true
fmt.Println(c == f) // mismatched types Celsius and Fahrenheit  - (2)
fmt.Println(c == Celsius(f)) // true - (3)

得到輸出結果。

100
180
mismatched types Fahrenheit and Celsius
true
true
mismatched types Celsius and Fahrenheit
true

範例:定義具名型別的方法-加上單位字串

說明:

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

c := FToC(212.0)
fmt.Println(c.String()) // 100°C
fmt.Println("%v\n", c)  // 100°C
fmt.Println("%s\n", c)  // 100°C
fmt.Println(c)          // 100°C
fmt.Println("%g\n", c)  // 100,預計是不會呼叫 String,但實際執行得到 "100°C"
fmt.Println(float64(c)) // 100,不會呼叫 String

得到輸出結果。

100°C
100°C
100°C
100°C
100
100

#6 套件與檔案

範例:套件管理

- hello/
   - main.go
   - go.mod
   - hello/
       - hello.go

說明

module hello

go 1.19
package hello

import "fmt"
func PrintHello() {
    fmt.Println("hello world")
}
package main

import "hello/hello"

func main(){
    hello.PrintHello()
}

得到輸出結果。

hello world

範例:攝氏與華氏的溫度轉換的套件

定義 2 個檔案:tempconv.goconv.go

tempconv.go 放置型別、常數、方法的宣告。

package tempconv

import "fmt"

type Celsius float64
type Fahrenheit float64

const (
  AbsoluteZero 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)
}

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

conv.go 放置轉換函式。

package tempconv

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

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

印出攝氏沸點轉成華氏的值。

package main

import (
  "fmt"
  "tempconv/tempconv"
)

func main(){
  fmt.Println(tempconv.CToF(tempconv.BoilingC)) // 212°F
}

利用匯入路徑來識別套件,例如:tempconv/tempconv,最後一段字串是套件名稱。

練習 2-1

對 tempconv 加上型別、常數與函式以處理克耳文單位,克耳文零度等於 -273.15 攝氏度,1K 的溫度等於 1°C 的溫差。

tempconv.go 放置型別、常數、方法的宣告。

package tempconv

import "fmt"

type Celsius float64
type Kelvin float64

const (
	AbsoluteZeroK Kelvin = -273.15
)

conv.go 放置轉換函式。

package tempconv

func CToK(c Celsius) Kelvin {
  return Kelvin(c)
}

func KToC(k Kelvin) Celsius {
  return Celsius(k)
}

印出 K 氏絕對零度轉成攝氏的值。

package main

import (
  "fmt"
  "tempconv/tempconv"
)

func main(){
  fmt.Println(tempconv.KToC(tempconv.AbsoluteZeroK)) // -273.15°C
}

練習 2-2 公斤與磅的轉換

weightconv.go 放置型別、常數、方法的宣告,這裡定義大貓和小貓的體重當成兩個常數。

package weightconv

import "fmt"

type Kilo float64
type Pound float64

const (
	SmallCat Kilo = 2
	BigCat Pound = 50
)

func (k Kilo) String() string    { return fmt.Sprintf("%g Kg", k) }
func (p Pound) String() string { return fmt.Sprintf("%g Pound", p) }

conv.go 放置轉換函式。

package weightconv

// 1 kg = 2.20462262 pound
func KToP(k Kilo) Pound {
  return Pound(k* 2.20462262)
}

// 1 pound = 0.45359237 kg
func PToK(p Pound) Kilo {
  return Kilo(p* 0.45359237)
}

印出小貓與大貓的體重轉換。

package main

import (
    "fmt"
    "weightconv/weightconv"
)

func main(){
    fmt.Println(weightconv.KToP(weightconv.SmallCat)) // 2 Kilo = 4.40924524 Pound
    fmt.Println(weightconv.PToK(weightconv.BigCat)) // 50 Pound = 22.6796185 Kg
}

套件初始化

範例:人口計算

說明

package popcount

var pc [256]byte

func init() {
  for i := range pc {
    pc[i] = pc[i/2] + byte(i&1) // 對 `00000001` 做遮罩
  }
}

func PopCount(x uint64) int {
  return int(pc[byte(x>>(0*8))] +
    pc[byte(x>>(1*8))] +
    pc[byte(x>>(2*8))] +
    pc[byte(x>>(3*8))] +
    pc[byte(x>>(4*8))] +
    pc[byte(x>>(5*8))] +
    pc[byte(x>>(6*8))] +
    pc[byte(x>>(7*8))])
}

init 計算 pc[0] ~ pc[255] 的值。

i = 0, i/2 = 0, i&1 = 0, pc[0] = 0
i = 1, i/2 = 0, i&1 = 1, pc[1] = 1
i = 2, i/2 = 1, i&1 = 0, pc[2] = 1
i = 3, i/2 = 1, i&1 = 1, pc[3] = 2
i = 4, i/2 = 2, i&1 = 0, pc[4] = 1
i = 5, i/2 = 2, i&1 = 1, pc[5] = 2
i = 6, i/2 = 3, i&1 = 0, pc[6] = 2
i = 7, i/2 = 3, i&1 = 1, pc[7] = 3
i = 8, i/2 = 4, i&1 = 0, pc[8] = 1
i = 9, i/2 = 4, i&1 = 1, pc[9] = 2
i = 10, i/2 = 5, i&1 = 0, pc[10] = 2
i = 11, i/2 = 5, i&1 = 1, pc[11] = 3
i = 12, i/2 = 6, i&1 = 0, pc[12] = 2
i = 13, i/2 = 6, i&1 = 1, pc[13] = 3
i = 14, i/2 = 7, i&1 = 0, pc[14] = 3
i = 15, i/2 = 7, i&1 = 1, pc[15] = 4
i = 16, i/2 = 8, i&1 = 0, pc[16] = 1
i = 17, i/2 = 8, i&1 = 1, pc[17] = 2
i = 18, i/2 = 9, i&1 = 0, pc[18] = 2
i = 19, i/2 = 9, i&1 = 1, pc[19] = 3
i = 20, i/2 = 10, i&1 = 0, pc[20] = 2
i = 21, i/2 = 10, i&1 = 1, pc[21] = 3
i = 22, i/2 = 11, i&1 = 0, pc[22] = 3
i = 23, i/2 = 11, i&1 = 1, pc[23] = 4
i = 24, i/2 = 12, i&1 = 0, pc[24] = 2
i = 25, i/2 = 12, i&1 = 1, pc[25] = 3
i = 26, i/2 = 13, i&1 = 0, pc[26] = 3
i = 27, i/2 = 13, i&1 = 1, pc[27] = 4
i = 28, i/2 = 14, i&1 = 0, pc[28] = 3
i = 29, i/2 = 14, i&1 = 1, pc[29] = 4
i = 30, i/2 = 15, i&1 = 0, pc[30] = 4
i = 31, i/2 = 15, i&1 = 1, pc[31] = 5
i = 32, i/2 = 16, i&1 = 0, pc[32] = 1
i = 33, i/2 = 16, i&1 = 1, pc[33] = 2
i = 34, i/2 = 17, i&1 = 0, pc[34] = 2
i = 35, i/2 = 17, i&1 = 1, pc[35] = 3
i = 36, i/2 = 18, i&1 = 0, pc[36] = 2
i = 37, i/2 = 18, i&1 = 1, pc[37] = 3
i = 38, i/2 = 19, i&1 = 0, pc[38] = 3
i = 39, i/2 = 19, i&1 = 1, pc[39] = 4
i = 40, i/2 = 20, i&1 = 0, pc[40] = 2
i = 41, i/2 = 20, i&1 = 1, pc[41] = 3
i = 42, i/2 = 21, i&1 = 0, pc[42] = 3
i = 43, i/2 = 21, i&1 = 1, pc[43] = 4
i = 44, i/2 = 22, i&1 = 0, pc[44] = 3
i = 45, i/2 = 22, i&1 = 1, pc[45] = 4
i = 46, i/2 = 23, i&1 = 0, pc[46] = 4
i = 47, i/2 = 23, i&1 = 1, pc[47] = 5
i = 48, i/2 = 24, i&1 = 0, pc[48] = 2
i = 49, i/2 = 24, i&1 = 1, pc[49] = 3
i = 50, i/2 = 25, i&1 = 0, pc[50] = 3
i = 51, i/2 = 25, i&1 = 1, pc[51] = 4
i = 52, i/2 = 26, i&1 = 0, pc[52] = 3
i = 53, i/2 = 26, i&1 = 1, pc[53] = 4
i = 54, i/2 = 27, i&1 = 0, pc[54] = 4
i = 55, i/2 = 27, i&1 = 1, pc[55] = 5
i = 56, i/2 = 28, i&1 = 0, pc[56] = 3
i = 57, i/2 = 28, i&1 = 1, pc[57] = 4
i = 58, i/2 = 29, i&1 = 0, pc[58] = 4
i = 59, i/2 = 29, i&1 = 1, pc[59] = 5
i = 60, i/2 = 30, i&1 = 0, pc[60] = 4
i = 61, i/2 = 30, i&1 = 1, pc[61] = 5
i = 62, i/2 = 31, i&1 = 0, pc[62] = 5
i = 63, i/2 = 31, i&1 = 1, pc[63] = 6
i = 64, i/2 = 32, i&1 = 0, pc[64] = 1
i = 65, i/2 = 32, i&1 = 1, pc[65] = 2
i = 66, i/2 = 33, i&1 = 0, pc[66] = 2
i = 67, i/2 = 33, i&1 = 1, pc[67] = 3
i = 68, i/2 = 34, i&1 = 0, pc[68] = 2
i = 69, i/2 = 34, i&1 = 1, pc[69] = 3
i = 70, i/2 = 35, i&1 = 0, pc[70] = 3
i = 71, i/2 = 35, i&1 = 1, pc[71] = 4
i = 72, i/2 = 36, i&1 = 0, pc[72] = 2
i = 73, i/2 = 36, i&1 = 1, pc[73] = 3
i = 74, i/2 = 37, i&1 = 0, pc[74] = 3
i = 75, i/2 = 37, i&1 = 1, pc[75] = 4
i = 76, i/2 = 38, i&1 = 0, pc[76] = 3
i = 77, i/2 = 38, i&1 = 1, pc[77] = 4
i = 78, i/2 = 39, i&1 = 0, pc[78] = 4
i = 79, i/2 = 39, i&1 = 1, pc[79] = 5
i = 80, i/2 = 40, i&1 = 0, pc[80] = 2
i = 81, i/2 = 40, i&1 = 1, pc[81] = 3
i = 82, i/2 = 41, i&1 = 0, pc[82] = 3
i = 83, i/2 = 41, i&1 = 1, pc[83] = 4
i = 84, i/2 = 42, i&1 = 0, pc[84] = 3
i = 85, i/2 = 42, i&1 = 1, pc[85] = 4
i = 86, i/2 = 43, i&1 = 0, pc[86] = 4
i = 87, i/2 = 43, i&1 = 1, pc[87] = 5
i = 88, i/2 = 44, i&1 = 0, pc[88] = 3
i = 89, i/2 = 44, i&1 = 1, pc[89] = 4
i = 90, i/2 = 45, i&1 = 0, pc[90] = 4
i = 91, i/2 = 45, i&1 = 1, pc[91] = 5
i = 92, i/2 = 46, i&1 = 0, pc[92] = 4
i = 93, i/2 = 46, i&1 = 1, pc[93] = 5
i = 94, i/2 = 47, i&1 = 0, pc[94] = 5
i = 95, i/2 = 47, i&1 = 1, pc[95] = 6
i = 96, i/2 = 48, i&1 = 0, pc[96] = 2
i = 97, i/2 = 48, i&1 = 1, pc[97] = 3
i = 98, i/2 = 49, i&1 = 0, pc[98] = 3
i = 99, i/2 = 49, i&1 = 1, pc[99] = 4
i = 100, i/2 = 50, i&1 = 0, pc[100] = 3
i = 101, i/2 = 50, i&1 = 1, pc[101] = 4
i = 102, i/2 = 51, i&1 = 0, pc[102] = 4
i = 103, i/2 = 51, i&1 = 1, pc[103] = 5
i = 104, i/2 = 52, i&1 = 0, pc[104] = 3
i = 105, i/2 = 52, i&1 = 1, pc[105] = 4
i = 106, i/2 = 53, i&1 = 0, pc[106] = 4
i = 107, i/2 = 53, i&1 = 1, pc[107] = 5
i = 108, i/2 = 54, i&1 = 0, pc[108] = 4
i = 109, i/2 = 54, i&1 = 1, pc[109] = 5
i = 110, i/2 = 55, i&1 = 0, pc[110] = 5
i = 111, i/2 = 55, i&1 = 1, pc[111] = 6
i = 112, i/2 = 56, i&1 = 0, pc[112] = 3
i = 113, i/2 = 56, i&1 = 1, pc[113] = 4
i = 114, i/2 = 57, i&1 = 0, pc[114] = 4
i = 115, i/2 = 57, i&1 = 1, pc[115] = 5
i = 116, i/2 = 58, i&1 = 0, pc[116] = 4
i = 117, i/2 = 58, i&1 = 1, pc[117] = 5
i = 118, i/2 = 59, i&1 = 0, pc[118] = 5
i = 119, i/2 = 59, i&1 = 1, pc[119] = 6
i = 120, i/2 = 60, i&1 = 0, pc[120] = 4
i = 121, i/2 = 60, i&1 = 1, pc[121] = 5
i = 122, i/2 = 61, i&1 = 0, pc[122] = 5
i = 123, i/2 = 61, i&1 = 1, pc[123] = 6
i = 124, i/2 = 62, i&1 = 0, pc[124] = 5
i = 125, i/2 = 62, i&1 = 1, pc[125] = 6
i = 126, i/2 = 63, i&1 = 0, pc[126] = 6
i = 127, i/2 = 63, i&1 = 1, pc[127] = 7
i = 128, i/2 = 64, i&1 = 0, pc[128] = 1
i = 129, i/2 = 64, i&1 = 1, pc[129] = 2
i = 130, i/2 = 65, i&1 = 0, pc[130] = 2
i = 131, i/2 = 65, i&1 = 1, pc[131] = 3
i = 132, i/2 = 66, i&1 = 0, pc[132] = 2
i = 133, i/2 = 66, i&1 = 1, pc[133] = 3
i = 134, i/2 = 67, i&1 = 0, pc[134] = 3
i = 135, i/2 = 67, i&1 = 1, pc[135] = 4
i = 136, i/2 = 68, i&1 = 0, pc[136] = 2
i = 137, i/2 = 68, i&1 = 1, pc[137] = 3
i = 138, i/2 = 69, i&1 = 0, pc[138] = 3
i = 139, i/2 = 69, i&1 = 1, pc[139] = 4
i = 140, i/2 = 70, i&1 = 0, pc[140] = 3
i = 141, i/2 = 70, i&1 = 1, pc[141] = 4
i = 142, i/2 = 71, i&1 = 0, pc[142] = 4
i = 143, i/2 = 71, i&1 = 1, pc[143] = 5
i = 144, i/2 = 72, i&1 = 0, pc[144] = 2
i = 145, i/2 = 72, i&1 = 1, pc[145] = 3
i = 146, i/2 = 73, i&1 = 0, pc[146] = 3
i = 147, i/2 = 73, i&1 = 1, pc[147] = 4
i = 148, i/2 = 74, i&1 = 0, pc[148] = 3
i = 149, i/2 = 74, i&1 = 1, pc[149] = 4
i = 150, i/2 = 75, i&1 = 0, pc[150] = 4
i = 151, i/2 = 75, i&1 = 1, pc[151] = 5
i = 152, i/2 = 76, i&1 = 0, pc[152] = 3
i = 153, i/2 = 76, i&1 = 1, pc[153] = 4
i = 154, i/2 = 77, i&1 = 0, pc[154] = 4
i = 155, i/2 = 77, i&1 = 1, pc[155] = 5
i = 156, i/2 = 78, i&1 = 0, pc[156] = 4
i = 157, i/2 = 78, i&1 = 1, pc[157] = 5
i = 158, i/2 = 79, i&1 = 0, pc[158] = 5
i = 159, i/2 = 79, i&1 = 1, pc[159] = 6
i = 160, i/2 = 80, i&1 = 0, pc[160] = 2
i = 161, i/2 = 80, i&1 = 1, pc[161] = 3
i = 162, i/2 = 81, i&1 = 0, pc[162] = 3
i = 163, i/2 = 81, i&1 = 1, pc[163] = 4
i = 164, i/2 = 82, i&1 = 0, pc[164] = 3
i = 165, i/2 = 82, i&1 = 1, pc[165] = 4
i = 166, i/2 = 83, i&1 = 0, pc[166] = 4
i = 167, i/2 = 83, i&1 = 1, pc[167] = 5
i = 168, i/2 = 84, i&1 = 0, pc[168] = 3
i = 169, i/2 = 84, i&1 = 1, pc[169] = 4
i = 170, i/2 = 85, i&1 = 0, pc[170] = 4
i = 171, i/2 = 85, i&1 = 1, pc[171] = 5
i = 172, i/2 = 86, i&1 = 0, pc[172] = 4
i = 173, i/2 = 86, i&1 = 1, pc[173] = 5
i = 174, i/2 = 87, i&1 = 0, pc[174] = 5
i = 175, i/2 = 87, i&1 = 1, pc[175] = 6
i = 176, i/2 = 88, i&1 = 0, pc[176] = 3
i = 177, i/2 = 88, i&1 = 1, pc[177] = 4
i = 178, i/2 = 89, i&1 = 0, pc[178] = 4
i = 179, i/2 = 89, i&1 = 1, pc[179] = 5
i = 180, i/2 = 90, i&1 = 0, pc[180] = 4
i = 181, i/2 = 90, i&1 = 1, pc[181] = 5
i = 182, i/2 = 91, i&1 = 0, pc[182] = 5
i = 183, i/2 = 91, i&1 = 1, pc[183] = 6
i = 184, i/2 = 92, i&1 = 0, pc[184] = 4
i = 185, i/2 = 92, i&1 = 1, pc[185] = 5
i = 186, i/2 = 93, i&1 = 0, pc[186] = 5
i = 187, i/2 = 93, i&1 = 1, pc[187] = 6
i = 188, i/2 = 94, i&1 = 0, pc[188] = 5
i = 189, i/2 = 94, i&1 = 1, pc[189] = 6
i = 190, i/2 = 95, i&1 = 0, pc[190] = 6
i = 191, i/2 = 95, i&1 = 1, pc[191] = 7
i = 192, i/2 = 96, i&1 = 0, pc[192] = 2
i = 193, i/2 = 96, i&1 = 1, pc[193] = 3
i = 194, i/2 = 97, i&1 = 0, pc[194] = 3
i = 195, i/2 = 97, i&1 = 1, pc[195] = 4
i = 196, i/2 = 98, i&1 = 0, pc[196] = 3
i = 197, i/2 = 98, i&1 = 1, pc[197] = 4
i = 198, i/2 = 99, i&1 = 0, pc[198] = 4
i = 199, i/2 = 99, i&1 = 1, pc[199] = 5
i = 200, i/2 = 100, i&1 = 0, pc[200] = 3
i = 201, i/2 = 100, i&1 = 1, pc[201] = 4
i = 202, i/2 = 101, i&1 = 0, pc[202] = 4
i = 203, i/2 = 101, i&1 = 1, pc[203] = 5
i = 204, i/2 = 102, i&1 = 0, pc[204] = 4
i = 205, i/2 = 102, i&1 = 1, pc[205] = 5
i = 206, i/2 = 103, i&1 = 0, pc[206] = 5
i = 207, i/2 = 103, i&1 = 1, pc[207] = 6
i = 208, i/2 = 104, i&1 = 0, pc[208] = 3
i = 209, i/2 = 104, i&1 = 1, pc[209] = 4
i = 210, i/2 = 105, i&1 = 0, pc[210] = 4
i = 211, i/2 = 105, i&1 = 1, pc[211] = 5
i = 212, i/2 = 106, i&1 = 0, pc[212] = 4
i = 213, i/2 = 106, i&1 = 1, pc[213] = 5
i = 214, i/2 = 107, i&1 = 0, pc[214] = 5
i = 215, i/2 = 107, i&1 = 1, pc[215] = 6
i = 216, i/2 = 108, i&1 = 0, pc[216] = 4
i = 217, i/2 = 108, i&1 = 1, pc[217] = 5
i = 218, i/2 = 109, i&1 = 0, pc[218] = 5
i = 219, i/2 = 109, i&1 = 1, pc[219] = 6
i = 220, i/2 = 110, i&1 = 0, pc[220] = 5
i = 221, i/2 = 110, i&1 = 1, pc[221] = 6
i = 222, i/2 = 111, i&1 = 0, pc[222] = 6
i = 223, i/2 = 111, i&1 = 1, pc[223] = 7
i = 224, i/2 = 112, i&1 = 0, pc[224] = 3
i = 225, i/2 = 112, i&1 = 1, pc[225] = 4
i = 226, i/2 = 113, i&1 = 0, pc[226] = 4
i = 227, i/2 = 113, i&1 = 1, pc[227] = 5
i = 228, i/2 = 114, i&1 = 0, pc[228] = 4
i = 229, i/2 = 114, i&1 = 1, pc[229] = 5
i = 230, i/2 = 115, i&1 = 0, pc[230] = 5
i = 231, i/2 = 115, i&1 = 1, pc[231] = 6
i = 232, i/2 = 116, i&1 = 0, pc[232] = 4
i = 233, i/2 = 116, i&1 = 1, pc[233] = 5
i = 234, i/2 = 117, i&1 = 0, pc[234] = 5
i = 235, i/2 = 117, i&1 = 1, pc[235] = 6
i = 236, i/2 = 118, i&1 = 0, pc[236] = 5
i = 237, i/2 = 118, i&1 = 1, pc[237] = 6
i = 238, i/2 = 119, i&1 = 0, pc[238] = 6
i = 239, i/2 = 119, i&1 = 1, pc[239] = 7
i = 240, i/2 = 120, i&1 = 0, pc[240] = 4
i = 241, i/2 = 120, i&1 = 1, pc[241] = 5
i = 242, i/2 = 121, i&1 = 0, pc[242] = 5
i = 243, i/2 = 121, i&1 = 1, pc[243] = 6
i = 244, i/2 = 122, i&1 = 0, pc[244] = 5
i = 245, i/2 = 122, i&1 = 1, pc[245] = 6
i = 246, i/2 = 123, i&1 = 0, pc[246] = 6
i = 247, i/2 = 123, i&1 = 1, pc[247] = 7
i = 248, i/2 = 124, i&1 = 0, pc[248] = 5
i = 249, i/2 = 124, i&1 = 1, pc[249] = 6
i = 250, i/2 = 125, i&1 = 0, pc[250] = 6
i = 251, i/2 = 125, i&1 = 1, pc[251] = 7
i = 252, i/2 = 126, i&1 = 0, pc[252] = 6
i = 253, i/2 = 126, i&1 = 1, pc[253] = 7
i = 254, i/2 = 127, i&1 = 0, pc[254] = 7
i = 255, i/2 = 127, i&1 = 1, pc[255] = 8
# PopCount
x = 0 PopCount(0) = 0
x = 1 PopCount(1) = 1
x = 2 PopCount(2) = 1
x = 3 PopCount(3) = 2
x = 254 PopCount(254) = 7
x = 255 PopCount(255) = 8

利用 timestamp 計算花多少時間。

如下範例 284671 - 284461 = 210 (micro seconds) (file: popcount.go)。

package main

import (
  "fmt"
  "time"
)

var pc [256]byte

func test() {
  for i := range pc {
    pc[i] = pc[i/2] + byte(i&1)
  }
}

func PopCount(x uint64) int {
  return int(pc[byte(x>>(0*8))] +
    pc[byte(x>>(1*8))] +
    pc[byte(x>>(2*8))] +
    pc[byte(x>>(3*8))] +
    pc[byte(x>>(4*8))] +
    pc[byte(x>>(5*8))] +
    pc[byte(x>>(6*8))] +
    pc[byte(x>>(7*8))])
}

func main() {
  test()
  fmt.Println(time.Now()) // 2022-08-31 14:26:14.284461 +0800 CST m=+0.000095479
  PopCount(255)
  fmt.Println(time.Now()) // 2022-08-31 14:26:14.284671 +0800 CST m=+0.000305969
}

練習 2-3 以單一運算式改寫 PopCount

我認為單一運算式就是查表的意思,以下略過。

利用 timestamp 計算花多少時間。

如下範例 215806 - 215604 = 202 (micro seconds) (file popcount_2_3.go)。

package main

import (
  "fmt"
  "time"
)

var pc [256]byte

func init() {
  for i := range pc {
    pc[i] = pc[i/2] + byte(i&1)
  }
}

func PopCount(x uint64) int {
  count := 0

	for i := uint64(0); i < 8; i++ {
		count += int(pc[byte(x>>(i*8))])
	}
	return count
}

func main() {
  fmt.Println(time.Now()) // 2022-08-31 15:30:45.215604 +0800 CST m=+0.000100824
  PopCount(255)
  fmt.Println(time.Now()) // 2022-08-31 15:30:45.215806 +0800 CST m=+0.000302278
}

練習 2-4 以位移 64 次的方式改寫 PopCount

利用 timestamp 計算花多少時間。

如下範例 114736 - 114936 = 200 (micro seconds) (file popcount_2_4.go)。

package main

import (
  "fmt"
  "time"
)

var pc [256]byte

func init() {
  for i := range pc {
    pc[i] = pc[i/2] + byte(i&1)
  }
}

func PopCount(x uint64) int {
  var counter uint64 = 0

  for i := 0; i < 64; i++ {
    counter += (x >> i) & 0x1
  }

  return int(counter)
}

func main() {
  fmt.Println(time.Now()) // 2022-08-31 14:29:03.114736 +0800 CST m=+0.000097190
  PopCount(255)
  fmt.Println(time.Now()) // 2022-08-31 14:29:03.114936 +0800 CST m=+0.000297015
}

練習 2-5 以 x&(x-1) 改寫 PopCount

利用 timestamp 計算花多少時間。

如下範例 896941 - 896757 = 184 (micro seconds) (file popcount_2_5.go)。

package main

import (
  "fmt"
  "time"
)

var pc [256]byte

func init() {
  for i := range pc {
    pc[i] = pc[i/2] + byte(i&1)
  }
}

func PopCount(x uint64) int {
  counter := 0

  for i := 0; i < 64; i++ {
    x = x&(x-1)

    if (x != 0) {
      counter++
    }
  }

  return counter
}

func main() {
  fmt.Println(time.Now()) // 2022-08-31 15:38:16.896757 +0800 CST m=+0.000094159
  PopCount(255)
  fmt.Println(time.Now()) // 2022-08-31 15:38:16.896941 +0800 CST m=+0.000278211
}
# 花費時間 (micro seconds)
2-3 查表 210
2-4 位移 64 次 200 (直接位移 64 bit 所以比較快)
2-5 刪掉最右邊非零 bit 184 (運算元更少,最快)

#7 範圍

名詞解釋

範例:詞彙區塊

在自己所在的括弧內找不到值,就會往上一層尋找,順序是 local -> global。

package main

import "fmt"

func f() {}

var g = "g"

func main() {
  f := "f"
  fmt.Println(f) // f,在內層宣告找到
  fmt.Println(g) // g,在外層宣告找到
  fmt.Println(h) // 編譯錯誤,未定義
}

得到輸出結果。

f
g
undefined: h

範例:同名但不同範圍的變數

說明:3 個 x 分別代表不同變數,因為其範圍不同而遮蔽上一層的值。

package main

import "fmt"

func main() {
  x := "hello!"

  for i := 0; i < len(x); i++ {
    x := x[i]

    if x != '!' {
      x := x + 'A' - 'a'
      fmt.Printf("%c", x) // HELLO
    }
  }
}

改寫 3 個 x 分別為 x、y、z 以便閱讀。

package main

import "fmt"

func main() {
  x := "hello!"

  for i := 0; i < len(x); i++ {
    y := x[i]

    if y != '!' {
      z := y + 'A' - 'a'
      fmt.Printf("%c", z) // HELLO
    }
  }
}

範例:隱含區塊

說明:

package main

import "fmt"

func f(a int) int { return a }
func g(b int) int { return b }

func main() {
  a := 1

  if x := f(a); x == 0 {
    fmt.Println(x) // (1)
  } else if y := g(x); x == y {
    fmt.Println(x, y) // (2)
  } else {
    fmt.Println(x, y) // (3)
  }

  fmt.Println(x, y) // (4) undefined: x, undefined: y
}

範例:宣告順序 vs 範圍

說明:f 的範圍只有在 if 之內,因此 if 之內沒有用到而報錯、後面的函式用到也會報錯未定義。

if f, err := os.Open("hello_world.txt"); err != nil { // f declared but not used
  return err
}

f.ReadByte() // undefined: f
f.close()    // undefined: f

改寫,讓 f 可以在條件與後續都能存取。

f, err := os.Open("hello_world.txt")

if err != nil {
  return err
}

f.ReadByte()
f.close()

再次改寫,讓 f 只限定於 if 區塊內存取。

if f, err := os.Open("hello_world.txt"); err != nil {
  return err
} else {
  f.ReadByte()
  f.close()
}

範例:避免不當宣告所產生的範圍

說明:由於 cwd 與 err 都沒有在 main 宣告過,因此 := 將其宣告為區域變數,導致 cwd 報錯未使用。

package main

import (
  "log"
  "os"
)

var cwd string

func main() {
  cwd, err := os.Getwd() // cwd declared but not used

  if (err) != nil {
    log.Fatal("os.Getwd failed: %v", err)
  }
}

改寫,在最後印出 cwd 的值,讓 cwd 被用到,就不會報錯。

package main

import (
  "log"
  "os"
)

var cwd string

func main() {
  cwd, err := os.Getwd()

  if (err) != nil {
    log.Fatal("os.Getwd failed: %v", err)
  }

  log.Printf("Working directory = %s", cwd) // Working directory = xxx/xxx/xxx
}

改寫,捨棄 := 這種會宣告為區域變數的作法,改用 var 分開宣告 err 即可。

package main

import (
  "log"
  "os"
)

var cwd string

func main() {
  var err error

  cwd, err = os.Getwd()

  if (err) != nil {
    log.Fatal("os.Getwd failed: %v", err)
  }
}

參考資料


Golang 精通 Go 程式設計 Go The Go Programming Language 讀書會 趨勢科技 Trend Micro 閱讀筆記