精通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/