- Package: 套件名稱
package main
- Import: 引用 library
import "fmt"
// or
import (
"fmt"
)
- Main: 程式運行的入口
func main() {
fmt.Println("Hello")
}
- Export Name: Library export 的 function 名稱必須為
大寫
開頭
- Function
func add(x int, y int) int {
return x + y
}
// 多值返回
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap(1, 2)
}
// 命名返回值
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
- 變數宣告
- var, const
- const 不能使用
:=
語法定義。
- 短聲明變數
- 在函式中,
:=
簡潔賦值語句在明確類型的地方,可以用於替代 var
定義。
- 函式外的每個語法塊都必須以關鍵字開始(
var
、 func
、等等), :=
結構不能使用在函式外。
- basic types
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 的別名
rune // int32 的別名, 代表一個Unicode碼
float32 float64
complex64 complex128
- loop
// for
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum)
}
// while
func main() {
sum := 1
for sum < 1000 {
sum += sum
}
fmt.Println(sum)
}
- if
// 便捷語句定義: v 作用域僅在 if 和 else 範圍之內
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("%g >= %g\n", v, lim)
}
- switch: 除非以
fallthrough
語句結束,否則分支會自動終止。
func main() {
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
fmt.Printf("%s.", os)
}
}
- struct
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
v.X = 4
fmt.Println(v.X)
}
- 指針: Go有指針,但是沒有指針運算。
type Vertex struct {
X int
Y int
}
func main() {
p := Vertex{1, 2}
q := &p
q.X = 1e9
fmt.Println(p)
}
- new 函式: 表達式 new(T) 分配了一個零初始化的 T 值,並返回指向它的指針。
var t *T = new(T)
// or
t := new(T)
- 陣列: 類型
[n]T
是一個有 n 個類型為 T 的值的陣列
var a [10]int
p := []int{2, 3, 5, 7, 11, 13}
q := p[1:4]
- 構造 slice
// slice 由函式 make 創建。這會分配一個零長度的陣列並且返回一個 slice 指向這個陣列
a := make([]int, 5) // len(a)=5
// 為了指定容量,可傳遞第三個參數到 make
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
- 空 slice: slice 的零值是
nil
- range
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
}
/*
* output
* 2**0 = 1
* 2**1 = 2
* 2**2 = 4
* 2**3 = 8
* 2**4 = 16
*/
- map
- map 映射鍵到值。
- map 在使用之前必須用
make
而不是 new
來創建;值為 nil
的 map 是空的,並且不能賦值
// example 1
var m map[string]Vertex
func main() {
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])
}
// example 2
var m = map[string]Vertex{
"Bell Labs": Vertex{
40.68433, -74.39967,
},
"Google": Vertex{
37.42202, -122.08408,
},
}
// exmaple 3: 如果頂級的類型只有類型名的話,可以在文法的元素中省略鍵名。
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}
// 在 map m 中插入或修改一個元素:
m[key] = elem
// 獲得元素:
elem = m[key]
// 刪除元素:
delete(m, key)
// 通過雙賦值檢測某個鍵存在:
// 如果 key 在 m 中, ok 為 true 。否則為 false ,並且 elem 是 map 的元素類型的零值。
elem, ok = m[key]
- 函式為值
func main() {
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(3, 4))
}
- 函式的閉包: Go 函式可以是閉包的。閉包是一個函式值,它來自函式體的外部的變數引用。函式可以對這個引用值進行訪問和賦值
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
}
- Method: Go 沒有類。然而,仍然可以在結構體類型上定義方法。
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := &Vertex{3, 4}
fmt.Println(v.Abs())
}
- 接收者為指針的方法: 方法可以與命名類型或命名類型的指針關聯。
- 有兩個原因需要使用指針接收者:
- 首先避免在每個方法調用中拷貝值(如果值類型是大的結構體的話會更有效率)
- 其次,方法可以修改接收者指向的值。
- pass by value or pass by reference
type Vertex struct {
X, Y float64
}
/*
* Important
* 如果使用 pass by value, 即 func (v Vertex), 則 Scale 沒任何作用,並不會修改到原本 v 所指到的位置的值
*/
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := &Vertex{3, 4}
v.Scale(5)
fmt.Println(v, v.Abs())
}
- 介面
- 介面類型是由一組方法定義的集合。
- 介面類型的值可以存放實現這些方法的任何值。
type Abser interface {
Abs() float64
}
func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}
a = f // a MyFloat implements Abser
a = &v // a *Vertex implements Abser
// In the following line, v is a Vertex (not *Vertex)
// and does NOT implement Abser.
a = v
fmt.Println(a.Abs())
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
- 隱式介面
- 類型通過實現那些方法來實現介面。
- 沒有顯式聲明的必要。
- 隱式介面解藕了實現介面的包和定義介面的包:互不依賴。
- 因此,也就無需在每一個實現上增加新的介面名稱,這樣同時也鼓勵了明確的介面定義。
type Reader interface {
Read(b []byte) (n int, err error)
}
type Writer interface {
Write(b []byte) (n int, err error)
}
type ReadWriter interface {
Reader
Writer
}
func main() {
var w Writer
// os.Stdout implements Writer
w = os.Stdout
fmt.Fprintf(w, "hello, writer\n")
}
- 錯誤
type MyError struct {
When time.Time
What string
}
func (e *MyError) Error() string {
return fmt.Sprintf("at %v, %s", e.When, e.What)
}
func run() error {
return &MyError{time.Now(), "it didn't work"}
}
func main() {
if err := run(); err != nil {
fmt.Println(err)
}
}
- Web 服務器
- 包 http 通過任何實現了 http.Handler 的值來響應 HTTP 請求:
package http
type Handler interface {
ServeHTTP(w ResponseWriter, r *Request)
}
- example
package main
import (
"fmt"
"net/http"
)
type Hello struct{}
func (h Hello) ServeHTTP(
w http.ResponseWriter,
r *http.Request) {
fmt.Fprint(w, "Hello!")
}
func main() {
var h Hello
http.ListenAndServe("localhost:4000", h)
}
- HTTP 處理: 在 web 服務器中註冊它們來處理指定的路徑。
http.Handle("/string", String("I'm a frayed knot."))
http.Handle("/struct", &Struct{"Hello", ":", "Gophers!"})
- channel是有類型的管道,可以用channel操作符
<-
對其發送或者接收值。
// 和 map 與 slice 一樣,channel 使用前必須創建:
ch := make(chan int)
ch <- v // 將 v 送入 channel ch。
v := <-ch // 從 ch 接收,並且賦值給 v。
- 默認情況下,在另一端準備好之前,發送和接收都會阻塞。這使得 goroutine 可以在沒有明確的鎖或競態變數的情況下進行同步。
- Example
func sum(a []int, c chan int) {
sum := 0
for _, v := range a {
sum += v
}
c <- sum // send sum to c
}
func main() {
a := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(a[:len(a)/2], c)
go sum(a[len(a)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}
- 緩衝 channel
func main() {
c := make(chan int, 2)
c <- 1
c <- 2
fmt.Println(<-c)
fmt.Println(<-c)
}
- range 和 close
- 發送者可以 close 一個 channel 來表示再沒有值會被發送了
- 接收者可以通過賦值語句的第二參數來測試channel是否被關閉
- 當沒有值可以接收並且 channel 已經被關閉,那麼 ok 會被設置回 false
v, ok := <-ch
- 循環
for i := range c
會不斷從channel接收值,直到它被關閉。
- Note
- 只有發送者才能關閉 channel,而不是接收者。
- 向一個已經關閉的 channel 發送數據會引起 panic。
- 還要注意: channel與文件不同;通常情況下無需關閉它們
- 只有在需要告訴接收者沒有更多的數據的時候才有必要進行關閉,例如中斷一個 range
- select
select
語句使得一個 goroutine 在多個通訊操作上等待
select
會阻塞,直到條件分支中的某個可以繼續執行,這時就會執行那個條件分支。當多個都準備好的時候,會隨機選擇一個。
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
// 把 x 丟到 channel, 當 channel 滿的時候會 block, 必須等到 channel 被清空(取值)後, 這個條件才能繼續執行
case c <- x:
x, y = y, x+y
// 從 channel 取值, 必須等到 channel quit 有值, 這個條件才能執行
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}