精通Go程式設計 Chapter 1 - Basic components of Go. # 1-1 Hello, World 從Hello, World看核心概念 * C對Go有最直接的影響 * Go是編譯語言(compiled language) * Go預設處理Unicode ## Go CLI ```bash= go run helloworld.go ``` go run * compiles and excutes ----- ```bash= 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 ```go= // 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](https://go.dev/play/p/KmEZ5Z-OvMF) * gofmt: 存檔時執行gofmt,可以讓程式一直保持正確format * goimpots: 必要時管理匯入宣告的插入與移除 # 1-2 命令列參數 (Command-Line Arguments) ```go= 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:] ```go= // 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) } ``` ```go= // a traditional "while" loop for condition { // ... } ``` ```go= // a traditional infinite loop for { // ... } ``` ```go= // 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) } ``` ```go= // 若資料量大 fmt.Println(strings.Join(os.Args[1:], " ")) // 若不在乎格式只想看到value fmt.Println(os.Arg(1:)) ``` # 1-3 找出重複行 Finding Duplicate Lines ```go= // 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 in`f` * e.g., log.Printf and fmt.Errorf * formatting their arguments as if by `%v`, followed by a newline * e.g., log.Println Next example... ```go= // 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... ```go= // 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.ReadFile` 和 `ioutil.WriteFile` 在底層使用*os.File的Read與Write method,但很少有程式設計師需要直接存取這些低階程序。來自bufio與io/ioutil的高階函式比較容易使用 # 1-4 動態GIF (Animated GIFs) * Go的標準圖形套件 * 建構點陣圖然後產生GIF * 利薩茹曲線(Lissajous figures) ```go= // 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這個實用工具的啟發 ```go= 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. ```go= // 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) ```go= // 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 ```go= // 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 ```go= // 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 ```go= switch coinflip() { case "heads": head++ case "tails": tails++ default: fmt.Println("landed on edge!") } ``` tagless switch; It's equivalent to `switch true` ```go= 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) ```go= 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 * http://www.gopl.io/ * https://www.openmymind.net/The-Little-Go-Book/