Try   HackMD

精通Go程式設計
Chapter 1 - Basic components of Go.

1-1 Hello, World

從Hello, World看核心概念

  • C對Go有最直接的影響
  • Go是編譯語言(compiled language)
  • Go預設處理Unicode

Go CLI

go run helloworld.go

go run

  • compiles and excutes

go build hellpworld.go

go build

  • compiles only
  • create an executable binary file called helloworld

Go standard library

https://pkg.go.dev/std

  • has over 100 packages for common tasks like I/O, sorting, and text manipulation.

Ex:
fmt: fmt 使用類似於 C 的 printf 和 scanf 的功能實現格式化 I/O
Println: One of the basic output functions in fmt; Println inserts blanks between operands and appends a newline.

Package main

  • 定義獨立可執行的程式(executable program)而非函式庫(library).
  • 程式執行的起點,程式的執行就是執行main
// main.js - 程式執行的起點 // Go不需要在陳述或宣告後面加分號(;),除非兩個或以上放在同一行(ex: expression in for loop) package main // 告訴compiler需要哪些套件(packages) // import 宣告必須接載package宣告之後 import "fmt" // 函式宣告 func 函式名稱(參數列) 結果列 {} // func的{括弧必須與func宣告放在同一行 func main() { fmt.Println("Hello, 世界") }
  • x + y運算式中的換行可以放在+運算子的後面,但不能放在+運算子之前 - example

  • gofmt: 存檔時執行gofmt,可以讓程式一直保持正確format

  • goimpots: 必要時管理匯入宣告的插入與移除

1-2 命令列參數 (Command-Line Arguments)

os.Args[i]
  • The variable os.Args is a slice of strings
  • s[i]: access value by index
  • len(s): check length of slice
  • s[m:n]: 0 <= m <= n <= len(s)
  • os.Args[0]: the name of the command itself
  • os.Args[1:len(os.Args)] == os.Args[i:]
// Echo1 prints its command-line arguments. package main import ( "fmt" "os" ) func main() { // declares 2 variables; default value is the empty string ("") var s, sep string // := 短變數宣告的一部分 // i++ 為陳述式(statements)而非運算式(expressions) // j = i++ is illegal // ++i/--i is illegal // 小括號不需要,大括號必要且必須與post statement在同一行 for i := 1; i < len(os.Args); i++ { s += sep + os.Args[i] // == s = s + sep + os.Args[i] sep = " " } fmt.Println(s) }
// a traditional "while" loop for condition { // ... }
// a traditional infinite loop for { // ... }
// Echo2 prints its command-line arguments. package main import ( "fmt" "os" ) func main() { // 一般使用前2宣告方式 // s := "" 較緊湊,只能用於函式中而非套件層級變數 // var s string 依靠預設初始化給字串鎖定零值 // var s = "" 除了在宣告多個變數外很少用 // var s string = "" 明確設定變數型別 s, sep := "", "" // range 產出一對值: index, value // _ : 空辨識符(blank identifier) for _, arg := range os.Args[1:] { s += sep + arg sep = " " } fmt.Println(s) }
// 若資料量大 fmt.Println(strings.Join(os.Args[1:], " ")) // 若不在乎格式只想看到value fmt.Println(os.Arg(1:))

1-3 找出重複行 Finding Duplicate Lines

// Dup1 prints the text of each line that appears more than // once in the standard input, preceded by its count. package main import ( "bufio" "fmt" "os" ) func main() { // map 保存一組key/value pairs 並提供儲存、讀取、檢測元素的操作 // key 可以是任何能用==比較值的型別,通常是string // value 可以是任何型別 // built-in function make 可以建立一個empty map counts := make(map[string]int) // 詳見4.3 // bufio: Package bufio implements buffered I/O. // 最重要的功能之一是讀取input,並將其拆成多行的Scanner型別 input := bufio.NewScanner(os.Stdin) // input.Scan() 讀取下一行並移除後面的換行字元 // return true - 有下一行 // return flase - 沒下一行 // input.Text() 取得結果 for input.Scan() { // line := input.Text() // counts[line] = counts[line] + 1 counts[input.Text()]++ } // NOTE: ignoring potential errors from input.Err() // line: key; n: value // map 迭代的順序沒有指定,但實際上是隨機的,每一次的結果可能不同 for line, n := range counts { if n > 1 { // formatted output fmt.Printf("%d\t%s\n", n, line) } } }

formatted output

https://pkg.go.dev/fmt#hdr-Printing

  • Printf has over a dozen such conversions
  • Go programmers call verbs
verb description
%d 整數(decimal integer)
%x, %o, %b 十六進位(hexadecimal)、八進位(octal)、二進位(binary)整數
%f, %g, %e 浮點數(floating-point number): 3.141593 3.141593e+00
%t boolean: true or false
%c 字元(rune), Unicode code point
%s string
%q 加引號的"abc"字串或'c'碼位
%v any value in a natual format
%T type of any value
%% literal percent sign; consumes no value
escape sequence description
\t tab
\n newline

By convention

  • formatting functions whose names end inf
    • e.g., log.Printf and fmt.Errorf
  • formatting their arguments as if by %v, followed by a newline
    • e.g., log.Println

Next example

// Dup2 prints the count and text of lines that appear more than once // in the input. It reads from stdin or from a list of named files. package main import ( "bufio" "fmt" "os" ) func main() { // A map is a reference to the data structure created by make. // The values inserted into the counts map by countLines are seen by main counts := make(map[string]int) files := os.Args[1:] if len(files) == 0 { countLines(os.Stdin, counts) } else { for _, arg := range files { // f: 開啟的檔案(*os.File) // err: built-in error type. If err == nil, the file was opened successfully f, err := os.Open(arg) if err != nil { // details of error handling in Section 5.4 fmt.Fprintf(os.Stderr, "dup2: %v\n", err) continue } countLines(f, counts) f.Close() } } for line, n := range counts { if n > 1 { fmt.Printf("%d\t%s\n", n, line) } } } func countLines(f *os.File, counts map[string]int) { input := bufio.NewScanner(f) for input.Scan() { counts[input.Text()]++ } // NOTE: ignoring potential errors from input.Err() }

Next example

// Dup3 prints the count and text of lines that // appear more than once in the named input files. // 只讀取檔案,沒有input package main import ( "fmt" "io/ioutil" "os" "strings" ) func main() { counts := make(map[string]int) for _, filename := range os.Args[1:] { data, err := ioutil.ReadFile(filename) if err != nil { fmt.Fprintf(os.Stderr, "dup3: %v\n", err) continue } // data: a byte slice // string(data): convert byte slice to string // strings.Split(string): convert string to a string slice. (https://pkg.go.dev/strings#Split) for _, line := range strings.Split(string(data), "\n") { counts[line]++ } } for line, n := range counts { if n > 1 { fmt.Printf("%d\t%s\n", n, line) } } }

bufio.Scanner, ioutil.ReadFileioutil.WriteFile 在底層使用*os.File的Read與Write method,但很少有程式設計師需要直接存取這些低階程序。來自bufio與io/ioutil的高階函式比較容易使用

1-4 動態GIF (Animated GIFs)

  • Go的標準圖形套件
  • 建構點陣圖然後產生GIF
  • 利薩茹曲線(Lissajous figures)
// Lissajous generates GIF animations of random Lissajous figures. package main import ( "image" "image/color" "image/gif" "io" "math" "math/rand" "os" ) // []color.Color{...} and gif.GIF{...} are 組合實字(composite literals, 4.2 & 4.4.1) var palette = []color.Color{color.White, color.Black} // 常數. must be a number, string or boolean const ( whiteIndex = 0 // first color in palette blackIndex = 1 // next color in palette ) func main() { lissajous(os.Stdout) } func lissajous(out io.Writer) { const ( cycles = 5 // number of complete x oscillator revolutions res = 0.001 // angular resolution size = 100 // image canvas covers [-size..+size] nframes = 64 // number of animation frames delay = 8 // delay between frames in 10ms units ) freq := rand.Float64() * 3.0 // relative frequency of y oscillator anim := gif.GIF{LoopCount: nframes} phase := 0.0 // phase difference for i := 0; i < nframes; i++ { rect := image.Rect(0, 0, 2*size+1, 2*size+1) img := image.NewPaletted(rect, palette) for t := 0.0; t < cycles*2*math.Pi; t += res { x := math.Sin(t) y := math.Sin(t*freq + phase) img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5), blackIndex) } phase += 0.1 anim.Delay = append(anim.Delay, delay) anim.Image = append(anim.Image, img) } gif.EncodeAll(out, &anim) // NOTE: ignoring encoding errors }

1-5 抓URL內容 (Fetching a URL)

Go提供一群集合於net之下的之下的packages讓發送與接收網路資料、低階網路連、設置伺服器變得容易,也使得Go的concurrency功能特別有用 (introduced in Ch8)

  • 抓取指定URL的內容並原封不動的輸出
  • 受到curl這個實用工具的啟發
package main import ( "fmt" "io/ioutil" "net/http" "os" ) func main() { for _, url := range os.Args[1:] { // 發出HTTP request resp, err := http.Get(url) if err != nil { fmt.Fprintf(os.Stderr, "fetch: %v\n", err) // 在錯誤情況下,會導致程序以status code 1 結束 os.Exit(1) } b, err := ioutil.ReadAll(resp.Body) // Body stream關閉避免資料洩漏(leaking resources) resp.Body.Close() if err != nil { fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err) os.Exit(1) } fmt.Printf("%s", b) } }

1-6 並行抓取URL內容 (Fetching URLs Concurrently)

  • Go最有趣的與值得注意的部分是它支援並行程式設計(concurrent programing) - 詳見Ch8, Ch9
  • 這邊只會稍微提到Go的concurrency, goroutines and channels.
// Fetchall fetches URLs in parallel and reports their times and sizes. package main import ( "fmt" "io" "io/ioutil" "net/http" "os" "time" ) func main() { start := time.Now() // 使用make建構channel字串 ch := make(chan string) for _, url := range os.Args[1:] { go fetch(url, ch) // go statement. start a goroutine } for range os.Args[1:] { fmt.Println(<-ch) // receive from channel ch } fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds()) } func fetch(url string, ch chan<- string) { start := time.Now() resp, err := http.Get(url) if err != nil { ch <- fmt.Sprint(err) // send to channel ch return } // io.Copy讀取response並寫到ioutil.Discard輸出串流以將它拋棄 nbytes, err := io.Copy(ioutil.Discard, resp.Body) resp.Body.Close() // don't leak resources if err != nil { ch <- fmt.Sprintf("while reading %s: %v", url, err) return } secs := time.Since(start).Seconds() ch <- fmt.Sprintf("%.2fs %7d %s", secs, nbytes, url) }
  • goroutine: 一個concurrent function的執行
  • channel: 讓goroutine傳遞特定type給其他goroutine的通訊機制
  • main 函式在一個goroutine中執行且go陳述(statement)建構額外的goroutine

ref: https://go.dev/tour/concurrency/2

1-7 網頁伺服器 (A Web Server)

// Server1 is a minimal "echo" server. package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/", handler) // each request calls handler log.Fatal(http.ListenAndServe("localhost:8000", nil)) } // handler echoes the Path component of the requested URL. func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path) }

Server 2

// Server2 is a minimal "echo" and counter server. package main import ( "fmt" "log" "net/http" "sync" ) var mu sync.Mutex var count int func main() { // two handlers // server在幕後為每個獨立的goroutine上執行處理程序,因此它可以同時處理多個請求 // 可能有競爭條件(race condition)的問題 http.HandleFunc("/", handler) http.HandleFunc("/count", counter) log.Fatal(http.ListenAndServe("localhost:8000", nil)) } // handler echoes the Path component of the requested URL. // mu.Lock() & mu.Unlock() 包圍count存取的目的為確保每次最多只有一個goroutine存取該變數 func handler(w http.ResponseWriter, r *http.Request) { mu.Lock() count++ mu.Unlock() fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path) } // counter echoes the number of calls so far. func counter(w http.ResponseWriter, r *http.Request) { mu.Lock() fmt.Fprintf(w, "Count %d\n", count) mu.Unlock() }

詳見Ch9

Server 3

// Server3 is an "echo" server that displays request parameters. package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/", handler) log.Fatal(http.ListenAndServe("localhost:8000", nil)) } // handler echoes the HTTP request. func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "%s %s %s\n", r.Method, r.URL, r.Proto) for k, v := range r.Header { fmt.Fprintf(w, "Header[%q] = %q\n", k, v) } fmt.Fprintf(w, "Host = %q\n", r.Host) fmt.Fprintf(w, "RemoteAddr = %q\n", r.RemoteAddr) // err := r.ParseForm() // if err != nil { // log.Print(err) // } if err := r.ParseForm(); err != nil { log.Print(err) } for k, v := range r.Form { fmt.Fprintf(w, "Form[%q] = %q\n", k, v) } }
  • fetch 程式複製HTTP response data給os.Stdout
  • fetchall 以 ioutil.Discard 拋棄回應(只計算長度)
  • httpResponseWriter

滿足一個共同interface - io.Writer

1-8 補充說明

沒有提到的部分

Control flow: ✅ if, ✅ for, ❌ switch

switch coinflip() { case "heads": head++ case "tails": tails++ default: fmt.Println("landed on edge!") }

tagless switch; It's equivalent to switch true

func Signum(x int) int { switch { case x > 0: return +1 default: return 0 case x < 0: return 1 } }

https://go.dev/play/p/KTClbi5T3VW

具名型別 (named types)

type Point struct { x, y int } var p Point

Pointers

  • & operator and * operator
    • & 產生變數的位址
      • 取得該pointer所指的變數
  • values that contain the address of a variable

Methods and interface

  • Method: Ch6
    • Go is unusual in that methods may be attached to almost any named type
  • Interface: Ch7
    • Interfaces are abstract types that let us treat different concrete types in the same way based on what methods they have, not how they are represented or implemented.

Packages

https://pkg.go.dev/std

Comments

  • //
  • /* */

Reference