精通Go程式設計
Chapter 1 - Basic components of Go.
從Hello, World看核心概念
go run helloworld.go
go run
go build hellpworld.go
go build
helloworld
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.
// 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: 必要時管理匯入宣告的插入與移除
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:))
// 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)
}
}
}
https://pkg.go.dev/fmt#hdr-Printing
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…
f
%v
, followed by a newline
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.ReadFile
和ioutil.WriteFile
在底層使用*os.File的Read與Write method,但很少有程式設計師需要直接存取這些低階程序。來自bufio與io/ioutil的高階函式比較容易使用
// 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
}
Go提供一群集合於net之下的之下的packages讓發送與接收網路資料、低階網路連、設置伺服器變得容易,也使得Go的concurrency功能特別有用 (introduced in Ch8)
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)
}
}
// 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)
}
ref: https://go.dev/tour/concurrency/2
// 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)
}
// 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
// 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)
}
}
os.Stdout
ioutil.Discard
拋棄回應(只計算長度)滿足一個共同interface - io.Writer
沒有提到的部分…
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
type Point struct {
x, y int
}
var p Point