Go语言快速简单入门(二)

一、流程控制
if

if err := Connect(); err != nil {
    fmt.Println(err)
    return
}

判断表达式不再需要括号括起来

for

for 初始语句;条件表达式;结束语句{
    循环体代码
}

初始语句和结束语句可省略
几种特殊写法:

for i <= 10 {
    i++
}
for {
   //do something
}
for ;;i++ {
    //do something
}

for range

  • 遍历数组、切片——获得索引和元素
for key, value := range []int{1, 2, 3, 4} {
    fmt.Printf("key:%d  value:%d\n", key, value)
}
  • 遍历字符串——获得字符
var str = "hello 你好"
for key, value := range str {
    fmt.Printf("key:%d value:0x%x\n", key, value)
}
  • 遍历map——获得map的键和值
m := map[string]int{
    "hello": 100,
    "world": 200,
}

for key, value := range m {
    fmt.Println(key, value)
}
  • 遍历通道(channel)——接收通道数据
c := make(chan int)

go func() {

    c <- 1
    c <- 2
    c <- 3
    close(c)
}()

for v := range c {
    fmt.Println(v)
}

代码说明如下:
第 1 行创建了一个整型类型的通道。
第 3 行启动了一个 goroutine,其逻辑的实现体现在第 5~8 行,实现功能是往通道中推送数据 1、2、3,然后结束并关闭通道。
这段 goroutine 在声明结束后,在第 9 行马上被并行执行。
从第 11 行开始,使用 for range 对通道 c 进行遍历,其实就是不断地从通道中取数据,直到通道被关闭。

  • 在遍历中选择希望获得的变量
m := map[string]int{
    "hello": 100,
    "world": 200,
}

for _, value := range m {
    fmt.Println(value)
}

switch/case
与c的区别是
1.switch 后面不再一定要一个整型变量,字符型的或者不写都可以;
2.case后面也不再要求是个整型常量,字符型的或者是一个判断语句都可以
3.不再需要break退出,自动默认推出
4.fallthrough兼容c语言里的continue;但是不建议使用

var a = "mum"
switch a {
case "mum", "daddy":
    fmt.Println("family")
}

var r int = 11
switch {
case r > 10 && r < 20:
    fmt.Println(r)
}

goto
goto跳到指定标签位置

    err := firstCheckError()
    if err != nil {
        goto onExit
    }

    err = secondCheckError()

    if err != nil {
        goto onExit
    }

    fmt.Println("done")

    return

onExit:
    fmt.Println(err)
    exitProcess()

break/continue
1.break 语句可以结束 for、switch 和 select 的代码块;
2.break 语句还可以在语句后面添加标签,表示退出某个标签对应的代码块,标签要求必须定义在对应的 for、switch 和 select 的代码块上
3.continue也有类似2的操作,可以继续循环指定标签的loop

package main

import "fmt"

func main() {

OuterLoop:
    for i := 0; i < 2; i++ {
        for j := 0; j < 5; j++ {
            switch j {
            case 2:
                fmt.Println(i, j)
                break OuterLoop
            case 3:
                fmt.Println(i, j)
                break OuterLoop
            }
        }
    }
}

二、函数

函数定义

func 函数名(参数列表)(返回参数列表){
    函数体
}
  • 简写
    类型相同只需要写一个变量名
func add(a, b int) int {
    return a + b
}
  • 返回值
    函数返回值类型如果定义了变量名,函数体里可以不定义直接用,return时也不需要写完整的return语句
func typedTwoValues() (int, int) {

    return 1, 2
}

a, b := typedTwoValues()
fmt.Println(a, b)
func namedRetValues() (a, b int) {

    a = 1
    b = 2

    return
}
func namedRetValues() (a, b int) {
    a = 1

    return a, 2
}
  • 调用
result := add(1,1)

把函数作为值进行保存

package main

import (
    "fmt"
)

func fire() {
    fmt.Println("fire")
}

func main() {

    var f func()

    f = fire

    f() //输出fire
}

匿名函数

func(参数列表)(返回参数列表){
    函数体
}
func(data int) {
    fmt.Println("hello", data)
}(100) //定义后直接执行
// 将匿名函数体保存到f()中
f := func(data int) {
    fmt.Println("hello", data)
}

// 使用f()调用
f(100)
  • 匿名函数用作回调函数
package main

import (
    "fmt"
)

// 遍历切片的每个元素, 通过给定函数进行元素访问
func visit(list []int, f func(int)) {

    for _, v := range list {
        f(v)
    }
}

func main() {

    // 使用匿名函数打印切片内容
    visit([]int{1, 2, 3, 4}, func(v int) {
        fmt.Println(v)
    })
}
  • 使用匿名函数实现封装操作
package main

import (
    "flag"
    "fmt"
)

var skillParam = flag.String("skill", "", "skill to perform")

func main() {

    flag.Parse()

    var skill = map[string]func(){
        "fire": func() {
            fmt.Println("chicken fire")
        },
        "run": func() {
            fmt.Println("soldier run")
        },
        "fly": func() {
            fmt.Println("angel fly")
        },
    }

    if f, ok := skill[*skillParam]; ok {
        f()
    } else {
        fmt.Println("skill not found")
    }

}

可变参数

func 函数名(固定参数列表, v … T)(返回参数列表){
    函数体
}

以下是对可变参数的函数的说明:

  • 可变参数一般被放置在函数列表的末尾,前面是固定参数列表,当没有固定参数时,所有变量就将是可变参数。
  • v 为可变参数变量,类型为 []T,也就是拥有多个 T 元素的 T 类型切片,v 和 T 之间由…即3个点组成。
  • T 为可变参数的类型,当 T 为 interface{} 时,传入的可以是任意类型。

可变参数传递:

package main

import "fmt"

// 实际打印的函数
func rawPrint(rawList ...interface{}) {

    // 遍历可变参数切片
    for _, a := range rawList {

        // 打印参数
        fmt.Println(a)
    }
}

// 打印函数封装
func print(slist ...interface{}) {

    // 将slist可变参数切片完整传递给下一个函数
    rawPrint(slist...)
}

func main() {

    print(1, 2, 3)
}

延迟执行语句defer
Go 语言的 defer 修饰的语句,会在函数执行结束后,根据defer的顺序弹栈执行

package main
import (
    "fmt"
)
func main() {
    fmt.Println("defer begin")
    // 将defer放入延迟调用栈
    defer fmt.Println(1)
    defer fmt.Println(2)
    // 最后一个放入, 位于栈顶, 最先调用
    defer fmt.Println(3)
    fmt.Println("defer end")
}

执行结果:
defer begin
defer end
3
2
1

  • 用defer实现并发控制
    传统实现方法
var (
    // 一个演示用的映射
    valueByKey      = make(map[string]int)
    // 保证使用映射时的并发安全的互斥锁
    valueByKeyGuard sync.Mutex
)
// 根据键读取值
func readValue(key string) int {
    // 对共享资源加锁
    valueByKeyGuard.Lock()
    // 取值
    v := valueByKey[key]
    // 对共享资源解锁
    valueByKeyGuard.Unlock()
    // 返回值
    return v
}

用defer实现

func readValue(key string) int {
    valueByKeyGuard.Lock()
   
    // defer后面的语句不会马上调用, 而是延迟到函数结束时调用
    defer valueByKeyGuard.Unlock()
    return valueByKey[key]
}

一般defer修饰的语句用来存放一些释放资源的操作,例如打开关闭文件之类的操作

运行时错误处理

  • net包中的错误处理机制
func Dial(network, address string) (Conn, error) {
    var d Dialer
    return d.Dial(network, address)
}

在 io 包中的 Writer 接口也拥有错误返回,代码如下:

type Writer interface {
    Write(p []byte) (n int, err error)
}

io 包中还有 Closer 接口,只有一个错误返回,代码如下:
纯文本复制

type Closer interface {
    Close() error
}

  • 自定义错误

错误接口

type error interface {
    Error() string
}

错误类型

var err = errors.New("this is an error")

示例:

package main
import (
    "fmt"
)
// 声明一个解析错误
type ParseError struct {
    Filename string // 文件名
    Line     int    // 行号
}
// 实现error接口,返回错误描述
func (e *ParseError) Error() string {
    return fmt.Sprintf("%s:%d", e.Filename, e.Line)
}
// 创建一些解析错误
func newParseError(filename string, line int) error {
    return &ParseError{filename, line}
}
func main() {
    var e error
    // 创建一个错误实例,包含文件名和行号
    e = newParseError("main.go", 1)
    // 通过error接口查看错误描述
    fmt.Println(e.Error())
    // 根据错误接口具体的类型,获取详细错误信息
    switch detail := e.(type) {
    case *ParseError: // 这是一个解析错误
        fmt.Printf("Filename: %s Line: %d\n", detail.Filename, detail.Line)
    default: // 其他类型的错误
        fmt.Println("other error")
    }
}

宕机处理 (panic)

package main
func main() {
    panic("crash")
}

宕机恢复(recover)
Go 没有异常系统,其使用 panic 触发宕机类似于其他语言的抛出异常,那么 recover 的宕机恢复机制就对应 try/catch 机制。

package main

import (
    "fmt"
    "runtime"
)

// 崩溃时需要传递的上下文信息
type panicContext struct {
    function string // 所在函数
}

// 保护方式允许一个函数
func ProtectRun(entry func()) {

    // 延迟处理的函数
    defer func() {

        // 发生宕机时,获取panic传递的上下文并打印
        err := recover()

        switch err.(type) {
        case runtime.Error: // 运行时错误
            fmt.Println("runtime error:", err)
        default: // 非运行时错误
            fmt.Println("error:", err)
        }

    }()

    entry()

}

func main() {
    fmt.Println("运行前")

    // 允许一段手动触发的错误
    ProtectRun(func() {

        fmt.Println("手动宕机前")

        // 使用panic传递上下文
        panic(&panicContext{
            "手动触发panic",
        })

        fmt.Println("手动宕机后")
    })

    // 故意造成空指针访问错误
    ProtectRun(func() {

        fmt.Println("赋值宕机前")

        var a *int
        *a = 1

        fmt.Println("赋值宕机后")
    })

    fmt.Println("运行后")
}
  • panic和recover的关系
    1.有 panic 没 recover,程序宕机。
    2.有 panic 也有 recover 捕获,程序不会宕机。执行完对应的 defer 后,从宕机点退出当前函数后继续执行。

猜你喜欢

转载自blog.csdn.net/LiuYangQ_Q/article/details/90730204