變數
var a int
var b, c bool = true, false
// factored style
var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
z complex128 = complx.Sqrt(-5 + 12i)
)
func main() {
k := 3 // 函式內可以使用短宣告
}
注意,
:=
是宣告符,不是賦值符
陣列和切片
var a [10]int // 陣列,型別為 [10]int
a[0] = 1
primes := [6]int{2, 3, 5, 7, 11, 13} // 陣列字面量
var s []int = primes[1:4] // 切片類似陣列引用,前閉後開。型別為 []T
s := []int{2, 3} // 切片字面量
s = append(s, 5) // 向切片新增元素
a := make([]int, 5) // 使用 make 構造切片
函式
func add(x, y int) int {
return x + y
}
// 返回多個值
func swap(x, y string) (string, string) {
return y, x
}
a, b := swap("world", "hello")
指標
var p *int // int 的指標
p = &a // 取地址
*p = 2 // 解引用
迴圈
// go 只有 for 迴圈
for i := 0; i < 10; i++ {
sum += i
}
// for in range
for i, v := range arr {
fmt.Printf("index: %d, value: v\n", i, v)
}
// omit value
for i := range arr {
fmt.Printf("index: %d\n", i)
}
// omit index
for _, v := range arr {
fmt.Printf("value: %d\n", v)
}
// "for" is go's "while"
for sum < 1000 {
sum += sum
}
// forever
for {
sum += 1
}
if 和 switch
if x < 0 {
sum += 1
} else {
sum -= 1
}
// if 可以帶一個短表示式
if v := math.Pow(x, n); v < lim { // 只有首字母大寫的名字會被匯出,因此是 Pow
return v
}
// switch,不需要 break
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.\n", os)
}
函式閉包
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
每次呼叫 addr()
時都返回一個閉包,這個閉包和本次呼叫的 addr()
內的 sum
繫結
map
var m map[string]int // string: int
m = make(map[string]int)
m["math_score"] = 100
// map 字面量
var m = map[string]int{
"math_score": 150,
"phy_score": 110,
}
// 如果頂級型別僅是一個型別名稱,您可以從字面量的元素中省略它。
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}
m[key] // 插入/取出/更新
delete(m, key) // 刪除元素
elem, ok = m[key] // 檢測 key 是否在 map 中。如果在,則 ok == true
elem, ok := m[key] // 如果 elem, ok 還沒有被宣告,你可以使用這種短宣告形式
結構體
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2} // 結構體字面量
v.X = 4 // 使用結構體
p := &v // p 是結構體的指標
p.X = 1e9 // 隱式解引用
v2 := Vertex{X: 1} // 只為指定成員變數賦值
}
關於 type
關鍵字的更多用法:你可以將基本資料型別包裝為自己的型別。
type MyFloat float64
type IPAddr [4]byte // 定義 IP 地址型別
方法
Go 語言沒有類,但是你可以為型別指定方法。
// 透過在函式名前面加上 receiver 引數列表來將函式變為方法
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
// 由於方法經常用來改變 receiver 內部的值,所以 receiver 指標更為常用
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
var v Vertex
v.Scale // 這裡本來需要提供一個指標,但是 go 進行了指標間接訪問,把 v.Scale 解釋為了 (&v).Scale
p = &v
p.Abs() // 反過來也是,這裡本來需要一個 value,但是卻提供了指標。go 將 p.Abs 解釋為了 (*p).Abs
介面
介面型別是方法簽名的一個集合
type Abser interface {
Abs() float64
}
// 任何實現了介面方法的型別都隱式實現了介面
func (f MyFloat) Abs() float64 {
...
}
func (v *Vertex) Abs() float64 {
// 可以這樣優雅地處理“空指標”引用
if t == nil {
fmt.Println("<nil>")
return
}
// 介面變數可以儲存任何實現了介面方法的值
var a Abser // nil,一個零值介面既不持有值也不持有具體型別
a = MyFloat(-math.Sqrt2)
a = &Vertex{3, 4}
// 空介面被用於處理未知型別值的程式碼。例如,fmt.Print 接受任意數量的 interface{} 型別引數。
func describe(i interface{}) {
fmt.Printf("(%v, %T)\n", i, i)
}
// 在 Go 1.18 中,interface{} 被 any 型別代替
// 型別斷言
f, ok := i.(float64) // 如果介面 i 的底層是 float64,那麼 f = float64,否則 ok = false
// type switches
switch v := i.(type) {
case T:
// here v has type T
case S:
// here v has type S
default:
// no match; here v has the same type as i
}
// 最常見的介面:fmt 包定義的 Stringer 介面
type Stringer interface {
String() string
}
func (p Person) String() string {
return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}
錯誤
Go程式用 error
值表示錯誤狀態。
error
型別是一個內建介面,類似於 fmt.Stringer
:
type error interface {
Error() string
}
函式通常會返回一個 error
值,呼叫程式碼應透過測試 error
是否等於 nil
來處理錯誤。
i, err := strconv.Atoi("42")
if err != nil {
fmt.Printf("couldn't convert number: %v\n", err)
return
}
fmt.Println("Converted integer:", i)
// 實現一個 error 結構體
type MyError struct {
When time.Time
What string
}
// 實現 error 介面
func (e *MyError) Error() string {
return fmt.Sprintf("at %v, %s", e.When, e.What)
}
協程
func foo() {
...
}
go foo() // 啟動協程
通道
通道是一種型別化的管道,透過它你可以使用通道運算子 <-
傳送和接收值。
預設情況下,傳送和接收操作會阻塞,直到另一方準備就緒。這使得 goroutines
可以在沒有顯式鎖或條件變數的情況下進行同步。
ch := make(chan int) // 與 map 和 slice 一樣,通道必須在使用之前建立
ch <- v // 將 v 傳送到通道 ch
v := <-ch // 從 ch 接收,並將值賦給 v
// 建立接收通道引數的函式
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 將 sum 傳送給 c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
sum(s[:len(s)/2], c)
x := <-c // 從 c 接收
fmt.Println(x)
}
帶有緩衝區的通道:
ch := make(chan int, 100) // 建立一個緩衝區長度為 100 的通道
當緩衝區已滿時,傳送到緩衝通道會被阻塞。當緩衝區為空時,接收會被阻塞。
close(ch) // 傳送方可以關閉通道以指示不會再傳送更多值
v, ok := <-ch // 接收方可以接收第二個引數來測試通道是否已關閉
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
// 迴圈會重複地從通道接收值,直到通道被關閉。
for i := range c {
fmt.Println(i)
}
}
select 語句允許一個 goroutine 等待多個通訊操作。
一個 select
阻塞,直到其中一個 case
可以執行,然後執行該 case
。如果有多個 case
準備就緒,則隨機選擇一個。
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x: // 向 c 傳送
x, y = y, x+y
case <-quit: // 從 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) // 從 c 取資料
}
quit <- 0 // 停止阻塞 quit
}()
fibonacci(c, quit)
}
如果沒有其他 case
準備就緒,那麼執行 default
。
select {
case i := <-c:
// use i
default:
// receiving from c would block
}
互斥鎖
package main
import (
"fmt"
"sync"
"time"
)
// SafeCounter 可以安全地併發使用。
type SafeCounter struct {
mu sync.Mutex
v map[string]int
}
// Inc 對給定 key 的計數器加 1
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()
// 獲取鎖,以便一次只有一個 goroutine 可以訪問 map c.v
c.v[key]++
c.mu.Unlock()
}
// Value 返回給定 key 的計數器的當前值
func (c *SafeCounter) Value(key string) int {
c.mu.Lock()
// 獲取鎖,以便一次只有一個 goroutine 可以訪問 map c.v
defer c.mu.Unlock() // 使用 defer 確保在 return 之後再釋放鎖
return c.v[key]
}
func main() {
c := SafeCounter{v: make(map[string]int)} // 這裡只對 map v 賦值
for i := 0; i < 1000; i++ {
go c.Inc("somekey")
}
time.Sleep(time.Second)
fmt.Println(c.Value("somekey"))
}
同步鎖
可以使用 sync.WaitGroup
來管理並等待所有的協程完成。
var wg sync.WaitGroup // 建立一個新的同步鎖
for _, u := range urls {
wg.Add(1) // 同步鎖訊號量 +1
go Crawl(u, depth-1, fetcher, c, &wg) // 啟動協程
}
subWg.Wait() // 等待所有協程結束
defer 關鍵字
一個 defer
語句會延遲函式的執行,直到包圍它的函式返回。
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
註釋
註釋方式和 C 一樣
匯入包
import "fmt" // 匯入單個包
import ( // 匯入多個包
"fmt"
"math"
)
import f "fmt" // 給匯入的包起一個別名
import _ "fmt" // 匯入包但不使用(僅用於初始化包)
import "myproject/mypackage" // 匯入本地包
import "github.com/user/package" // 匯入遠端包
一些注意事項:
- 匯入的包路徑要使用雙引號括起來。
- 匯入的包名通常與包的目錄名相同,但也可以在包內使用
package
關鍵字指定不同的包名。 - 如果匯入了包但未使用,編譯器會報錯。可以使用空白識別符號
_
來匯入包但不使用其內容,僅用於初始化該包。 - 包的匯入路徑可以是相對路徑(本地包)或絕對路徑(遠端包)。
- 一個 Go 原始檔必須在其
package
宣告之後立即匯入所需的包。