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))
}

猜你喜欢

转载自blog.csdn.net/boss2967/article/details/85121033