Go

Go语言基础(二)

2021-06-29  本文已影响0人  王勇1024

目录

关键字

defer

go的defer用法范例

理解 Go defer

关键字defer的用法类似于面向对象编程语言 Java 和 C# 的finally语句块,它一般用于释放某些已分配的资源。

defer在声明时不会立刻去执行,而是在函数 return 后去执行的。

它的主要应用场景有异常处理、记录日志、清理数据、释放数据库连接、文件打开句柄等释放资源等等。

defer 的重要用途一:清理释放资源

f, _ := os.Open(filename)
defer f.Close()

defer 的重要用途二:执行 recover

被 defer 的函数在 return 之后执行,这个时机点正好可以捕获函数抛出的 panic,因而 defer 的另一个重要用途就是执行 recover。

recover 只有在 defer 中使用才更有意义,如果在其他地方使用,由于 program 已经调用结束而提前返回而无法有效捕捉错误。

package main

import "fmt"

func main() {
    defer func() {
        if ok := recover(); ok != nil {
            fmt.Println("recover")
        }
    }()

    panic("error")
}
// output:
recover

多个 defer 的执行顺序

defer 的作用就是把关键字之后的函数执行压入一个栈中延迟执行,多个 defer 的执行顺序是后进先出 LIFO

defer func() { fmt.Println("1") }()
defer func() { fmt.Println("2") }()
defer func() { fmt.Println("3") }()

输出顺序是 321。

这个特性可以对一个 array 实现逆序操作。

被 deferred 函数的参数在 defer 时确定

这是 defer 的特点,一个函数被 defer 时,它的参数在 defer 时进行计算确定,即使 defer 之后参数发生修改,对已经 defer 的函数没有影响,什么意思?看例子:

func a() {
    i := 0
    defer fmt.Println(i)
    i++
    return
}
// output:0

a 执行输出的是 0 而不是 1,因为 defer 时,i 的值是 0,此时被 defer 的函数参数已经进行执行计算并确定了。

被 defer 的函数可以读取和修改带名称的返回值

func c() (i int) {
    defer func() { i++ }()
    return 1
}

被 defer 的函数是在 return 之后执行,可以修改带名称的返回值,上面的函数 c 返回的是 2。

defer与闭包

defer 调用的函数,参数的值在 defer 定义时就确定了,看下代码

defer fmt.Println(a + b),在这时,参数的值已经确定了。

而 defer 函数内部所使用的变量的值需要在这个函数运行时才确定,看下代码

defer func() { fmt.Println(a + b) }(),a 和 b 的值在函数运行时,才能确定。

func main() {
    var a = 1
    var b = 2
    defer func() {
        fmt.Println(a + b)
    }()
    a = 2
}
// output: 4

func main() {
    var a = 1
    var b = 2
    defer func(a int, b int) {
        fmt.Println(a + b)
    }(a, b)
    a = 2
}
// output: 3

当os.Exit()方法退出程序时,defer不会被执行

func main() {
    defer fmt.Println("1")
    fmt.Println("main")
    os.Exit(0)
}
// output: main

结论:当os.Exit()方法退出程序时,defer不会被执行。

defer只对当前协程有效

结构类型

声明一个结构体

type user struct {
  name string
  email string
  ext int
  privileged bool
}

使用结构体声明变量

// 声明 user 类型的变量
var bill user

 // 声明 user 类型的变量,并初始化所有字段
lisa := user{
  name: "Lisa",
  email: "lisa@email.com",
  ext: 123,
  privileged: true,
}

// 方法二:不使用字段名,创建结构类型的值
// 这种形式下,值的顺序很重要,必须要和结构声明中字段的顺序一致。
13 lisa := user{"Lisa", "lisa@email.com", 123, true}

结构体是值类型

type Person struct {
    name string
    age int 
    sex string
}
func main(){
    var p1 = Person {
        name:"哈哈",
        age:20,
        sex:"男",
    }
    p2 := p1
    fmt.Println(p1 == p2) // true
    p2.Name = "李四"
    fmt.Printf("%v %v", p2, p1) //{李四 20 男} {哈哈 20 男}
}

嵌套结构体

Go 语言允许用户扩展或者修改已有类型的行为。这个功能对代码复用很重要,在修改已有类型以符合新类型的时候也很重要。这个功能是通过嵌入类型(type embedding)完成的。嵌入类型是将已有的类型直接声明在新的结构类型里。被嵌入的类型被称为新的外部类型的内部类型。

声明嵌套结构体

// user 在程序里定义一个用户类型
type user struct {
  name string
  email string
}

type admin struct {
  person user  // 嵌入类型
  level string
}

嵌套结构体初始化

fred := admin{
  person: user{
    name: "Lisa",
    email: "lisa@email.com",
  },
  level: "super",
}

嵌入类型方法调用

 // notify 实现了一个可以通过 user 类型值的指针
// 调用的方法
func (u *user) notify() {
  fmt.Printf("Sending user email to %s<%s>\n", u.name, u.email)
}

func main() {
    // 创建一个 admin 用户
    fred := admin{
      person: user{
        name: "Lisa",
        email: "lisa@email.com",
      },
      level: "super",
    }
    // 我们可以直接访问内部类型的方法
    fred.user.notify()
    // 内部类型的方法也被提升到外部类型,如果外部类型也实现了该方法,这种调用方式将调用外部类型的方法实现
    fred.notify()
}

方法

方法定义

方法能给用户定义的类型添加新的行为。方法实际上也是函数,只是在声明时,在关键字 func 和方法名之间增加了一个参数

// user 在程序里定义一个用户类型
type user struct {
  name string
  email string
}

// notify 使用值接收者实现了一个方法
func (u user) notify() {
  fmt.Printf("Sending User Email To %s<%s>\n",
  u.name,
  u.email)
}

// changeEmail 使用指针接收者实现了一个方法
func (u *user) changeEmail(email string) {
  u.email = email
}

值接收者指针接收者

关键字 func 和函数名之间的参数被称作接收者,将函数与接收者的类型绑在一起。如果一个函数有接收者,这个函数就被称为方法。

Go 语言里有两种类型的接收者:值接收者指针接收者

如果使用值接收者声明方法,调用时会使 用这个值的一个副本来执行,如果在方法中对值做了修改,不会影响到原值。

当调用使用指针接收者声明的 方法时,这个方法会共享调用方法时接收者所指向的值,如果在方法中对值做了修改,会影响到原值。

Go 语言既允许使用值,也允许使用指针来调用方法,不必严格符合接收者的类型:

func main() {
    // user 类型的值可以用来调用
    // 使用值接收者声明的方法
    bill := user{"Bill", "bill@email.com"}
    bill.notify()

    // 指向 user 类型值的指针也可以用来调用
    // 使用值接收者声明的方法
    lisa := &user{"Lisa", "lisa@email.com"}
    lisa.notify()

    // user 类型的值可以用来调用
    // 使用指针接收者声明的方法
    bill.changeEmail("bill@newdomain.com")
    bill.notify()

    // 指向 user 类型值的指针可以用来调用
    // 使用指针接收者声明的方法
    lisa.changeEmail("lisa@newdomain.com")
    lisa.notify()
}

注意:如果方法接受者为值类型,可以直接传入指针类型。原因就是在 Go 语言中所有的都是值传递, 尽管传入的是 Man 的指针, 但是通过该指针我们可以找到其对应的值, Go 语言隐式帮我们做了类型转换.我们记住在 Go 语言中指针类型可以获得其关联的任意值类型, 但反过来却不行。

方法接收者
(t T) T and *T
(t *T) *T

接口

面向对象编程(OOP)中三个基本特征分别是封装,继承,多态。在 Go 语言中封装和继承是通过 struct 来实现的,而多态则是通过接口(interface)来实现的。

什么是接口

在 Go 语言中接口包含两种含义:它既是方法的集合, 同时还是一种类型. 在Go 语言中是隐式实现的,意思就是对于一个具体的类型,不需要声明它实现了哪些接口,只需要提供接口所必需的方法。

在 Go 语言的类型系统中有一个核心概念: 我们不应该根据类型可以容纳哪种数据而是应该根据类型可以执行哪种操作来设计抽象类型.

定义并实现接口

//声明一个接口
type Human interface{
  Say()
}
//定义两个类,这两个类分别实现了 Human 接口的 Say 方法
type women struct {
}

type man struct {
}
func (w *women) Say() {
    fmt.Println("I'm a women")
}
func(m *man) Say() {
    fmt.Println("I'm a man")
}
func main() {
    w := new(women)
    w.Say()
    m := new(man)
    m.Say()
}
//output:
//I'm a women
//I'm a man

如果一个具体类型实现了某个接口的所有方法,我们则成为该具体类型实现了该接口。注意:必须是所有方法

空接口

type EmptyInterface interface{}

对于空接口来说, 任何具体类型都实现了空接口。

类型断言

类型断言是作用在接口值上的操作(类似于Java的 instanceof), 类型断言的写法如下:

<目标类型>, <布尔参数> := <表达式>.(目标类型) //这种是安全的类型断言, 不会引发 panic.
<目标类型> := <表达式>.(目标类型) //这种是非安全的类型断言, 如果断言失败会引发 panic.

我们看一个例子:

package main

import "fmt"

type Shape interface {
    Area() float64
}

type Object interface {
    Volume() float64
}

type Skin interface {
    Color() float64
}

type Cube struct {
    side float64
}

func (c Cube)Area() float64 {
    return c.side * c.side
}

func (c Cube)Volume() float64 {
    return c.side * c.side * c.side
}

func main() {
    var s Shape = Cube{3.0}
    value1, ok1 := s.(Object)
    fmt.Printf("dynamic value of Shape 's' with value %v implements interface Object? %v\n", value1, ok1)
    value2, ok2 := s.(Skin)
    fmt.Printf("dynamic value of Shape 's' with value %v implements interface Skin? %v\n", value2, ok2)
}
// output:
// dynamic value of Shape 's' with value {3} implements interface Object? true
// dynamic value of Shape 's' with value <nil> implements interface Skin? false

因为在程序运行中,有时会无法确定接口值的动态类型,因此通过类型断言可以来检测其是否是一个特定的类型,这样便可以针对性的进行业务处理。

多态

// 这个示例程序使用接口展示多态行为
package main

import (
    "fmt"
)

// notifier 是一个定义了
// 通知类行为的接口
type notifier interface {
    notify()
}

// user 在程序里定义一个用户类型
type user struct {
    name  string
    email string
}

// notify 使用指针接收者实现了 notifier 接口
func (u *user) notify() {
    fmt.Printf("Sending user email to %s<%s>\n",
        u.name,
        u.email)
}

// admin 定义了程序里的管理员
type admin struct {
    name  string
    email string
}

// notify 使用指针接收者实现了 notifier 接口
func (a *admin) notify() {
    fmt.Printf("Sending admin email to %s<%s>\n",
        a.name,
        a.email)
}

// main 是应用程序的入口
func main() {
    // 创建一个 user 值并传给 sendNotification
    bill := user{"Bill", "bill@email.com"}
    sendNotification(&bill)

    // 创建一个 admin 值并传给 sendNotification
    lisa := admin{"Lisa", "lisa@email.com"}
    sendNotification(&lisa)
}

// sendNotification 接受一个实现了 notifier 接口的值
// 并发送通知
func sendNotification(n notifier) {
    n.notify()
}
// output:
// Sending user email to Bill<bill@email.com>
// Sending admin email to Lisa<lisa@email.com>
上一篇下一篇

猜你喜欢

热点阅读