美文网首页
Go语言开发笔记

Go语言开发笔记

作者: 阿帕奇UP | 来源:发表于2020-04-02 17:31 被阅读0次

    一、Go开发环境

    1. GOROOT

    就是Go的安装路径。

    2. GOPATH

    GOPATH 是作为编译后二进制的存放目的地和 import 包时的搜索路径 (其实也是你的工作目录,你可以在 src 下创建你自己的 go 源文件,然后开始工作)。

    GOPATH 之下主要包含三个目录:

    • bin:目录主要存放可执行文件;
    • pkg:目录存放编译好的库文件;
    • src:目录下主要存放 Go 的源文件。

    3. 程序的运行

    $ go run xxx.go    // go run 命令, 将 .go源文件进行编译、链接,然后运行生产可执行文件
    $ go build xxx.go  // go build 命令,编译生成一个可执行程序
    

    二、Go语言基础

    1. 常量

    const limit = 512
    const top uint16 = 1421
    const Pi float64 = 3.1415926
    const x, y, z int = 1, 3, 5   //多重赋值
    

    iota 是一个可以被编译器修改的常量,在 const 关键字出现时被重置为 0,在下一个 const 出现之前,每出现一次 iota,其所代表的数字自动加 1。

    const (
      a = iota  // a == 0
      b = iota  // b == 1
      c = iota  // c == 2
    )
    const d = iota  // d == 0, 因为const的出现,iota被重置为0。
    

    2. 变量

    var a int
    var b string
    var c float64
    var d [5] int    // 数组
    var e [] int     // 数组切片
    var f int = 5
    var g = 55       // 自动类型推导(省略了int的显式声明)
    h := 666         // 省略 var,省略 int
    

    交换两个变量:

    x := 2
    y := 3
    x, y = y, x      // Go支持多重赋值
    

    3. 数据类型

    3.1 整型

    类型 说明
    byte 等同于 uint8
    int 依赖于不同平台下的实现,可以是 int32 或者 int64
    int8 [-128, 127]
    int16 [-32768, 32767]
    int32 [-2147483648, 2147483647]
    int64 [-9223372036854775808, 9223372036854775807]
    rune 等同于 int32
    uint 依赖于不同平台下的实现,可以是 uint32 或者 uint64
    uint8 [0, 255]
    uint16 [0, 65535]
    uint32 [0, 4294967295]
    uint64 [0, 18446744073709551615]
    uintptr 一个可以恰好容纳指针值的无符号整型(对 32 位平台是 uint32, 对 64 位平台是 uint64)

    查看某数据类型的字节长度:

    var x int64 = 12
    fmt.Println("length of int64 :", unsafe.Sizeof(x))    // length of int64 : 8
    

    3.2 浮点型

    类型 说明
    float32 ±3.402 823 466 385 288 598 117 041 834 845 169 254 40x1038 计算精度大概是小数点后 7 个十进制数
    float64 ±1.797 693 134 862 315 708 145 274 237 317 043 567 981x1038 计算精度大概是小数点后 15 个十进制数
    complex32 复数,实部和虚部都是 float32
    complex64 复数,实部和虚部都是 float64

    3.3 布尔型

    go语言提供了布尔值 true 和 false. 不接受其他类型的赋值,不支持类型转换。

    var a bool
    a = true 
    b := (2 == 3)
    

    3.4 字符串

    转义字符 含义
    \ 表示反斜线
    ' 单引号
    " 双引号
    \n 换行符
    \uhhhh 4 个 16 进制数字给定的 Unicode 字符

    在Go语言中单个字符可以使用单引号' ' 来创建。

    一个单一的字符可以用一个单一的rune来表示。 rune类型,等同于 int32。

    字符串支持的操作如下:

    语法 描述
    s += t 将字符串 t 追加到 s 末尾
    s + t 将字符串 s 和 t 级联
    s[n] 从字符串 s 中索引位置为 n 处的原始字节
    s[n:m] 从位置 n 到位置 m-1 处取得的字符(字节)串
    s[n:] 从位置 n 到位置 len(s)-1 处取得的字符(字节)串
    s[:m] 从位置 0 到位置 m-1 处取得的字符(字节)串
    len(s) 字符串 s 中的字节数
    len([]rune(s)) 字符串 s 中字符的个数,可以使用更快的方法 utf8.RuneCountInString()
    [ ]rune(s) 将字符串 s 转换为一个 unicode 值组成的串
    string(chars) chars 类型是 []rune 或者 []int32, 将之转换为字符串
    [ ]byte(s) 无副本的将字符串 s 转换为一个原始的字节的切片数组,不保证转换的字节是合法的 UTF-8 编码字节

    3.5 格式化字符串

    格式化指令 含义
    %% % 字面量
    %b 一个二进制整数,将一个整数格式化为二进制的表达方式
    %c 一个 Unicode 的字符
    %d 十进制数值
    %o 八进制数值
    %x 小写的十六进制数值
    %X 大写的十六进制数值
    %U 一个 Unicode 表示法表示的整形码值,默认是 4 个数字字符
    %s 输出以原生的 UTF-8 字节表示的字符,如果 console 不支持 UTF-8 编码,则会输出乱码
    %t 以 true 或者 false 的方式输出布尔值
    %v 使用默认格式输出值,或者使用类型的 String() 方法输出的自定义值,如果该方法存在的话
    %T 输出值的类型

    3.6 字符类型

    Go语言中支持两个字符类型:

    Byte (实际上是 Unit8的别名), 代表UTF-8字符串的单个字节的值。

    rune , 代表单个 Unicode字符。

    3.7 数组

    数组是一个定长的序列,其中每个元素的类型都相同。 固定长度,不可修改。

    var a [5] int
    var b = [5]int{1,2,3,4,5}
    var c = [...]int{1,2,3,4,5}    // 如果使用了省略号,Go语言会为我们自动计算数组的长度。
    

    3.8 切片

    Go语言的切片比数组更加灵活、强大、方便! 切片不定长,可以随时调整长度。

    数组是按值传递的(即传递的副本),而切片是引用类型,传递切片的成本非常小。

    var a make([]int, 10, 20)      // 切片的长度和容量分别为 10 和 20
    var b = []int{1,2,3,4,5}
    

    3.9 包

    Go语言中的代码组织方式是包。 包是各种类型和函数的集合。

    在包中,如果标识符的首字母是大写的,那这些标识符是可以被导出的,可以在包以外直接使用。

    4. 流程控制

    if aBoolValue {
      block1
    }else if anotherBoolValue {
      block2
    }else {
      block3
    }
    

    go语言只支持for循环语句,不支持 while / do-while语句。

    sum := 0 
    for i := 0; i < 10; i++ { 
        sum += i 
    }  
    
    for boolVlaue {
                                // while 循环。 Go语言中没有while关键字
    }
    for index, char := range aString {
                      // 迭代字符串
    }
    for item := range aChannel {
                      // 迭代通道
    }
    
    switch aExpression {
      case expression1:
          block1
      ... 
      case expressionN:
            blockN
      default:
            BlockD
    }
    

    5. 函数

    package Divide
    func divide(a int, b int) (num int, err error) {
      if b == 0 {
        err := errors.New("被除数不能为0")
        return
      }
      return a / b, nil           // 支持返回多个值。不必创建临时的结构体来包装。
    }
    
    // 下面是函数的调用
    package main
    import (
      "Divide"          // 导入 Add 包
      "fmt"
    )
    func main() {
      c, err := Divide.divide(1,2)
      if err==nil {
        fmt.Println(c)
      }
    }
    

    5.1 匿名函数

    匿名函数由一个不带函数名的函数声明和函数体组成。

    func (a,b,c int) bool {
      return a * b < c
    }
    

    你可以将匿名函数直接赋值给一个变量,也可以直接调用运行。

    x := func (a,b,c int) bool {
      return a * b < c
    }
    
    func (a,b,c int) bool {
      return a * b < c
    } (1,2,3)                 // 小括号内直接给参数列表 表示函数调用
    

    接口是一个自定义类型。它声明了一个或者多个方法。任何实现了这些方法的对象(类型)都满足这个接口。

    var i interface{} = 99              // 创建一个interface{}类型,其值为99
        var s interface{} = []string{"left", "right"}
        j := i.(int)                    // 我们假设i是兼容int类型,并使用类型断言将其转换为int类型
        fmt.Printf("type and value of j is: %T and %d\n", j, j)
    
        if s, ok := s.([]string); ok {  // 创建了影子变量,if的作用域中覆盖了外部的变量s
            fmt.Printf("%T -> %q\n", s, s)
        }
    

    5.2 defer

    defer 语句是在 return 之后执行的。

    func CopyFile(dst, src string) (w int64, err error) {
        srcFile, err := os.Open(src)
        if err != nil {
            return
        }
        defer srcFile.Close()
        dstFile, err := os.Create(dst)
        if err != nil {
            return
        }
        defer dstFile.Close()
        return io.Copy(dstFile, srcFile)
    }
    

    5.3 panic & recover

    panic() 函数用于抛出异常,recover() 函数用于捕获异常。

    6. 结构体

    type ColorPoint struct {
      color.Color            // 匿名字段(嵌入)
      x, y int                           // 具名字段(聚合)
    }
    

    7. 方法

    方法是作用在自定义类型上的一类特殊函数。

    定义方法和定义函数几乎相同,只是需要在 func 关键字和方法名之间必须写上接接受者。

    type Count int
    func (count *Count) Increment() {        // 接受者是一个 `Count` 类型的指针
      *count++
    }
    

    8. 组合

    Go 语言中没有继承。 但是提供了一个组合特性。

    相对于继承的编译期确定实现,组合的运行态指定实现,更加灵活。

    9. 接口

    Go中接口是一组方法签名。当一个类型为接口中的所有方法提供定义时,它被称为实现该接口。

    infterface{} 类型是声明了空方法集的接口类型。任何一个值都满足 interface{} 类型,也就是说如果一个函数或者方法接收 interface{} 类型的参数,那么任意类型的参数都可以传递给该函数。

    package main
    import "fmt"
    type Human struct { // 结构体
        name  string
        age   int
        phone string
    }
    
    //Human实现SayHi方法
    func (h Human) SayHi() {
        fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
    }
    
    //Human实现Sing方法
    func (h Human) Sing(lyrics string) {
        fmt.Println("La la la la...", lyrics)
    }
    
    type Student struct {
        Human  //匿名字段
        school string
        loan   float32
    }
    
    type Employee struct {
        Human   //匿名字段
        company string
        money   float32
    }
    
    // Employee重载Human的SayHi方法
    func (e Employee) SayHi() {
        fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
            e.company, e.phone)
    }
    
    // Interface Men被Human,Student和Employee实现
    // 因为这三个类型都实现了这两个方法
    type Men interface {
        SayHi()
        Sing(lyrics string)
    }
    
    func main() {
        mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
        paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
        sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
        Tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000}
    
        //定义Men类型的变量i
        var i Men
    
        //i能存储Student
        i = mike
        fmt.Println("This is Mike, a Student:")
        i.SayHi()
        i.Sing("November rain")
    
        //i也能存储Employee
        i = Tom
        fmt.Println("This is Tom, an Employee:")
        i.SayHi()
        i.Sing("Born to be wild")
    
        //定义了slice Men
        fmt.Println("Let's use a slice of Men and see what happens")
        x := make([]Men, 3)
        //这三个都是不同类型的元素,但是他们实现了interface同一个接口
        x[0], x[1], x[2] = paul, sam, mike
    
        for _, value := range x {
            value.SayHi()
        }
    }
    

    三、并发编程

    1. 并发与并行

    并发指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,通过 CPU 时间片轮转使多个进程快速交替的执行。而并行的关键是你有同时处理多个任务的能力。

    并发和并行都可以是很多个线程,就看这些线程能不能同时被(多个)CPU 执行,如果可以就说明是并行,而并发是多个线程被(一个)CPU 轮流切换着执行。

    并发与并行的区别:并发是两个队列,使用一台咖啡机;并行是两个队列,使用两台咖啡机。如果串行,一个队列使用一台咖啡机,那么哪怕前面那个人有事出去了半天,后面的人也只能等着他回来才能去接咖啡,这效率无疑是最低的。

    2. 协程

    协程也叫轻量级线程。与传统的进程和线程相比,协程最大的优点就在于其足够“轻”,操作系统可以轻松创建上百万个协程而不会导致系统资源枯竭,而线程和进程通常最多不过近万个。

    多数语言在语法层面上是不支持协程的,一般都是通过库的方式进行支持,但库的支持方式和功能不够完善,经常会引发阻塞等一系列问题,而 Go 语言在语法层面上支持协程,也叫 goroutine。这让协程变得非常简单,让轻量级线程的切换管理不再依赖于系统的进程和线程,也不依赖 CPU 的数量。

    3. goroutine

    goroutine 是 Go 语言并行设计的核心。goroutine 是一种比线程更轻量的实现,十几个 goroutine 可能在底层就是几个线程。

    要使用 goroutine 只需要简单的在需要执行的函数前添加 go 关键字即可。

    func Add(a, b int) {
      c := a + b
      fmt.Println(c)
    }
    go Add(1, 2)                  // 使用 go 关键字让函数并发执行
    

    当在一个函数前加上 go 关键字,该函数就会在一个新的 goroutine 中并发执行,当该函数执行完毕时,这个新的 goroutine 也就结束了。

    4. channel

    Go 语言提供的信道(channel)就是专门解决并发通信问题的.

    channelgoroutine 之间互相通讯的东西。类似我们 Unix 上的管道(可以在进程间传递消息),用来 goroutine 之间发消息和接收消息。其实,就是在做 goroutine 之间的内存共享。

    var a chan int            // 声明一个传递元素类型为int的channel
    var b chan float64
    var c chan string
    
    x := make(chan int)       // 声明并初始化一个int型的名为a的channel
    y := make(chan float64)
    z := make(chan string)
    

    channel 最频繁的操作就是写入和读取,这两个操作也非常简单:

    a := make(chan int)
    a <- 1                    // 将数据写入channel
    z := <-a                  // 从channel中读取数据
    

    channel的关闭非常简单,使用 Go 语言内置的 close() 函数即可关闭 channel

    ch := make(chan int)
    close(ch)
    

    5. select

    select 用于处理异步 IO 问题,它的语法与 switch 非常类似。由 select 开始一个新的选择块,每个选择条件由 case 语句来描述,并且每个 case 语句里必须是一个 channel 操作。

    func main() {
        c1 := make(chan string)    // 初始化两个 channel c1 和 c2
        c2 := make(chan string)
        go func() {            // 开启两个 goroutine 分别往 c1 和 c2 写入数据
            time.Sleep(time.Second * 1)
            c1 <- "one"
        }()
        go func() {
            time.Sleep(time.Second * 2)
            c2 <- "two"
        }()
        for i := 0; i < 2; i++ {
            select {                  // 通过 select 监听两个 channel
            case msg1 := <-c1:
                fmt.Println("received", msg1)
            case msg2 := <-c2:
                fmt.Println("received", msg2)
            }
        }
    }
    

    6. 超时机制

    t := make(chan bool)
    go func {
        time.Sleep(1e9) //等待1秒
        t <- true
    }
    select {
        case <-ch:  //从ch中读取数据
        case <-t:  //如果1秒后没有从ch中读取到数据,那么从t中读取,并进行下一步操作
    }
    

    这样的方法就可以让程序在等待 1 秒后继续执行,而不会因为 ch 读取等待而导致程序停滞,从而巧妙地实现了超时处理机制,这种方法不仅简单,在实际项目开发中也是非常实用的。

    相关文章

      网友评论

          本文标题:Go语言开发笔记

          本文链接:https://www.haomeiwen.com/subject/watfphtx.html