这里是 go 根底大部分内容来源于一门视频课程

学完后再来看手摸手开发一个全栈项目就基本能够直接上手了,项目本身没有什么难度!

Go言语是运用包来组织源代码的,包(package)是多个 Go 源码的调集,是一种代码复用方案。Go言语中为咱们供给了许多内置包,如 fmt、os、io等。

任何源代码文件有必要归于某个包,一起源码文件的第一行有效代码有必要是 package pacakgeName 句子,经过该句子声明自己地点的包。

Go言语没有强制要求包名有必要和其地点的目录名同名,但还是建议包名和地点目录同名,这样结构更清晰。

同一个途径下只能存在一个package,一个package能够由多个源代码文件组成, package main 包有且只能由一个 main 函数

包名的界说是不包括目录途径的,可是包在引证时一般运用全途径引证。比方在GOPATH/src/a/b/下界说一个包 c。在包 c 的源码中只需声明为package c,而不是声明为package a/b/c,可是在导入 c 包时,需求带上途径,例如import "a/b/c

go mod 存在时引进自界说包是以 go mod 文件 module 称号最初如 module coolcar 引进自界说包 "coolcar/proto/gen/go"

Go中假如函数名的首字母大写,标明该函数是公有的,能够被其他程序调用,假如首字母小写,该函数便是是私有的。

包名

包名一般是小写的,运用一个简略且有含义的称号。

包名一般要和地点的目录同名,也能够不同,包名中不能包括-等特别符号。

包一般运用域名作为目录称号,这样能确保包名的唯一性,比方 GitHub 项目的包一般会放到GOPATH/src/github.com/userName/projectName 目录下。

包名为 main 的包为运用程序的入口包,编译不包括 main 包的源码文件时不会得到可履行文件。

导入包

import “包的途径”

import 导入句子通常放在源码文件最初包声明句子的下面;

导入的包名需求运用双引号包裹起来;

包名是从GOPATH/src/后开始核算的,运用/ 进行途径分隔。

// 单行导入
import "包 1 的途径"
import "包 2 的途径"
// 多行导入  多个包 推荐运用多行
import (
    "包 1 的途径"
    "包 2 的途径"
)

包的导入途径

包的引证途径有两种写法,分别是全途径导入和相对途径导入。

相对途径只能用于导入GOPATH下的包,规范包的导入只能运用全途径导入。

绝对途径便是GOROOT/src/GOPATH/src/后边包的寄存途径。

包的引证格局

运用规范格局引证包,可是代码中却没有运用包,编译器会报错。假如包中有 init 初始化函数,则经过import _ "包的途径" 这种方式引证包,仅履行包的初始化函数,即便包没有 init 初始化函数,也不会引发编译器报错。

package main
// 界说别号
import F "fmt"
func main() {
    // 运用别号调用办法
    F.Println("import别号")
}
// 省掉引证格局
package main
import . "fmt"
func main() {
    //不需求加前缀 fmt.
    Println("省掉引证格局 此种方式不推荐  因为无法辨认函数归于那个包")
}
// 匿名引证格局 
package main
import (
    _ "database/sql"
    "fmt"
)
func main() {
    fmt.Println("匿名引证格局")
}

常用指令

build

用来打包go的源文件

build 单个文件 go build file1.go

build 多个文件 go build file1.go file2.go

build 编译时的附加参数:

  1. -v 编译时显现包名
  2. -p n 敞开并发编译,默许情况下该值为 CPU 逻辑核数
  3. -a 强制从头构建
  4. -n 打印编译时会用到的一切指令,但不真实履行
  5. -x 打印编译时会用到的一切指令
  6. -race敞开竞态检测
  7. -o 指定文件名 go build -o myexec main.go

clean

go clean 指令能够用来删去 Go 编译过程中发生的暂时文件和目录,包括以下几种:

  1. 履行go build指令时在当时目录下生成的与包名或许 Go 源码文件同名的可履行文件。这些文件是在编译 Go 代码时生成的,用于运转和测验代码。
  2. 履行go test指令并加入-c符号时在当时目录下生成的以包名加.test后缀为名的文件。这些文件是在编译测验代码时生成的,用于运转测验用例。
  3. 履行go install指令装置当时代码包时发生的成果文件。这些文件是在装置 Go 代码时生成的,用于部署和分发代码。
  4. 在编译 Go 或 C 源码文件时遗留在相应目录中的文件或目录。这些文件是在编译过程中暂时生成的,通常是中间文件或目标文件,一般不需求保存。

附加参数

  1. -i 铲除关联的装置的包和可运转文件,也便是经过go install装置的文件;
  2. -n 把需求履行的铲除指令打印出来,可是不履行;
  3. -r 循环的铲除在 import 中引进的包;
  4. -x 打印出来履行的详细指令,其实便是 -n 打印的履行版别;
  5. -cache 删去一切go build指令的缓存
  6. -testcache 删去当时包一切的测验成果

run

go run指令会编译源码,而且直接履行源码的 main() 函数,不会在当时目录留下可履行文件。

go run不会在运转目录下生成任何文件,可履行文件被放在暂时文件中被履行,作业目录被设置为当时目录。在go run的后部能够增加参数,这部分参数会作为代码能够接受的指令行输入供给给程序。

gofmt

Go言语的开发团队制定了一致的官方代码风格,而且推出了 ~gofmt 东西(gofmt 或 go fmt)来协助开发者格局化他们的代码到一致的风格。

gofmt 是一个 cli 程序,会优先读取规范输入,假如传入了文件途径的话,会格局化这个文件,假如传入一个目录,会格局化目录中一切 .go 文件,假如不传参数,会格局化当时目录下的一切 .go 文件。

附加参数

  1. -l仅把那些不符合格局化规范的、需求被指令程序改写的源码文件的绝对途径打印到规范输出。而不是把改写后的悉数内容都打印到规范输出。
  2. -w把改写后的内容直接写入到文件中,而不是作为成果打印到规范输出。
  3. -r增加形如“a[b:len(a)] -> a[b:]”的重写规矩。假如咱们需求自界说某些额定的格局化规矩,就需求用到它。
  4. -s简化文件中的代码。
  5. -d只把改写前后内容的比照信息作为成果打印到规范输出。而不是把改写后的悉数内容都打印到规范输出。指令程序将运用 diff 指令对内容进行比对。在 Windows 操作系统下或许没有 diff 指令,需求另行装置。
  6. -e打印一切的语法过错到规范输出。假如不运用此符号,则只会打印每行的第 1 个过错且只打印前 10 个过错。
  7. -comments是否保存源码文件中的注释。在默许情况下,此符号会被隐式的运用,而且值为 true。
  8. -tabwidth此符号用于设置代码中缩进所运用的空格数量,默许值为 8。要使此符号生效,需求运用“-tabs”符号并把值设置为 false。
  9. -tabs是否运用 tab('t')来替代空格标明缩进。在默许情况下,此符号会被隐式的运用,而且值为 true。
  10. -cpuprofile是否敞开 CPU 运用情况记载,并将记载内容保存在此符号值所指的文件中。

install

go build 指令相似,附加参数绝大多数都能够与 go build 通用。go install 只是将编译的中间文件放在 GOPATHpkg 目录下,以及固定地将编译成果放在 GOPATHbin 目录下。

get

go get 指令能够借助代码管理东西经过远程拉取或更新代码包及其依靠包,并自动完成编译和装置。整个过程就像装置一个 App 一样简略。 go get github.com/davyxu/cellnet

这个指令能够动态获取远程代码包,现在支持的有 BitBucket、GitHub、Google CodeLaunchpad。在运用 go get 指令前,需求装置与远程包匹配的代码管理东西,如 Git、SVN、HG 等,参数中需求供给一个包名。

附加参数

  1. -d 只下载不装置
  2. -u 强制运用网络去更新包和它的依靠包
  3. -f 只要在你包括了 -u 参数的时候才有效,不让 -u 去验证 import 中的每一个都现已获取了,这关于本地 fork 的包特别有用
  4. -fix 在获取源码之后先运转 fix,然后再去做其他的事情
  5. -t 一起也下载需求为运转测验所需求的包
  6. -v显现履行的指令

gopm

go get 指令因为网络原因或许导致某些包无法下载

运用gopm来获取无法下载的包(装置需运用指令行东西)github地址:

装置 go get -u github.com/gpmgo/gopm

常用指令

# 查看当时工程依靠
gopm list
# 显现依靠详细信息
gopm list -v
# 列出文件依靠
gopm list -t [file]
# 拉取依靠到缓存目录
gopm get -r xxx
# 仅下载当时指定的包
gopm get -d xxx
# 拉取依靠到$GOPATH
gopm get -g xxx
# 查看更新一切包
gopm get -u xxx
# 拉取到当时地点目录
gopm get -l xxx
# 运转当时目录程序
gopm run
# 生成当时工程的 gopmfile 文件用于包管理
gopm gen -v
# 依据当时项目 gopmfile 链接依靠并履行 go install
gopm install -v
# 更新当时依靠
gopm update -v
# 整理暂时文件
gopm clean
# 编译到当时目录
gopm bin

generate

go generate 指令是在Go言语 1.4 版别里边新增加的一个指令,当运转该指令时,它将扫描与当时包相关的源代码文件,找出一切包括 //go:generate的特别注释,提取并履行该特别注释后边的指令。

格局

go generate [-run regexp] [-n] [-v] [-x] [command] [build flags] [file.go... | packages]

  1. -run 正则表达式匹配指令行,仅履行匹配的指令;
  2. -v 输出被处理的包名和源文件名;
  3. -n 显现不履行指令;
  4. -x 显现并履行指令;
  5. command 能够是在环境变量 PATH 中的任何指令。

留意

  1. 该特别注释有必要在 .go 源码文件中;
  2. 每个源码文件能够包括多个 generate 特别注释;
  3. 运转go generate指令时,才会履行特别注释后边的指令;
  4. go generate指令履行犯错时,将中止程序的运转;
  5. 特别注释有必要以//go:generate最初,双斜线后边没有空格。

test

go test 指令,会自动读取源码目录下面名为 *_test.go 的文件,生成并运转测验用的可履行文件。

pprof

Go言语东西链中的 go pprof 能够协助开发者快速剖析及定位各种功能问题,如 CPU 消耗、内存分配及堵塞剖析。

go pprof 东西链合作 Graphviz 图形化东西能够将runtime.pprof包生成的数据转换为 PDF 格局,以图片的方式展现程序的功能剖析成果。

接口

Go 言语供给了另外一种数据类型即接口,它把一切的具有共性的办法界说在一起,任何其他类型只需完成了这些办法便是完成了这个接口。

Go 言语的接口规划对错侵入式的,接口编写者无须知道接口被哪些类型完成。而接口完成者只需知道完成的是什么姿态的接口,但无须指明完成哪一个接口。编译器知道最终编译时运用哪个类型完成哪个接口,或许接口应该由谁来完成。

Go言语中运用接口来体现多态,是duck-type(鸭子类型)的一种体现。当看到一只鸟走起来像鸭子、游水起来像鸭子、叫起来也像鸭子,那么这只鸟就能够被称为鸭子。

接口的界说与完成

package main
import (
    "fmt"
)
// 界说一个接口
type Phone interface {
    call()
}
// 界说一个结构体
type NokiaPhone struct {
}
// 完成
func (nokiaPhone NokiaPhone) call() {
    fmt.Println("I am Nokia, I can call you!")
}
type IPhone struct {
}
func (iPhone IPhone) call() {
    fmt.Println("I am iPhone, I can call you!")
}
func main() {
    //界说接口类型的变量
    var phone Phone
    //只需完成了此接口办法的类型,那么这个类型的变量(接纳者类型)就能够给i赋值
    phone = new(NokiaPhone)
    phone.call()
    phone = new(IPhone)
    phone.call()
}
package main
import "fmt"
// 声明一个接口
type reg interface {
	get(str string) string
}
// 声明结构体
type str1 struct {
	str string
}
// 界说结构体办法
func (s str1) get(str string) string {
	return str + "str1"
}
type str2 struct {
	str string
}
func (s str2) get(str string) string {
	return str + "str2"
}
func getStr(r reg) {
	fmt.Println(r.get("你是猪!"))
}
func main() {
	// 完成了办法  便是完成了接口
	testStr := str2{"11111111111"}
	getStr(testStr)
}

接口被完成的条件

接口的办法与完成接口的类型办法格局一致,在类型中增加与接口签名一致的办法就能够完成该办法。

签名包括办法中的称号、参数列表、回来参数列表。也便是说,只需完成接口类型中的办法的称号、参数列表、回来参数列表中的恣意一项与接口要完成的办法不一致,那么接口的这个办法就不会被完成。

接口中一切办法均被完成,当一个接口中有多个办法时,只要这些办法都被完成了,接口才干被正确编译并运用。

空接口与类型断语

具有0个办法的接口称为空接口。它标明为interface {}。因为空接口有0个办法,一切类型都完成了空接口。

类型断语用于提取接口的根底值,语法:i.(T)

package main
import(
"fmt"
)
func assert(i interface{}){
    s:= i.(int)
    fmt.Println(s)
}
// 程序打印的是int值, 可是假如咱们给s 变量赋值的是string类型,程序就会panic。
func main(){
  var s interface{} = 55
  assert(s)
}
// 能够这样写 ======================================================= 
package main
import (  
    "fmt"
)
func assert(i interface{}) {  
    v, ok := i.(int)
    fmt.Println(v, ok)
}
func main() {  
    // 假如 i 的值是int类型, 那么v便是i 对应的值, 
    // ok便是true。否则ok为false,程序并不会panic。
    var s interface{} = 56
    assert(s)
    var i interface{} = "Steven Paul"
    assert(i)
}

类型判别

类型判别的语法相似于类型断语。在类型断语的语法i.(type)中,类型type应该由类型转换的关键字type替换。

// 基本类型判别
package main
import (  
    "fmt"
)
func findType(i interface{}) {  
    switch i.(type) {
    case string:
        fmt.Printf("String: %sn", i.(string))
    case int:
        fmt.Printf("Int: %dn", i.(int))
    default:
        fmt.Printf("Unknown typen")
    }
}
func main() {  
    findType("Naveen")
    findType(77)
    findType(89.98)
}
/* 
=================================================================
 还能够将类型与接口进行比较。假如咱们有一个类型而且
 该类型完成了一个接口,那么能够将它与它完成的接口进行比较。
*/
package main
import "fmt"
type Describer interface {  
    Describe()
}
type Person struct {  
    name string
    age  int
}
func (p Person) Describe() {  
    fmt.Printf("%s is %d years old", p.name, p.age)
}
func findType(i interface{}) {  
    switch v := i.(type) {
    case Describer:
        v.Describe()
    default:
        fmt.Printf("unknown typen")
    }
}
func main() {  
    findType("Naveen")
    p := Person{
        name: "Naveen R",
        age:  25,
    }
    findType(p)
}
// 输出
unknown type  
Naveen R is 25 years old // 验证成功 调用 Describe()

接口的承继

接口既然能够承继自然能够组合

package main
import "fmt"
 // 界说一个接口  声明SayHi办法
type Humaner interface {
    SayHi()
}
 // 界说一个接口  承继Humaner接口 存在 SayHi、Sing办法
type Personer interface {
    Humaner
    Sing(lrc string)
}
 // 界说一个结构体
type Student struct {
    name string
    id int
}
// 完成接口的sayhi
func (s *Student)SayHi()  {
    fmt.Printf("%s sayhin", s.name)
}
 // 完成接口 Sing
func (p *Student)Sing(lrc string)  {
    fmt.Printf("student %s sing %sn", p.name, lrc)
}
func main() {  
    //界说一个接口的类型的变量
    var i Personer
    s := &Student{"mike", 1}
    i = s
    i.SayHi()
    i.Sing("loving you ")
}
// 履行成果
// mike sayhi
// student mike sing loving you

反常

反常分为三种修正时反常、编译时反常、运转时反常,通常 error 回来一般反常 panic 回来致命反常。

error 反常

error 反常不会中止程序履行

Go 言语经过内置的过错接口供给了非常简略的过错处理机制。

error类型是一个接口类型,这是它的界说:

type error interface {
    Error() string
}

创立一个 error

创立一个 error 最简略的办法便是调用 errors.New函数,它会依据传入的过错信息回来一个新的 error,示例代码如下:

package main
import (
    "errors"
    "fmt"
)
 // 运用 errors 来抛出一个error反常
func MyDiv(a, b int) (result int ,err error) {
    err = nil
    if b == 0 {
        err = errors.New("1/0 is error")
    } else {
        result = a / b
    }
    return result, err
}
func main() {
    result, err := MyDiv(1, 0)
    if err != nil {
        fmt.Println("result err = ", err)
    } else {
        fmt.Println("result = ", result)
    }
}

自界说过错类型

上边说 error 是一个接口,那么咱们就能够运用 error 接口自界说一个 Error() 办法,来回来自界说的过错信息。

package main
import (
    "fmt"
    "math"
)
// 界说结构体
type dualError struct {
    Num     float64
    problem string
}
// 结构体办法  自定已error 过错
func (e dualError) Error() string {
    // 回来自界说过错信息
    return fmt.Sprintf("Wrong!!!,because "%f" is a negative number", e.Num)
}
// 界说一个函数  接纳一个参数,  回来两个参数 其中最终一个时过错 error
func Sqrt(f float64) (float64, error) {
    // 判别获取参数小于 0 抛出error 反常
    if f < 0 {
        // dualError{Num: f} 默许会默许调用其是完成的 error 办法
        return -1, dualError{Num: f}
    }
    return math.Sqrt(f), nil
}
func main() {
    result, err := Sqrt(-13)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(result)
    }
}
// 运转成果如下: Wrong!!!,because "-13.000000" is a negative number

panic 反常

panic 反常与 error 的差异在于, panic 反常会中止程序的持续履行。

不是一切的 panic 反常都来自运转时,直接调用内置的 panic 函数也会引发 panic 反常;panic 函数接受任何值作为参数。当某些不应该发生的场景发生时咱们就应该调用 panic。

func testb()  {
	//显式调用panic函数,导致程序中止
	panic("this is a panic test")
}
func testc() {
	fmt.Println("cccccccccccc")
}
func main() {
    // testb 中运用了 panic 反常 则  testc不会持续履行
	testb()
	testc()
}

运用 recover函数,捕获panic反常

recover 函数是一个内置函数

  1. recover 函数只要在 defer 代码块中才会有效果
  2. 专门用来接纳panic函数回来值。
  3. 假如在deferred函数中调用了内置函数recover,而且界说该defer句子的函数发生了panic反常,recover会使程序从panic中康复,并回来panic value。导致panic反常的函数不会持续运转,但能正常回来。
  4. 在未发生panic时调用recover,recover会回来nil。

panic 是一个能够中止程序履行流程的内置函数假设当时 F 函数傍边某处代码触发 panic 函数则 F 函数中止后边代码的履行,转而履行 F 函数内部的 defer 函数(假如现已声明晰defer函数的话…),然后完毕F函数,将当时处理权转给F的调用函数

func recoverFunc(i int) {
    // recover 有必要在 defer 函数内
    // 在 recover() 声明前的过错无法被捕获
    // recover() 回来值为接口类型
    defer func() {
            if recover() != nil {
                    fmt.Println("你的程序遇到致命的过错!", recover())
            }
    }()
    // 数组越界
    var testArr [10]int
    testArr[i] = 100
}
// 发生数组下标越界反常
recoverFunc(100)

单元测验

Go自带了测验框架和东西,在testing包中,以便完成单元测验(T类型)和功能测验(B类型)。

一般测验代码放在*_test.go文件中,与被测代码放于同一个包中。
语法格局 func TestXxx(*testing.T)

Xxx 能够是任何字母数字字符串,可是第一个字母不能是小写字母。

测验办法

  1. Fail: 符号失败,但持续履行当时测验函数
  2. FailNow: 失败,立即中止当时测验函数履行
  3. Log: 输出过错信息
  4. Error: Fail + Log
  5. Fatal: FailNow + Log
  6. Skip: 越过当时函数,通常用于未完成的测验用例

第一个单元测验

运转以下指令,自动收集一切的测验文件(*_test.go)提取悉数测验函数。

func Fib(n int) int {
    if n < 2 {
            return n
    }
    return Fib(n-1) + Fib(n-2)
}
func TestFib(t *testing.T) {
    var (
        in       = 7
        expected = 13
    )
    actual := Fib(in)
    if actual != expected {
        t.Errorf("Fib(%d) = %d; expected %d", in, actual, expected)
    }
}

履行 go test .输出:

$ go test .
ok      chapter09/testing    0.007s  // 标明经过

修正函数为

func Fib(n int) int {
        if n < 2 {
                return n
        }
        return Fib(n-1) + Fib(n-1)
}

再次履行测验 go test . 得到输出 输出成果包括:犯错的测验函数称号,履行时长和过错信息。

$ go test .
--- FAIL: TestSum (0.00s)
    t_test.go:16: Fib(10) = 64; expected 13
FAIL
FAIL    chapter09/testing    0.009s

testing 的测验用例方式

TestXxxx(t *testing.T) // 基本测验用例

BenchmarkXxxx(b *testing.B) // 压力测验的测验用例

Example_Xxx() // 测验控制台输出的比如

TestMain(m *testing.M) // 测验 Main 函数

如 Example 的比如:

func Example_GetScore() {
        score := getScore(100, 100, 100, 2.1)
        fmt.Println(score)
        // Output:
        // 31.1
    }

常见过错

测验函数找不到?

测验函数找不到?或许 go: cannot find main module, but found .git?或许 cannot determine module path for source directory xxxxxxx

查看根目录是否存在 mod 文件,如不存在请新建,在根目录履行指令 go mod init [自界说一个名字]

goroutine 协程

goroutine是Go并行规划的中心。goroutine说到底其实便是协程它比线程更小,十几个goroutine或许体现在底层便是五六个线程。

Go言语内部帮你完成了这些goroutine之间的内存共享。履行goroutine只需很少的栈内存(大概是4~5KB),当然会依据相应的数据弹性。

也正因为如此,可一起运转成千上万个并发使命。goroutine比thread更易用、更高效、更简便。

协程的特点

协程是一个轻量级的线程,此处能够与进程、线程、协程 进行比照

Go协程对错抢占式多使命处理,需求由协程自动交出控制权

协程是一个 虚拟机层面的多使命处理

多个协程或许运转在一个或许多个线程上

创立 goroutine

在函数前 加 go 关键字 即可创立 goruoutine


func newTask() {
    for {
        fmt.Println("this is new task")
        time.Sleep(time.Second) //延时1s
    }
}
func main() {
    go newTask() // 建一个协程,新建一个使命
    for {
        fmt.Println("this is a main goroutine")
        time.Sleep(time.Second) //延时1s
    }
}

主协程先退出其它子协程也会跟着退出

// 主协程退出了,其它子协程也要跟着退出
// manin 函数本身是一个 goroutine 
func main() {
    go func() {
        fmt.Println("this is a new task")
        time.Sleep(time.Second)
    }() //() 代表调用
    i :=0
    for {
        i++
        fmt.Println("main this is a main task i=", i)
        time.Sleep(time.Second)
        if i == 3 {
                break
        }
    }
}

Go言语协程调度器

Go言语中一切的协程都经过调度器进行调度,大并发下成百上千甚至几千的协程调用 经过调度器安排到不同的线程中履行。

调度的切换调度器会在适宜的时刻点进行协程之间的切换,经过切换能够将核算资源分配给其他协程而不至于被一个协程锁死资源

goroutine 或许的切换点 仅是参阅不能确保切换,不能确保在其他当地不进行切换

  1. IO/SELECT
  2. channel
  3. 函数调用 (有时)
  4. runtime.Gosched() 手动切换
  5. 等候锁

相关办法

runtime.Gosched() 让出时刻片,先让别的协程履行,它履行完,在回来履行此协程

runtime.Goexit() 中止地点的协程

runtime.GOMAXPROCS() 指定以x核运算

Channel

Channel是Go中的一个中心类型,你能够把它看成一个管道,经过它并发中心单元就能够发送或许接纳数据进行通讯(communication)。

Channel能够作为一个先入先出(FIFO)的行列,接纳的数据和发送的数据的次序是一致的。

Channel的创立

channel有必要先创立再运用: ch := make(chan int) 或许 make(chan int, 100), 否则会永久堵塞。二者的差异在于 一个创立时初始化了容量,一个无

Channel类型 ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .,可选的<-代表channel的方向。假如没有指定方向,那么Channel便是双向的,既能够接纳数据,也能够发送数据。

ch := make(chan int)
ch <- v    // 发送值v到Channel ch中
v := <-ch  // 从Channel ch中接纳数据,并将数据赋值给v
//========================================================================================
chan T          // 能够接纳和发送类型为 T 的数据
chan<- float64  // 只能够用来发送 float64 类型的数据
<-chan int      // 只能够用来接纳 int 类型的数据

Channel有无容量的差异(缓存)

容量(capacity)代表Channel包容的最多的元素的数量,代表Channel的缓存的巨细。

假如没有设置容量,或许容量设置为0, 阐明Channel没有缓存,只要senderreceiver都准备好了后它们的通讯(communication)才会发生(Blocking)。

假如设置了缓存,就有或许不发生堵塞, 只要buffer满了后 send才会堵塞, 而只要缓存空了后receive才会堵塞。一个nil channel不会通讯。

经过 len 函数能够获得 chan 中的元素个数,经过 cap 函数能够得到 channel 的缓存长度。

range 遍历

channel 也能够运用 range 取值,而且会一向从 channel 中读取数据,直到有 goroutine 对改 channel 履行 close 操作,循环才会完毕。

// consumer worker
ch := make(chan int, 10)
for x := range ch{
    fmt.Println(x)
}

封闭 Channel

golang 供给了内置的 close 函数对 channel 进行封闭操作。

封闭一个未初始化(nil) 的 channel 会发生 panic

重复封闭同一个 channel 会发生 panic

向一个已封闭的 channel 中发送音讯会发生 panic

从已封闭的 channel 读取音讯不会发生 panic,且能读出 channel 中还未被读取的音讯,若音讯均已读出,则会读到类型的零值。

从一个已封闭的 channel 中读取音讯永久不会堵塞,而且会回来一个为 falseok-idiom,能够用它来判别 channel 是否封闭

封闭 channel 会发生一个广播机制,一切向 channel 读取音讯的 goroutine 都会收到音讯

// 封闭
ch := make(chan int)
close(ch)
// 运用一个额定的回来参数来查看channel是否封闭。 假如OK 是false,标明接纳的x是发生的零值,这个channel被封闭了或许为空。
x, ok := <- ch
fmt.Println(x, ok)

select

select 是用于处理通道操作的关键字,通常用于在多个通道上等候操作。它答应你在多个通道上进行非堵塞的操作,并依据操作的情况履行相应的代码块。select 句子使得在多个通道之间进行挑选和控制变得更加灵活。

select句子挑选一组或许的send操作和receive操作去处理。它相似switch,可是只是用来处理通讯(communication)操作。

  1. 它的case能够是send句子,也能够是receive句子,亦或许default
  2. 假如有一起多个 case 去处理,那么Go会伪随机的挑选一个case处理(pseudo-random)。
  3. 假如没有case需求处理,则会挑选default去处理,假如default case存在的情况下。
  4. 假如没有default case,则select句子会堵塞,直到某个case需求处理。

receive 句子能够将值赋值给一个或许两个变量。它有必要是一个receive操作。

最多答应有一个default case,它能够放在case列表的任何方位,尽管咱们大部分会将它放在最终。

nil channel上的操作会一向被堵塞,假如没有default case,只要nil channel的select会一向被堵塞。

import "fmt"
func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}
func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}

select句子和switch句子一样它不是循环它只会挑选一个case来处理,假如想一向处理channel你能够在外面加一个无限的for循环:

for {
    select {
    case c <- x:
        x, y = y, x+y
    case <-quit:
        fmt.Println("quit")
        return
    }
}

超时处理

select有很重要的一个运用便是超时处理。 因为上面咱们说到,假如没有case需求处理,select句子就会一向堵塞着。这时候咱们或许就需求一个超时操作,用来处理超时的情况。

time.After办法,它回来一个类型为<-chan Time的单向的channel,在指定的时刻发送一个当时时刻给回来的channel中。

下面这个比如咱们会在2秒后往channel c1中发送一个数据,可是select设置为1秒超时,因而咱们会打印出timeout 1,而不是result 1。

import "time"
import "fmt"
func main() {
    c1 := make(chan string, 1)
    go func() {
        time.Sleep(time.Second * 2)
        c1 <- "result 1"
    }()
    select {
    case res := <-c1:
        fmt.Println(res)
    case <-time.After(time.Second * 1):
        fmt.Println("timeout 1")
    }
}

Timer 和 Ticker

timer是一个守时器,代表未来的一个单一事情,你能够告诉timer你要等候多长时刻,它供给一个Channel,在将来的那个时刻那个Channel供给了一个时刻值。

// 比如中第二行会堵塞2秒钟左右的时刻,直到时刻到了才会持续履行。
timer1 := time.NewTimer(time.Second * 2)
<-timer1.C
fmt.Println("Timer 1 expired")

当然假如只是想单纯的等候的话,能够运用time.Sleep来完成。还能够运用timer.Stop来中止计时器。

timer2 := time.NewTimer(time.Second)
go func() {
    <-timer2.C
    fmt.Println("Timer 2 expired")
}()
stop2 := timer2.Stop()
if stop2 {
    fmt.Println("Timer 2 stopped")
}

ticker是一个守时触发的计时器,它会以一个距离intervalChannel发送一个事情(当时时刻),而Channel的接纳者能够以固定的时刻距离从Channel中读取事情。

// 比如中ticker每500毫秒触发一次,你能够调查输出的时刻。
ticker := time.NewTicker(time.Millisecond * 500)
go func() {
    for t := range ticker.C {
        fmt.Println("Tick at", t)
    }
}()

timer, ticker也能够经过Stop办法来中止。一旦它中止,接纳者不再见从channel中接纳数据了。

fmt

fmt包完成了相似C言语printfscanf的格局化I/O。格局化动作(’verb’)源自C言语但更简略。

通用

%v 值的默许格局标明

%+v 相似%v,但输出结构体时会增加字段名

%#v 值的Go语法标明

%T 值的类型的Go语法标明

%% 百分号

布尔值

%t单词truefalse

整数

%b 标明为二进制

%c 该值对应的unicode码值

%d 标明为十进制

%o 标明为八进制

%q 该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义标明

%x 标明为十六进制,运用a-f

%X 标明为十六进制,运用A-F

%U 标明为Unicode格局:U+1234,等价于”U+%04X”

浮点数与复数

%b 无小数部分、二进制指数的科学计数法,如-123456p-78

%e 科学计数法,如-1234.456e+78

%E 科学计数法,如-1234.456E+78

%f 有小数部分但无指数部分,如123.456

%F 等价于%f

%g 依据实际情况采用%e%f格局(以获得更简洁、精确的输出)

%G 依据实际情况采用%E%F格局(以获得更简洁、精确的输出)

字符串和 []byte

%s 直接输出字符串或许[]byte

%q 该值对应的双引号括起来的go语法字符串字面值,必要时会采用安全的转义标明

%x 每个字节用两字符十六进制数标明(运用a-f)

%X 每个字节用两字符十六进制数标明(运用A-F)

指针

%p 标明为十六进制,并加上前导的0x

格局化过错

假如给某个占位符供给了非法的参数,如给%d供给了一个字符串,生成的字符串会包括该问题的描绘,如下所例:

// 类型过错或占位符不知道:%!verb(type=value)
Printf("%d", hi)
// %!d(string=hi)
// 实参太多:%!(EXTRA type=value)
Printf("hi", "guys")
// hi%!(EXTRA string=guys)
// 实参太少:%!verb(MISSING)
Printf("hi%d")
// hi %!d(MISSING)
// 宽度或精度不是 int 类型:%!(BADWIDTH)或 %!(BADPREC)
Printf("%*s", 4.5, "hi")
// %!(BADWIDTH)hi
Printf("%.*s", 4.5, "hi")
// %!(BADPREC)hi

仍有一些需求留意的当地请点击这里查阅

往期文章