1. 程式人生 > >Go內部培訓——節點解析31-40

Go內部培訓——節點解析31-40

31. Go 函式定義

  • 函式是Go語言的重要內容。
package main
import "fmt"
// 這個函式計算兩個int型輸入資料的和,並返回int型的和
func plus(a int, b int) int {
// Go需要使用return語句顯式地返回值
return a + b
}
func main() {
// 函式的呼叫方式很簡單
// "名稱(引數列表)"
res := plus(1, 2)
fmt.Println("1+2 =", res)
}

32. Go 函式多返回值

  • Go 函式多返回值
package main
import
"fmt" // 這個函式的返回值為兩個int func vals() (int, int) { return 3, 7 } func main() { // 獲取函式的兩個返回值 a, b := vals() fmt.Println(a) fmt.Println(b) // 如果你只對多個返回值裡面的幾個感興趣 // 可以使用下劃線(_)來忽略其他的返回值 _, c := vals() fmt.Println(c) }

33. Go 函式回撥

  • Go支援函式回撥,你可以把函式名稱作為引數傳遞給另外一個函式,然後在別的地方實現這個函式。
package main
import "fmt"
type Callback func(x, y int) int func main() { x, y := 1, 2 fmt.Println(test(x, y, add)) } //提供一個介面,讓外部去實現 func test(x, y int, callback Callback) int { return callback(x, y) } func add(x, y int) int { return x + y }

34. Go 函式命名返回值

  • 函式接受引數。在 Go 中,函式可以返回多個“結果引數”,而不僅僅是一個值。它們可以像變數那樣命名和使用。
  • 如果命名了返回值引數,一個沒有引數的 return 語句,會將當前的值作為返回值返回。注意,如果遇到if等程式碼塊和返回值同名,還需要顯示寫出返回值。
package main
import "fmt"
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
func main() {
fmt.Println(split(17))
}

35. Go 互斥

  • 上面的例子中,我們看過了如何在多個協程之間原子地訪問計數器,對於更復雜的例子,我們可以使用Mutex 來在多個協程之間安全地訪問資料。
package main
import (
"fmt"
"math/rand"
"runtime"
"sync"
"sync/atomic"
"time"
)
func main() {
// 這個例子的狀態就是一個map
var state = make(map[int]int)
// 這個`mutex`將同步對狀態的訪問
var mutex = &sync.Mutex{}
// ops將對狀態的操作進行計數
var ops int64 = 0
// 這裡我們啟動100個協程來不斷地讀取這個狀態
for r := 0; r < 100; r++ {
go func() {
total := 0
for {
// 對於每次讀取,我們選取一個key來訪問,
// mutex的`Lock`函式用來保證對狀態的
// 唯一性訪問,訪問結束後,使用`Unlock`
// 來解鎖,然後增加ops計數器
key := rand.Intn(5)
mutex.Lock()
total += state[key]
mutex.Unlock()
atomic.AddInt64(&ops, 1)
// 為了保證這個協程不會讓排程器出於飢餓狀態,
// 我們顯式地使用`runtime.Gosched`釋放了資源
// 控制權,這種控制權會在通道操作結束或者
// time.Sleep結束後自動釋放。但是這裡我們需要
// 手動地釋放資源控制權
runtime.Gosched()
}
}()
}
// 同樣我們使用10個協程來模擬寫狀態
for w := 0; w < 10; w++ {
go func() {
for {
key := rand.Intn(5)
val := rand.Intn(100)
mutex.Lock()
state[key] = val
mutex.Unlock()
atomic.AddInt64(&ops, 1)
runtime.Gosched()
}
}()
}
// 主協程Sleep,讓那10個協程能夠執行一段時間
time.Sleep(time.Second)
// 輸出總操作次數
opsFinal := atomic.LoadInt64(&ops)
fmt.Println("ops:", opsFinal)
// 最後鎖定並輸出狀態
mutex.Lock()
fmt.Println("state:", state)
mutex.Unlock()
}

36. Go 環境變數

  • 環境變數是一種很普遍的將配置資訊傳遞給Unix程式的機制。
package main
import "os"
import "strings"
import "fmt"
func main() {
// 為了設定一個key/value對,使用`os.Setenv`
// 為了獲取一個key的value,使用`os.Getenv`
// 如果所提供的key在環境變數中沒有對應的value,
// 那麼返回空字串
os.Setenv("FOO", "1")
fmt.Println("FOO:", os.Getenv("FOO"))
fmt.Println("BAR:", os.Getenv("BAR"))
// 使用`os.Environ`來列出環境變數中所有的key/value對
// 你可以使用`strings.Split`方法來將key和value分開
// 這裡我們列印所有的key
fmt.Println()
for _, e := range os.Environ() {
pair := strings.Split(e, "=")
fmt.Println(pair[0])
}
}

37. Go 集合功能

  • 我們經常需要程式去處理一些集合資料,比如選出所有符合條件的資料或者使用一個自定義函式將一個集合元素拷貝到另外一個集合。
  • 在一些語言裡面,通常是使用泛化資料結構或者演算法。但是Go不支援泛化型別,在Go裡面如果你的程式或者資料型別需要操作集合,那麼通常是為集合提供一些操作函式。
  • 這裡演示了一些操作strings切片的集合函式,你可以使用這些例子來構建你自己的函式。注意在有些情況下,使用內聯集合操作程式碼會更清晰,而不是去建立新的幫助函式。
package main
import "strings"
import "fmt"
// 返回t在vs中第一次出現的索引,如果沒有找到t,返回-1
func Index(vs []string, t string) int {
for i, v := range vs {
for i, v := range vs {
if v == t {
return i
}
}
return -1
}
// 如果t存在於vs中,那麼返回true,否則false
func Include(vs []string, t string) bool {
return Index(vs, t) >= 0
}
// 如果使用vs中的任何一個字串作為函式f的引數可以讓f返回true,
// 那麼返回true,否則false
func Any(vs []string, f func(string) bool) bool {
for _, v := range vs {
if f(v) {
return true
}
}
return false
}
// 如果分別使用vs中所有的字串作為f的引數都能讓f返回true,
// 那麼返回true,否則返回false
func All(vs []string, f func(string) bool) bool {
for _, v := range vs {
if !f(v) {
return false
}
}
return true
}
// 返回一個新的字串切片,切片的元素為vs中所有能夠讓函式f
// 返回true的元素
func Filter(vs []string, f func(string) bool) []string {
vsf := make([]string, 0)
for _, v := range vs {
if f(v) {
vsf = append(vsf, v)
}
}
return vsf
}
// 返回一個bool型別切片,切片的元素為vs中所有字串作為f函式
// 引數所返回的結果
func Map(vs []string, f func(string) string) []string {
vsm := make([]string, len(vs))
for i, v := range vs {
vsm[i] = f(v)
}
return vsm
}
}
func main() {
// 來,試試我們的字串切片操作函式
var strs = []string{"peach", "apple", "pear", "plum"}
fmt.Println(Index(strs, "pear"))
fmt.Println(Include(strs, "grape"))
fmt.Println(Any(strs, func(v string) bool {
return strings.HasPrefix(v, "p")
}))
fmt.Println(All(strs, func(v string) bool {
return strings.HasPrefix(v, "p")
}))
fmt.Println(Filter(strs, func(v string) bool {
return strings.Contains(v, "e")
}))
// 上面的例子都使用匿名函式,你也可以使用命名函式
fmt.Println(Map(strs, strings.ToUpper))
}

38. Go 介面

  • 介面是一個方法簽名的集合。
  • 所謂方法簽名,就是指方法的宣告,而不包括實現。
package main
import "fmt"
import "math"
// 這裡定義了一個最基本的表示幾何形狀的方法的介面
type geometry interface {
area() float64
perim() float64
}
// 這裡我們要讓正方形square和圓形circle實現這個介面
type square struct {
width, height float64
}
type circle struct {
radius float64
}
// 在Go中實現一個介面,只要實現該介面定義的所有方法即可
// 下面是正方形實現的介面
func (s square) area() float64 {
return s.width * s.height
}
func (s square) perim() float64 {
return 2*s.width + 2*s.height
}
// 圓形實現的介面
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}
// 如果一個函式的引數是介面型別,那麼我們可以使用命名介面
// 來呼叫這個函式
// 比如這裡的正方形square和圓形circle都實現了介面geometry,
// 那麼它們都可以作為這個引數為geometry型別的函式的引數。
// 在measure函式內部,Go知道呼叫哪個結構體實現的介面方法。
func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.perim())
}
func main() {
s := square{width: 3, height: 4}
c := circle{radius: 5}
// 這裡circle和square都實現了geometry介面,所以
// circle型別變數和square型別變數都可以作為measure
 
// 函式的引數
measure(s)
measure(c)
}

39. Go 結構體

  • Go語言結構體資料類是將各個型別的變數定義的集合,通常用來表示記錄。
package main
import "fmt"
// 這個person結構體有name和age成員
type person struct {
name string
age int
}
func main() {
// 這個語法建立一個新結構體變數
fmt.Println(person{"Bob", 20})
// 可以使用"成員:值"的方式來初始化結構體變數
fmt.Println(person{name: "Alice", age: 30})
// 未顯式賦值的成員初始值為零值
fmt.Println(person{name: "Fred"})
// 可以使用&來獲取結構體變數的地址
fmt.Println(&person{name: "Ann", age: 40})
// 使用點號(.)來訪問結構體成員
s := person{name: "Sean", age: 50}
fmt.Println(s.name)
// 結構體指標也可以使用點號(.)來訪問結構體成員
// Go語言會自動識別出來
sp := &s
fmt.Println(sp.age)
// 結構體成員變數的值是可以改變的
sp.age = 51
fmt.Println(sp.age)
}
 

40. Go 程序觸發

  • 有的時候,我們需要從Go程式裡面觸發一個其他的非Go程序來執行。
package main
import "fmt"
import "io/ioutil"
import "os/exec"
func main() {
// 我們從一個簡單的命令開始,這個命令不需要任何引數
// 或者輸入,僅僅向stdout輸出一些資訊。`exec.Command`
// 函式建立了一個代表外部程序的物件
dateCmd := exec.Command("date")
// `Output`是另一個執行命令時用來處理資訊的函式,這個
// 函式等待命令結束,然後收集命令輸出。如果沒有錯誤發
// 生的話,`dateOut`將儲存date的資訊
dateOut, err := dateCmd.Output()
if err != nil {
panic(err)
}
fmt.Println("> date")
fmt.Println(string(dateOut))
// 下面我們看一個需要從stdin輸入資料的命令,我們將
// 資料輸入傳給外部程序的stdin,然後從它輸出到stdout
// 的執行結果收集資訊
grepCmd := exec.Command("grep", "hello")
// 這裡我們顯式地獲取input/output管道,啟動程序,
// 向程序寫入資料,然後讀取輸出結果,最後等待程序結束
grepIn, _ := grepCmd.StdinPipe()
grepOut, _ := grepCmd.StdoutPipe()
grepCmd.Start()
grepIn.Write([]byte("hello grep\ngoodbye grep"))
grepIn.Close()
grepBytes, _ := ioutil.ReadAll(grepOut)
grepCmd.Wait()
// 在上面的例子中,我們忽略了錯誤檢測,但是你一樣可以
// 使用`if err!=nil`模式來進行處理。另外我們僅僅收集了
// `StdoutPipe`的結果,同時你也可以用一樣的方法來收集
// `StderrPipe`的結果
fmt.Println("> grep hello")
fmt.Println(string(grepBytes))
// 注意,我們在觸發外部命令的時候,需要顯式地提供
// 命令和引數資訊。另外如果你想用一個命令列字串
// 觸發一個完整的命令,你可以使用bash的-c選項
lsCmd := exec.Command("bash", "-c", "ls -a -l -h")
lsOut, err := lsCmd.Output()
if err != nil {
panic(err)
}
fmt.Println("> ls -a -l -h")
fmt.Println(string(lsOut))
}