19.go语言基础学习(上)——2019年12月16日

Posted oneapple

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了19.go语言基础学习(上)——2019年12月16日相关的知识,希望对你有一定的参考价值。

2019年12月16日16:57:04

5.接口

2019年11月01日15:56:09

5.1 duck typing

1.

技术图片

2.

技术图片

接口

3.介绍

Go 语言的接口设计是非侵入式的,接口编写者无须知道接口被哪些类型实现。

而接口实现者只需知道实现的是什么样子的接口,但无须指明实现哪一个接口。

编译器知道最终编译时使用哪个类型实现哪个接口,或者接口应该由谁来实现。

每个接口类型由数个方法组成。接口的形式代码如下:

type 接口类型名 interface{
    方法名1( 参数列表1 ) 返回值列表1
    方法名2( 参数列表2 ) 返回值列表2
    …
}

对各个部分的说明:

  • 接口类型名:使用 type 将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加 er,如有写操作的接口叫 Writer,有字符串功能的接口叫 Stringer,有关闭功能的接口叫 Closer 等。
  • 方法名:当方法名首字母是大写时,且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
  • 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以被忽略,例如:
type writer interface{
    Write([]byte) error
}

3.接口由使用者定义

技术图片

4.

技术图片

接口的值类型

5.type assertion 类型断言

判断r的真实类型,是不是mock.Retriever类型实现的。

r.(*mock.Retriever)

r.(type)

// Type assertion
    if mockRetriever, ok := r.(*mock.Retriever); ok {
        fmt.Println(mockRetriever.Contents)
    } else {
        fmt.Println("r is not a mock retriever")
    }

    fmt.Println(
        "Try a session with mockRetriever")
    fmt.Println(session(&mockRetriever))
}

技术图片

6.interface里一般有类型和值

fmt.println("%T %v ",r,r)

func inspect(r Retriever) {
  
    fmt.Println("Inspecting", r)
    fmt.Printf(" > Type:%T Value:%v
", r, r)
    fmt.Print(" > Type switch: ")
  
    switch v := r.(type) {
    case *mock.Retriever:
        fmt.Println("Contents:", v.Contents)
    case *real.Retriever:
        fmt.Println("UserAgent:", v.UserAgent)
    }
    fmt.Println()
}

7.接口变量里有什么

技术图片

8.接口变量自带指针

9.接口变量同样采用值传递,几乎不需要使用接口的指针

10.指针接受者实现只能以指针方式使用;值接受者都可以

var r Retriever

    mockRetriever := mock.Retriever{
        Contents: "this is a fake imooc.com"}
    r = &mockRetriever
    inspect(r)

    r = &real.Retriever{
        UserAgent: "Mozilla/5.0",
        TimeOut:   time.Minute,
    }

11.查看接口类型

interface{}支持任何类型

type Queue []interface{}

func (q *Queue) Push(v interface{}) {
    *q=append(*q,v)

}

Type Assertion

Type Switch

2019年11月03日

5.2 接口的组合

2019年11月03日13:33:08

1.新定义一个接口——为了组合

type Poster interface {
    Post(url string,
        form map[string]string) string
}

func post(poster Poster) {
    poster.Post(url,
        map[string]string{
            "name":   "ccmouse",
            "course": "golang",
        })
}

2.定义一个组合接口

type RetrieverPoster interface {
    Retriever
    Poster
}

3.mockRetriever接口实现了Post方法

//mockretriever.go
func (r *Retriever) Post(url string,
    form map[string]string) string {
    r.Contents = form["contents"]
    return "ok"
}


//main.go
func session(s RetrieverPoster) string {
    s.Post(url, map[string]string{
        "contents": "another faked imooc.com",
    })
    return s.Get(url)
}


func main() {
    var r Retriever

    mockRetriever := mock.Retriever{
        Contents: "this is a fake imooc.com"}
    r = &mockRetriever
    inspect(r)

    r = &real.Retriever{
        UserAgent: "Mozilla/5.0",
        TimeOut:   time.Minute,
    }
    inspect(r)

    fmt.Println(
        "Try a session with mockRetriever")
    fmt.Println(session(&mockRetriever))
}

6.函数式编程

2019年11月03日14:53:11

1.

技术图片


#### 2.闭包

<img src="https://tva1.sinaimg.cn/large/006y8mN6ly1g8kwses02tj313u0u0h1k.jpg" alt="image-20191103150946598" style="zoom:25%;" />

#### 3.匿名函数

Go语言支持匿名函数,即在需要使用函数时再定义函数,匿名函数没有函数名只有函数体,函数可以作为一种类型被赋值给函数类型的变量,匿名函数也往往以变量方式传递,这与C语言的回调函数比较类似,不同的是,Go语言支持随时在代码里定义匿名函数。

**定义一个匿名函数**

匿名函数的定义格式如下:

func(参数列表)(返回参数列表){
函数体
}
```

匿名函数的定义就是没有名字的普通函数定义。

在定义时调用匿名函数

匿名函数可以在声明后调用,例如:

func(data int) {    
    fmt.Println(
    "hello", data)
}(100)

注意第3行} (100)

将匿名函数赋值给变量

匿名函数可以被赋值,例如:

// 将匿名函数体保存到f()中
f := func(data int) {
    fmt.Println("hello", data)
}

// 使用f()调用
f(100)

匿名函数的用途非常广泛,它本身就是一种值,可以方便地保存在各种容器中实现回调函数和操作封装。

4.闭包的记忆效应

被捕获到闭包中的变量让闭包本身拥有了记忆效应,闭包中的逻辑可以修改闭包捕获的变量,变量会跟随闭包生命期一直存在,闭包本身就如同变量一样拥有了记忆效应。

累加器的实现:

package main
import (
    "fmt"
)
// 提供一个值, 每次调用函数会指定对值进行累加
func Accumulate(value int) func() int {
    // 返回一个闭包
    return func() int {
        // 累加
        value++
        // 返回一个累加值
        return value
    }
}
func main() {
    // 创建一个累加器, 初始值为1
    accumulator := Accumulate(1)
    // 累加1并打印
    fmt.Println(accumulator())
    fmt.Println(accumulator())
    // 打印累加器的函数地址
    fmt.Printf("%p
", accumulator)
    // 创建一个累加器, 初始值为1
    accumulator2 := Accumulate(10)
    // 累加1并打印
    fmt.Println(accumulator2())
    // 打印累加器的函数地址
    fmt.Printf("%p
", accumulator2)
}

代码说明如下:

  • 第 8 行,累加器生成函数,这个函数输出一个初始值,调用时返回一个为初始值创建的闭包函数。
  • 第 11 行,返回一个闭包函数,每次返回会创建一个新的函数实例。
  • 第 14 行,对引用的 Accumulate 参数变量进行累加,注意 value 不是第 11 行匿名函数定义的,但是被这个匿名函数引用,所以形成闭包。
  • 第 17 行,将修改后的值通过闭包的返回值返回。
  • 第 24 行,创建一个累加器,初始值为 1,返回的 accumulator 是类型为 func()int 的函数变量。
  • 第 27 行,调用 accumulator() 时,代码从 11 行开始执行匿名函数逻辑,直到第 17 行返回。
  • 第 32 行,打印累加器的函数地址。

对比输出的日志发现 accumulator 与 accumulator2 输出的函数地址不同,因此它们是两个不同的闭包实例。

7.错误处理和资源管理

7.1 defer调用

2019年11月03日16:00:50

1.defer调用来实现资源管理

2.确保调用在函数结束时发生

3.参数在defer语句时计算

4.defer列表为后进先出

5.何时使用defer调用

Open/Close

Lock/unlock

PrintHeader/PrintFooter

6.源码

package main

import (
    "fmt"
    "os"
    "u2pppw/src/functional/fib"

    "bufio"


)

func tryDefer() {
    for i := 0; i < 100; i++ {
        defer fmt.Println(i)
        if i == 30 {
            // Uncomment panic to see
            // how it works with defer
            // panic("printed too many")
        }
    }
}


func writeFile(filename string) {
    file, err := os.OpenFile(filename,
        os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0666)

    if err != nil {
        if pathError, ok := err.(*os.PathError); !ok {
            panic(err)
        } else {
            fmt.Printf("%s, %s, %s
",
                pathError.Op,
                pathError.Path,
                pathError.Err)
        }
        return
    }
    defer file.Close()

    writer := bufio.NewWriter(file)
    defer writer.Flush()

    f := fib.Fibonacci()
    for i := 0; i < 20; i++ {
        fmt.Fprintln(writer, f())
    }
}

func main() {
    tryDefer()
    writeFile("fib.txt")
}

7.2 错误处理概念

2019年11月03日16:57:12

1.为了防止程序挂掉——错误处理

    if err != nil {
        if pathError, ok := err.(*os.PathError); !ok {
            panic(err)
        } else {
            fmt.Printf("%s, %s, %s
",
                pathError.Op,
                pathError.Path,
                pathError.Err)
        }
        return
    }

服务器统一出错处理

1.如何实现统一的错误处理逻辑

2.起一个服务端读取内容

读取方法

const prefix = "/list/"

func HandleFileList(writer http.ResponseWriter,
    request *http.Request) error {
    fmt.Println()
    if strings.Index(
        request.URL.Path, prefix) != 0 {
        return userError(
            fmt.Sprintf("path %s must start "+
                "with %s",
                request.URL.Path, prefix))
    }
    path := request.URL.Path[len(prefix):]
    file, err := os.Open(path)
    if err != nil {
        return err
    }
    defer file.Close()

    all, err := ioutil.ReadAll(file)
    if err != nil {
        return err
    }

    writer.Write(all)
    return nil
}

处理错误的包装方法——使用函数式编程

type appHandler func(writer http.ResponseWriter,
    request *http.Request) error

func errWrapper(
    handler appHandler) func(
    http.ResponseWriter, *http.Request) {
    return func(writer http.ResponseWriter,
        request *http.Request) {
        // panic
        defer func() {
            if r := recover(); r != nil {
                log.Printf("Panic: %v", r)
                http.Error(writer,
                    http.StatusText(http.StatusInternalServerError),
                    http.StatusInternalServerError)
            }
        }()

        err := handler(writer, request)

        if err != nil {
            log.Printf("Error occurred "+
                "handling request: %s",
                err.Error())

            // user error
            if userErr, ok := err.(userError); ok {
                http.Error(writer,
                    userErr.Message(),
                    http.StatusBadRequest)
                return
            }

            // system error
            code := http.StatusOK
            switch {
            case os.IsNotExist(err):
                code = http.StatusNotFound
            case os.IsPermission(err):
                code = http.StatusForbidden
            default:
                code = http.StatusInternalServerError
            }
            http.Error(writer,
                http.StatusText(code), code)
        }
    }
}

main方法

func main() {
    http.HandleFunc("/",
        errWrapper(filelisting.HandleFileList))

    err := http.ListenAndServe(":8888", nil)
    if err != nil {
        panic(err)
    }
}

3.源码

main.go

package main

import (
    "log"
    "net/http"
    _ "net/http/pprof"
    "os"
    "u2pppw/src/errhandling/filelistingserver/filelisting"
)

type appHandler func(writer http.ResponseWriter,
    request *http.Request) error

func errWrapper(
    handler appHandler) func(
    http.ResponseWriter, *http.Request) {
    return func(writer http.ResponseWriter,
        request *http.Request) {
        // panic
        defer func() {
            if r := recover(); r != nil {
                log.Printf("Panic: %v", r)
                http.Error(writer,
                    http.StatusText(http.StatusInternalServerError),
                    http.StatusInternalServerError)
            }
        }()

        err := handler(writer, request)

        if err != nil {
            log.Printf("Error occurred "+
                "handling request: %s",
                err.Error())

            // user error
            if userErr, ok := err.(userError); ok {
                http.Error(writer,
                    userErr.Message(),
                    http.StatusBadRequest)
                return
            }

            // system error
            code := http.StatusOK
            switch {
            case os.IsNotExist(err):
                code = http.StatusNotFound
            case os.IsPermission(err):
                code = http.StatusForbidden
            default:
                code = http.StatusInternalServerError
            }
            http.Error(writer,
                http.StatusText(code), code)
        }
    }
}

type userError interface {
    error
    Message() string
}

func main() {
    http.HandleFunc("/",
        errWrapper(filelisting.HandleFileList))

    err := http.ListenAndServe(":8888", nil)
    if err != nil {
        panic(err)
    }
}

handle.go

package filelisting

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "os"
    "strings"
)

const prefix = "/list/"

type userError string

func (e userError) Error() string {
    return e.Message()
}

func (e userError) Message() string {
    return string(e)
}

func HandleFileList(writer http.ResponseWriter,
    request *http.Request) error {
    fmt.Println()
    if strings.Index(
        request.URL.Path, prefix) != 0 {
        return userError(
            fmt.Sprintf("path %s must start "+
                "with %s",
                request.URL.Path, prefix))
    }
    path := request.URL.Path[len(prefix):]
    file, err := os.Open(path)
    if err != nil {
        return err
    }
    defer file.Close()

    all, err := ioutil.ReadAll(file)
    if err != nil {
        return err
    }

    writer.Write(all)
    return nil
}

7.3panic和recover

2019年11月04日14:00:22

1. panic

1.停止当前函数执行

2.一直向上返回,执行每一层的defer

3.如果没有遇见recover,程序退出

2. recover

1.仅在defer调用中使用

defer func() {
        err:=recover()
    }()

2.获取panic的值

如果无法处理,就可以重新panic

package main

import (
    "fmt"
)

func tryRecover() {
    defer func() {
        r := recover()
        if r == nil {
            fmt.Println("Nothing to recover. " +
                "Please try uncomment errors " +
                "below.")
            return
        }
        if err, ok := r.(error); ok {
            fmt.Println("Error occurred:", err)
        } else {
            panic(fmt.Sprintf(
                "I don't know what to do: %v", r))
        }
    }()

    // Uncomment each block to see different panic
    // scenarios.
    // Normal error
    //panic(errors.New("this is an error"))

    // Division by zero
    //b := 0
    //a := 5 / b
    //fmt.Println(a)

    // Causes re-panic
    panic(123)
}

func main() {
    tryRecover()
}

技术图片

8.goroutine

2019年11月04日14:49:18

1.并发编程

package main

import (
    "fmt"
    "time"
)

func main() {
    for i := 0; i < 1000; i++ {
        go func(i int) {
            for {
                fmt.Printf("Hello from "+
                    "goroutine %d
", i)
            }
        }(i)
    }
    time.Sleep(time.Minute)
}

2.协程 Coroutine

轻量级线程

非抢占式多任务处理,由协程主动交出控制权

多个协程可能在一个或多个线程上运行

func main() {

    var a [10]int
    for i:=0;i<10;i++{
        go func(i int){
            for{
                //fmt.Println("hello %d
",i)
                a[i]++
                runtime.Gosched()
            }
        }(i)
    }

    time.Sleep(time.Millisecond)
    fmt.Println(a)
    
}

3.任何函数只需加上go就能送给调度器运行

4.不需要在定义时区分时都是异步函数

5.调度器在合适的点进行切换

6.使用-race来检测数据访问冲突

7.goroutine可能的切换点

I/O,select

channel

等待锁

函数调用

runtime,Gosched()

技术图片

9.channel

2019年11月04日15:30:28

1.介绍

Go语言中的通道(channel)是一种特殊的类型。在任何时候,同时只能有一个 goroutine 访问通道进行发送和获取数据。goroutine 间通过通道就可以通信。

通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。

2.声明通道类型

通道本身需要一个类型进行修饰,就像切片类型需要标识元素类型。通道的元素类型就是在其内部传输的数据类型,声明如下:

var 通道变量 chan 通道类型
  • 通道类型:通道内的数据类型。
  • 通道变量:保存通道的变量。

chan 类型的空值是 nil,声明后需要配合 make 后才能使用。

3.创建通道

通道是引用类型,需要使用 make 进行创建,格式如下:

通道实例 := make(chan 数据类型)
  • 数据类型:通道内传输的元素类型。
  • 通道实例:通过make创建的通道句柄。

请看下面的例子:

ch1 := make(chan int)                 // 创建一个整型类型的通道
ch2 := make(chan interface{})         // 创建一个空接口类型的通道, 可以存放任意格式
type Equip struct{ /* 一些字段 */ }
ch2 := make(chan *Equip)             // 创建Equip指针类型的通道, 可以存放*Equip

4.发送数据

// 创建一个空接口通道
ch := make(chan interface{})
// 将0放入通道中
ch <- 0
// 将hello字符串放入通道中
ch <- "hello"

10.beego介绍及搭建

2019年12月06日13:28:50

1.介绍

  • Beego是一个能够快速开发Go应用程序的HTTP框架, 它可以用来迅速的开发API, 网络APP(网站)和后端服务
  • Beego是一个MVC的框架

beego有八个模块,分别是

  1. cache
  2. config:
  3. context:
  4. httplibs: curl函数
  5. logs:
  6. orm
  7. session
  8. toolbox

2.优势

beego是一个类似tornado的Go应用框架,采用了RESTFul的方式来实现应用框架,是一个超轻量级的框架,主要有如下的特点:

  • 支持MVC的方式,用户只需要关注逻辑,实现对应method的方法即可
  • 支持websocket,通过自定义Handler实现集成sockjs等方式实现
  • 支持自定义路由,支持各种方式的路由,正则、语意均支持,类似sinatra
  • session集成,支持memory、file、redis、mysql等存储
  • 表单处理自动化解析,用户可以很方便的获取数据
  • 日志分级系统,用户可以很方便的调试和应用日志记录
  • 自定义配置文件,支持ini格式的文本配置,可以方便的在系统中调参数
  • 采用了Go内置的模板,集成实现了很多Web开发中常用的函数

3.环境搭建

beego 的安装是典型的 Go 安装包的形式:

go get github.com/astaxie/beego

Go 升级,通过该方式用户可以升级 beego 框架,强烈推荐该方式:

go get -u github.com/astaxie/beego

源码下载升级,用户访问 https://github.com/astaxie/beego ,下载源码,然后覆盖到 $GOPATH/src/github.com/astaxie/beego 目录,然后通过本地执行安装就可以升级了:

go install  github.com/astaxie/beego

bee 工具简介

bee 工具是一个为了协助快速开发 beego 项目而创建的项目,通过 bee 您可以很容易的进行 beego 项目的创建、热编译、开发、测试、和部署。

bee 工具的安装

您可以通过如下的方式安装 bee 工具:

go get github.com/beego/bee

安装完之后,bee 可执行文件默认存放在 $GOPATH/bin 里面,所以您需要把 $GOPATH/bin 添加到您的环境变量中,才可以进行下一步。

new 命令

new 命令是新建一个 Web 项目,我们在命令行下执行 bee new <项目名> 就可以创建一个新的项目。但是注意该命令必须在 $GOPATH/src 下执行。最后会在 $GOPATH/src 相应目录下生成如下目录结构的项目:

bee new myproject
[INFO] Creating application...
/gopath/src/myproject/
/gopath/src/myproject/conf/
/gopath/src/myproject/controllers/
/gopath/src/myproject/models/
/gopath/src/myproject/static/
/gopath/src/myproject/static/js/
/gopath/src/myproject/static/css/
/gopath/src/myproject/static/img/
/gopath/src/myproject/views/
/gopath/src/myproject/conf/app.conf
/gopath/src/myproject/controllers/default.go
/gopath/src/myproject/views/index.tpl
/gopath/src/myproject/main.go
13-11-25 09:50:39 [SUCC] New application successfully created!
myproject
├── conf
│   └── app.conf
├── controllers
│   └── default.go
├── main.go
├── models
├── routers
│   └── router.go
├── static
│   ├── css
│   ├── img
│   └── js
├── tests
│   └── default_test.go
└── views
    └── index.tpl

8 directories, 4 files

api 命令

上面的 new 命令是用来新建 Web 项目,不过很多用户使用 beego 来开发 API 应用。所以这个 api 命令就是用来创建 API 应用的,执行命令之后如下所示:

bee api apiproject
create app folder: /gopath/src/apiproject
create conf: /gopath/src/apiproject/conf
create controllers: /gopath/src/apiproject/controllers
create models: /gopath/src/apiproject/models
create tests: /gopath/src/apiproject/tests
create conf app.conf: /gopath/src/apiproject/conf/app.conf
create controllers default.go: /gopath/src/apiproject/controllers/default.go
create tests default.go: /gopath/src/apiproject/tests/default_test.go
create models object.go: /gopath/src/apiproject/models/object.go
create main.go: /gopath/src/apiproject/main.go

这个项目的目录结构如下:

apiproject
├── conf
│   └── app.conf
├── controllers
│   └── object.go
│   └── user.go
├── docs
│   └── doc.go
├── main.go
├── models
│   └── object.go
│   └── user.go
├── routers
│   └── router.go
└── tests
    └── default_test.go

从上面的目录我们可以看到和 Web 项目相比,少了 static 和 views 目录,多了一个 test 模块,用来做单元测试的。

同时,该命令还支持一些自定义参数自动连接数据库创建相关 model 和 controller:
bee api [appname] [-tables=""] [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
如果 conn 参数为空则创建一个示例项目,否则将基于链接信息链接数据库创建项目。

run 命令

我们在开发 Go 项目的时候最大的问题是经常需要自己手动去编译再运行,bee run 命令是监控 beego 的项目,通过 fsnotify监控文件系统。但是注意该命令必须在 $GOPATH/src/appname 下执行。
这样我们在开发过程中就可以实时的看到项目修改之后的效果:

bee run
13-11-25 09:53:04 [INFO] Uses 'myproject' as 'appname'
13-11-25 09:53:04 [INFO] Initializing watcher...
13-11-25 09:53:04 [TRAC] Directory(/gopath/src/myproject/controllers)
13-11-25 09:53:04 [TRAC] Directory(/gopath/src/myproject/models)
13-11-25 09:53:04 [TRAC] Directory(/gopath/src/myproject)
13-11-25 09:53:04 [INFO] Start building...
13-11-25 09:53:16 [SUCC] Build was successful
13-11-25 09:53:16 [INFO] Restarting myproject ...
13-11-25 09:53:16 [INFO] ./myproject is running...

我们打开浏览器就可以看到效果 http://localhost:8080/

以上是关于19.go语言基础学习(上)——2019年12月16日的主要内容,如果未能解决你的问题,请参考以下文章

2019年12月5日 学习内容总结

吃透GBDT(2019-05-26)

2019-03-02

实验报告(2019年6月12日)

2019年12月9日:周一自主学习成果

世界编程语言排行榜的2009年排行