持续之前这两篇文章,咱们讲到了接口。

  • 后端言语很难?前端入门go根底语法只需求3小时!(上)

  • 后端言语很难?前端入门go根底语法只需求3小时!(中)

接口

Go 中,接口是一种类型,它界说了一组办法。接口类型变量能够存储任何完结了该接口的类型的值,这样就能够在程序中运用统一的接口类型来处理不同的详细类型。

例如:

type Reader interface {
    Read(p []byte) (n int, err error)
}

上面的代码界说了一个名为 Reader 的接口,该接口有一个办法 Read(p []byte) (n int, err error)。任何类型假如完结了Reader接口中的Read()办法,都能够被赋值给Reader类型变量.

接口还能够包括多个办法, 例如

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

这个Writer接口包括了两个办法,Write和Close。任何类型假如完结了这两个办法,就能够被赋值给Writer类型变量。

在 Go 中,接口是隐式完结的,因而完结接口不需求显式声明。只要类型界说了接口中的一切办法,就能够以为它完结了该接口。

接口还能够嵌套, 例如

type ReadWriter interface {
    Reader
    Writer
}

这个ReadWriter接口包括了Reader和Writer两个接口中一切的办法,任何类型完结了这两个接口中一切的办法就能够被赋值给ReadWriter类型变量。

比照js:这个跟ts也差不多,但语法不太相同,咱们的interface要承继或许type经过 && 来聚合,go还有点像ts中type这种组合的办法

接口的多态

Go 中,接口还能够用来完结多态。比如咱们有一个函数,它承受一个接口类型的参数,那么这个函数就能够承受任何完结了这个接口的类型的参数。这样就能够在编译时就发现类型问题,而不是在运转时。

例如:

type Shape interface {
    Area() float64
}
type Rectangle struct {
    width, height float64
}
func (r Rectangle) Area() float64 {
    return r.width * r.height
}
type Circle struct {
    radius float64
}
func (c Circle) Area() float64 {
    return math.Pi * c.radius * c.radius
}
func getArea(s Shape) float64 {
    return s.Area()
}
func main() {
    r := Rectangle{width: 10, height: 5}
    c := Circle{radius: 5}
    fmt.Println("Area of rectangle: ", getArea(r))
    fmt.Println("Area of circle: ", getArea(c))
}

上面的代码界说了一个接口 Shape,它有一个办法 Area()。Rectangle 和 Circle 两种类型都完结了这个办法,所以它们都完结了 Shape 接口。

这是go中完结面向目标承继的内容,是很重要的,由于面向接口编程,是函数跟业务解耦详细业务是十分重要的,函数自身解耦了数据和行为,有了接口就把数据限制了,把数据变为一种协议,只要完结这个协议的数据就能够,从而完结面向接口完结函数

接口类型断语和类型判别

别的,Go 中的接口还支撑类型断语和类型判别,能够在运转时判别一个变量是否完结了某个接口。

类型断语是指将接口类型断语成详细类型。这样就能够拜访详细类型的字段和办法。类型断语的语法如下:

x.(T)

x 是一个接口类型,T 是详细类型。假如 x 断语成功,则回来 x 的详细值,不然回来一个类型断语失利的 panic。

类型判别是指判别接口类型是否完结了某个接口。这样就能够在运转时判别一个变量是否完结了某个接口。类型判别的语法如下:

x.(type)

x 是一个接口类型。假如 x 完结了 T 接口,回来x的详细类型,不然回来nil

在 Go 中,类型断语和类型判别能够用来在运转时判别一个变量是否完结了某个接口,并拜访详细的字段和办法。这样能够进步代码的灵敏性。

咱们举比如阐明一下:

类型断语:

package main
import "fmt"
type animal interface {
    speak()
}
type dog struct {
}
func (d dog) speak() {
    fmt.Println("Woof!")
}
func main() {
    var d animal = dog{}
    if val, ok := d.(dog); ok {
        val.speak()
    } else {
        fmt.Println("d is not a dog")
    }
}

上面的代码中,咱们界说了一个 animal 接口,并完结了一个 dog 类型。在 main 函数中,咱们声明晰一个 animal 类型的变量 d,并将一个 dog 类型的变量赋值给它。然后咱们运用类型断语来判别 d 是否是 dog 类型。假如断语成功,就能够调用 dog 类型的 speak 办法。

留意:这儿咱们需求留意的是,断语是会回来内容的,这个跟typescript是彻底不相同的

类型判别:

package main
import "fmt"
type animal interface {
    speak()
}
type dog struct {
}
func (d dog) speak() {
    fmt.Println("Woof!")
}
func main() {
    var d animal = dog{}
    switch v := d.(type) {
        case dog:
            v.speak()
        default:
            fmt.Println("d is not a dog")
    }
}

咱们界说了一个 animal 接口,并完结了一个 dog 类型。在 main 函数中,咱们声明晰一个 animal 类型的变量 d,并将一个 dog 类型的变量赋值给它。然后咱们运用类型判别来判别 d 是否完结了 dog 类型。假如判别成功,就能够调用 dog 类型的 speak 办法。

总结:类型断语和类型判别都能够用来在运转时判别一个变量是否完结了某个接口,并拜访详细的字段和办法。可是类型断语是拜访详细类型的值,类型判别是拜访详细类型。

比照js,咱们的ts如同没有直接把类型变为字符串的才能,毕竟js不是内置类型的,打通js和类型。

Goroutines和Channels

并发程序指一起进行多个使命的程序,随着硬件的发展,并发程序变得越来越重要。Web服务器会一次处理不计其数的恳求。平板电脑和手机app在渲染用户画面一起还会后台履行各种计算使命和网络恳求。即使是传统的批处理问题–读取数据,计算,写输出–现在也会用并发来隐藏掉I/O的操作延迟以充分运用现代计算机设备的多个核心。计算机的功用每年都在以非线性的速度增加。

go的Goroutines能够类比以下协程,可是它们是有显着差异。

在这儿很多前端同学对操作体系中,进程、线程、协程并不了解,咱们有必要介绍一下:

必定留意,这些是大概念,详细到完结的言语里,是有差异的 。

进程(Process),线程(Thread),[协程]

  • 进程:

一个进程是计算机中的一个独立的程序关于某数据调集的一次运转活动,是体系进行资源分配和调度的基本单位。每个进程都有自己独立的内存空间和体系资源,互不搅扰。

进程一般由程序、数据集、进程操控块三部分组成。

  • 程序: 指进程所要履行的指令调集,包括可履行程序的机器言语代码和数据。
  • 数据集: 指进程所需求的数据,包括大局变量和局部变量。
  • 进程操控块: 是体系为每个进程保护的数据结构,记载了进程的当时状况,进程的基本信息,如进程ID,优先级,状况,进程的资源信息等。进程操控块是体系保护进程信息的重要数据结构,用于调度和办理进程,如记载进程。

最终,进程的限制是创立、吊销和切换的开支比较大。

  • 线程:

线程是进程的一个实体,是被体系独立调度和分配的基本单位,线程自己基本上不具有体系资源,只具有一点在运转中必不可少的资源(如程序计数器,一组寄存器和栈),可是它可与同属一个进程的其他的线程同享进程所具有的全部资源 线程线程是在进程之后发展出来的概念。

线程的长处是减小了程序并发履行时的开支,进步了操作体系的并发功用,缺陷是线程没有自己的体系资源。

  • 协程:

协程: 协程是一种用户态的轻量级线程, 它是程序员可操控的(用户态履行),能够自行暂停和康复履行,不由体系调度。

子程序调用总是一个进口,一次回来,一旦退出即完结了子程序的履行。

然后咱们站在协程的视点看看它有什么优缺陷:

  • 长处:
  1. 协程是轻量级的,它没有线程那么大的体系开支,所以它比线程更容易创立和办理。
  2. 协程是可控的,程序员能够自行操控协程的暂停和康复,这样能够更灵敏的完结并发。
  3. 协程能够在单一线程中完结多使命的调度,这样能够减少线程上下文切换的开支。
  • 缺陷:
  1. 协程需求额定的机制来防止数据抵触,这可能会增加程序的复杂性。
  2. 协程不能运用多核处理器的优势,由于它们运转在单一线程中。

go里边的协程是同享数据的。可是,Go言语供给了一些机制来防止在多个协程之间同享变量时的数据抵触。

Go言语供给了一种叫做channel的机制,允许协程之间进行通讯。经过运用channel,能够在多个协程之间传递数据而不会发生数据抵触。

Go言语还供给了一种叫做互斥锁(mutex)的机制,用于在多个协程之间同步拜访同享变量。运用互斥锁能够确保在某一时刻只要一个协程能够拜访同享变量。

Goroutines

在Go言语中,运用关键字go来发动一个协程,如:

go foo()

这样就会在独自的协程中发动函数foo()。

协程之间能够运用通道(channel)来进行通讯。通道是Go言语中的一种数据结构,能够用来在不同协程之间传递数据。

咱们举一个详细的比如:

一个实用的 Go 协程事例是网络爬虫。网络爬虫程序一般需求一起拜访多个网站,并在获取数据后进行处理。运用协程能够在拜访一个网站时一起拜访其他网站,进步爬取效率。例如:

package main
import (
    "fmt"
    "net/http"
)
func main() {
    urls := []string{
        "http://www.example.com",
        "http://www.example.net",
        "http://www.example.org",
    }
    for _, url := range urls {
        go fetch(url)
    }
    fmt.Scanln()
}
func fetch(url string) {
    resp, err := http.Get(url)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer resp.Body.Close()
    fmt.Println(url, resp.Status)
}

在这个比如中,咱们运用了 Go 内置的 net/http 包来拜访网站,并在循环中运用 go 关键字来并发地履行 fetch 函数。这样,程序能够一起拜访多个网站,而不会阻塞在一个网站上。

Channels

Channels 是 Go 言语中的一种通讯机制,用于在 goroutines 之间进行同步和通讯。经过 Channels,一个 goroutine 能够将数据发送到另一个 goroutine,并等候其接收。

Channels 相似于其他言语中的管道或行列,可是 Channels 在 Go 中是一种内置类型,并供给了丰厚的操作办法。

下面是一个网络爬虫程序的比如,它运用了 Channels 来完结并发爬取,并在爬取完结后将数据发送到另一个 goroutine 进行处理:

package main
import (
    "fmt"
    "net/http"
)
func main() {
    urls := []string{
        "http://www.example.com",
        "http://www.example.net",
        "http://www.example.org",
    }
    // 创立一个用于爬取的 channel
    fetchChannel := make(chan string)
    // 创立一个用于处理数据的 channel
    processChannel := make(chan string)
    // 发动多个 goroutine 进行爬取
    for _, url := range urls {
        go fetch(url, fetchChannel)
    }
    // 发动一个 goroutine 来处理数据
    go process(processChannel)
    // 从 fetch channel 中读取数据,并发送到 process channel
    for i := 0; i < len(urls); i++ {
        fetchResult := <-fetchChannel
        processChannel <- fetchResult
    }
    close(processChannel)
    fmt.Scanln()
}
func fetch(url string, fetchChannel chan string) {
    resp, err := http.Get(url)
    if err != nil {
        fetchChannel <- err.Error()
        return
    }
    defer resp.Body.Close()
    fetchChannel <- url + " " + resp.Status
}
func process(processChannel chan string) {
    for data := range processChannel {
        fmt.Println(data)
    }
}

在上面的比如中,咱们运用了两个 channel

Go 言语中的包(package)是一种模块化编程的办法,用于将相关的类型、变量、函数和常量组织在一起。

一切的 Go 程序都必须在一个包中,main 包是一个特殊的包,它是程序的进口。

包中的类型、变量、函数和常量能够经过 import 关键字导入到其他包中运用。

例如:

在一个名为 math 的包中界说了一个名为 Add 的函数,它承受两个整型参数并回来它们的和。

package math
func Add(a, b int) int {
    return a + b
}

在另一个名为 main 的包中,咱们能够导入 math 包并运用它的 Add 函数

package main
import "math"
func main() {
    result := math.Add(1, 2)
    fmt.Println(result)
}

履行这个程序将会输出 3。

Go 还支撑匿名导入,能够运用 _ 关键字导入一个包,但不运用它的任何类型、变量、函数和常量。这能够用于导入一个包中的 init 函数。

例如:

import _ "math/rand"

这样会导入 math/rand 包,但不会运用任何类型、变量、函数和常量。

导入途径

每个包是由一个大局仅有的字符串所标识的导入途径定位。出现在import句子中的导入途径也是字符串。

import (
    "fmt"
    "math/rand"
    "encoding/json"
    "golang.org/x/net/html"
    "github.com/go-sql-driver/mysql"
)

假如你计划同享或发布包,那么导入途径最好是全球仅有的。为了防止抵触,一切非标准库包的导入途径主张以地点组织的互联网域名为前缀;而且这样也有利于包的检索。例如,上面的import句子导入了Go团队保护的HTML解析器和一个流行的第三方保护的MySQL驱动。

包声明

在每个Go语音源文件的最初都必须有包声明句子。包声明句子的首要目的是确认当时包被其它包导入时默许的标识符(也称为包名)。

例如,math/rand包的每个源文件的最初都包括package rand包声明句子,所以当你导入这个包,你就能够用rand.Int、rand.Float64相似的办法拜访包的成员。

package main
import (
    "fmt"
    "math/rand"
)
func main() {
    fmt.Println(rand.Int())
}

一般来说,默许的包名就是包导入途径名的最终一段,因而即使两个包的导入途径不同,它们依然可能有一个相同的包名。例如,math/rand包和crypto/rand包的包名都是rand。稍后咱们将看到怎么一起导入两个有相同包名的包。

关于默许包名一般选用导入途径名的最终一段的约定也有三种例外情况。

  • 第一种,包对应一个可履行程序,也就是main包,这时候main包自身的导入途径是无关紧要的。姓名为main的包是给 go build 构建指令一个信息,这个包编译完之后必须调用连接器生成一个可履行程序。

  • 第二种,包地点的目录中可能有一些文件名是以*test.go为后缀的Go源文件。而且这些源文件声明的包名也是以_test为后缀名的。这种目录能够包括两种包:

    • 一种一般包,
    • 一种则是测验的外部扩展包。

    一切以_test为后缀包名的测验外部扩展包都由go test指令独立编译,一般包和测验的外部扩展包是相互独立的。后边会介绍test的内容

  • 第三种,一些依靠版本号的办理工具会在导入途径后追加版本号信息,例如”gopkg.in/yaml.v2″。这种情况下包的姓名并不包括版本号后缀,而是yaml。”

测验

Maurice Wilkes,第一个存储程序计算机EDSAC的设计者,1949年他在实验室爬楼梯时有一个彻悟。在《计算机前驱回想录》(Memoirs of a Computer Pioneer)里,他回想到:“忽然间有一种醍醐灌顶的感觉,我整个后半生的美好时光都将在寻觅程序BUG中度过了”。必定从那之后的大部分正常的码农都会怜惜Wilkes过份失望的想法,尽管或许不是没有人困惑于他对软件开发的难度的单纯看法。

现在的程序现已远比Wilkes年代的更大也更复杂,也有许多技能能够让软件的复杂性可得到操控。其间有两种技能在实践中证明是比较有用的。第一种是代码在被正式布置前需求进行代码评审。第二种则是测验,也就是本章的讨论主题。

测验函数

每个测验函数必须导入testing包。测验函数有如下的签名:

func TestName(t *testing.T) {
// ...
}

测验函数的姓名必须以Test最初,可选的后缀名必须以大写字母最初:

func TestSin(t *testing.T) { /* ... */ }
func TestCos(t *testing.T) { /* ... */ }
func TestLog(t *testing.T) { /* ... */ }

其间t参数用于陈述测验失利和附加的日志信息。

咱们来举一个比如:

gopl.io/ch11/word1

// Package word provides utilities for word games.
package word
// IsPalindrome reports whether s reads the same forward and backward.
// (Our first attempt.)
func IsPalindrome(s string) bool {
    for i := range s {
        if s[i] != s[len(s)-1-i] {
            return false
        }
    }
    return true
}

在相同的目录下,word_test.go测验文件中包括了TestPalindrome和TestNonPalindrome两个测验函数。每一个都是测验IsPalindrome是否给出正确的成果,并运用t.Error陈述失利信息:

package word
import "testing"
func TestPalindrome(t *testing.T) {
    if !IsPalindrome("detartrated") {
        t.Error(`IsPalindrome("detartrated") = false`)
    }
    if !IsPalindrome("kayak") {
        t.Error(`IsPalindrome("kayak") = false`)
    }
}
func TestNonPalindrome(t *testing.T) {
    if IsPalindrome("palindrome") {
        t.Error(`IsPalindrome("palindrome") = true`)
    }
}

go test指令假如没有参数指定包那么将默许选用当时目录对应的包(和go build指令相同)。咱们能够用下面的指令构建和运转测验。

$ cd $GOPATH/src/gopl.io/ch11/word1
$ go test
ok gopl.io/ch11/word1 0.008s
  • 测验用例称号一般命名为Test加上待测验的办法名。
  • 测验用的参数有且只要一个,在这儿是t *testing.T
  • 基准测验(benchmark)的参数是*testing.B,TestMain 的参数是*testing.M类型。

go test -v-v参数会显示每个用例的测验成果,别的-cover参数能够检查覆盖率

例如下面的:

$ go test -v
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
=== RUN   TestMul
--- PASS: TestMul (0.00s)
PASS
ok      example 0.007s

假如只想运转其间的一个用例,例如TestAdd,能够用-run参数指定,该参数支撑通配符*,和部分正则表达式,例如、$

$ go test -run TestAdd -v
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
PASS
ok      example 0.007s

子测验(Subtests)

子测验是 Go 言语内置支撑的,能够在某个测验用例中,依据测验场景运用t.Run创立不同的子测验用例:

// calc_test.go
func TestMul(t *testing.T) {
	t.Run("pos", func(t *testing.T) {
		if Mul(2, 3) != 6 {
			t.Fatal("fail")
		}
	})
	t.Run("neg", func(t *testing.T) {
		if Mul(2, -3) != -6 {
			t.Fatal("fail")
		}
	})
}
  • 之前的比如测验失利时运用t.Error/t.Errorf,这个比如中运用t.Fatal/t.Fatalf,差异在于前者遇错不停,还会持续履行其他的测验用例,后者遇错即停。

反射

反射是指在运转时动态获取和操作类型、变量、函数和接口的才能。在 Go 言语中,反射是经过内置的 reflect 包完结的。

常用的api如下:

reflect.ValueOf(x) 会回来一个 reflect.Value 类型的变量,它包括了变量的值和类型信息。经过这个变量,咱们能够获取变量的值,批改变量的值,获取变量的类型和类别等。

reflect.TypeOf(x) 会回来一个 reflect.Type 类型的变量,它包括了变量的类型信息。经过这个变量,咱们能够获取变量的类型称号,获取字段和办法等。

举个比如:

package main
import (
    "fmt"
    "reflect"
)
func main() {
    var x float64 = 3.4
    v := reflect.ValueOf(x)
    fmt.Println("type:", v.Type())
    fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
    fmt.Println("value:", v.Float())
}

这儿咱们运用反射获取了一个 float64 类型的变量的类型、类别和值。 输出:

type: float64
kind is float64: true
value: 3.4

咱们能够在运转时动态获取变量的类型,类别和值,并进行各种操作。

在 Go 中,反射还能够用来获取结构体的字段、办法和标签。

举个比如:

package main
import (
    "fmt"
    "reflect"
)
type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}
func (p Person) SayHello() {
    fmt.Println("Hello, my name is", p.Name)
}
func main() {
    p := Person{Name: "John", Age: 30}
    t := reflect.TypeOf(p)
    // 获取字段
    fmt.Println("fields:")
    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)
        fmt.Printf("%d: %s %s json=%s\n", i, f.Name, f.Type, f.Tag.Get("json"))
    }
    // 获取办法
    fmt.Println("\nmethods:")
    for i := 0; i < t.NumMethod(); i++ {
        m := t.Method(i)
        fmt.Printf("%d: %s\n", i, m.Name)
    }
}

输出:

fields:
0: Name string json=name
1: Age int json=age
methods:
0: SayHello

咱们经过反射获取了结构体 Person 的字段称号、类型和标签,以及办法称号。

最根底的部分现已完结

接下来,是看了一个视频(<Go言语从入门到实战>),总结了一些语法上的常见坑点。

类型转化

与ts的差异:

1、Go言语不允许隐式类型转化 2、别号和原有类型也不能进行隐式类型转化

package main
func main(){
    var a intt = 1
    var b int64
    b = a // 报错
}

怎么批改,显示类型转化即可

 b = int64(a)

咱们再看下别号

package main
type MyInt int64
func main(){
    var b int64
    var c MyInt
    c = b // 报错 批改的话:c = MyInt(b)
}

指针类型

  • 不支撑指针运算
  • 与ts的差异: string是值类型,默许的初始化值是空字符串,不是undefined
package main
type MyInt int64
func main(){
    a := 1
    aPoint := &a
    aPoint := aPoint + 1
    var b string // 被初始化为一个空值
}

算数运算符

  • go没有前置++

while循环

与js的不同,没有while循环,但能够用for循环来完结

n := 0
for n < 5 {
    n++
    fmt.Println(n)
}

if句子

怎么说呢,if的go风格是下面这样的,跟js不太像,跟node其实有点像,node曾经是回调函数都有过错优先,go也是这样的,只不过写起来语法不相同,思想是相同的。

package main
func main(){
   if v, err := someFun(); err == nil {
       xxx
   }  else {
       xxx
   }
}

switch句子也有相似的用法

switch os:= runtime.GOOS; os {
    case "darwin":
        fmt.Println("OS X.")
    case "linux"
        fmt.Println("Linux.")
    default:
        fmt.Printf("%s.", os)
}

switch还有一种用法,跟js彻底不同,下面的逗号相当于匹配任意一个就算true

switch i {
    case 0, 2:
        xx
    case 1, 3:
        xx
    default:
        xx
}

多维数组

例如怎么在go中声明2维数组

c := [2][2]int{{1, 2}, {3, 4}}

go中判别map中某个key是否元素存在

写法如下,跟js大不相同

m1 := map[int]int{}
m1[2] = 0
if v,ok := m1[3]; ok {
} else {
}

go中的slice在什么情况下会同享数据

在 Go 中,slice 是对数组的一个封装。当一个新的 slice 是由一个现已存在的 slice 创立时,两个 slice 会同享同一个底层数组。

这可能会发生在以下两种情况:

  1. 经过切片语法创立新的 slice:
original := []int{1, 2, 3, 4, 5}
// Create a new slice that shares the same underlying array
new := original[1:3]
  1. 经过调用内置函数 make 创立新的 slice,而且指定了第三个参数,而且这个参数不为0,表明容量而不是长度,这样创立的slice 会同享同一个底层数组:
original := make([]int, 5, 10)
// Create a new slice that shares the same underlying array
new := original[:3]

在这些情况下,原始的 slice 和新的 slice 都同享相同的底层数组。当批改其间一个 slice 中的数据时,另一个 slice 中的数据也会被更改。

当然在创立新的slice的时候假如是经过append()来创立的就不会同享数据了,由于append会新开一块新的内存来存储数据。

go里边的相面目标其实没有严格的承继功用

网上很多文章说go支撑承继,其实并不支撑,严格来讲应该算是组合,而不是承继,相似设计模式的桥接模式。

在Go言语中,能够经过组合一个类型的结构体字段来完结相似承继的功用。这种办法更加简练、明晰,一起也防止了承继所带来的复杂性和灵敏性问题。

go中的字符串表现为Unicode字符组成的byte切片

在 Go 言语中,字符串是由一系列Unicode字符组成的,字符串在内存中是以UTF-8编码的形式存储的。实际上,字符串在内存中是一个byte切片,每个字符在内存里是一段接连的区间。

空接口能够表明任何类型

在 Go 中,空接口(interface{})表明能够存储任何类型的值。由于一切类型都满足空接口的束缚(不需求完结任何办法),所以能够将任何类型的值赋值给空接口类型的变量。这使得空接口十分适用于完结通用函数、数据结构等。

留意,能够经过断语来将空接口转化为指定类型

v, ok := p.(int) // ok等于true时,转化成功

go接口的最佳实践

倾向于运用小的接口界说,很多接口只包括一个办法,比如

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

较大的接口界说,能够由多个小接口界说组合而成

type ReadWriter interface {
    Reader
    Writer
}

只依靠于必要功用的最小接口

func StoreData(reader Reader) error { ... }

GO没有过错机制

比如没有try catch句子。咱们能够经过errors.New来快速创立过错实例,运用多回来值的特性,去处理过错

errors.New("n must be int rhe range [0, 1]")

过错处理准则

及早失利,防止嵌套

就是说假如函数报错,就直接return了

过错的recover

常常有同学这样写代码:

defer func() {
    if err := recover(); err != nil {
        log.Error("recover panic", err)
    }
}

由于过错可能是咱们体系中某些资源消耗完了,我这样康复了,其实体系依然是不能作业的,咱们的健康检查也很难去检查出现在的问题,由于常常健康检查只是检测当时的运用是否正常供给服务,成果仍是在的,成果你现已不能供给正常的服务了。

怎么处理呢?

“Let it Crash”,重启大法好啊!

package一致

同一目录里的Go代码的package要保持一致,要不编译不经过

init能够界说多个

首先 init函数时在main函数被履行前,一切依靠的package的init办法都会被履行

而且包的每个源文件也能够有多个init函数,这点比较特殊

例如:

// my_series.go

package series
func init() {
    fmt.Println("init1")
}
func init() {
    fmt.Println("init2")
}
func Square(n int) int {
    return n * n
}
package main
import "ch15/series"
func main() {
     fmt.Println(series.Square(2)) 
}

最终输出,init的函数依次履行了

go get 指令默许拜访https

“go get -u” 是 Go 言语中的指令,用于装置和更新 Go 包。

“go get” 指令用于装置 Go 包,它会从长途代码库下载包的源代码,并装置到本地。

“-u” 参数表明更新,当装置现已存在的包时,会更新这个包到最新版本。

例如,履行 “go get -u github.com/golang/example” 指令,会装置或更新名为 “example” 的包,并将其装置到 $GOPATH/src/github.com/golang 目录下。

需求留意的是,go get指令默许会拜访https的代码库,假如你需求拜访http的代码库或许本地目录,需求运用-d参数。

协程的原理

go的协程开支十分小,初始化的栈只要2k,而java的线程栈巨细是1M。java线程和内核目标的映射是1:1,而go的协程和内核目标的映射是M:N(多对多),显着go要更强。

协程并发处理的机制:

后端语言很难?前端入门go基础语法只需要3小时!(下)

上图中,M代表的是体系线程,P是GO言语自己完结的协程处理器,G是一个协程使命,组成协程行列。

假如一个协程花费的时间特别长

机制1:协程的看护进程会记载协程处理器完结的协程数量,假如一段时间内,处理器完结的数量没有改变,就会往使命栈中插入一个标记,当时协程遇到非内联函数时,在使命栈中读到该标记,就会把该协程使命移到行列的末尾;

机制2:假如履行协程过程中遇到一个非CPU密集型使命,例如IO,需求中止等候时,协程处理器P会把自己移到别的一个可运用的体系线程中,持续履行行列中其他的协程使命。当中止的协程被重新唤醒后,会把自己重新加入到协程行列里或许大局等候行列中。其间,中止的协程会把context保存在协程目标里,当协程被唤醒后会将其重新读取到寄存器中,持续运转。

一个很容出错的事例:

func TestGroutine(t *testing.T) {
    for i := 0; i < 10; i++ {
        go func(i int) {
            fmt.Println(i)
        }()
    }
}

其间i打印的值都是10,由于go 后边的代码有点像js的异步,for循环是同步代码,当循环完毕,i现已是10了,然后由于go又是词法作用域,所以,每次去寻觅外部i的值,都是10。

参考:

  • geektutu.com/post/quick-…