golang核心编程

golang核心笔记

一、Go的命令行

- GOROOT: go当前安装目录
- GOPATH: 工作区的集合,多个用:分隔.工作区是放置 Go 源码文件的目录.三个目录:**src 目录,pkg 目录,bin 目录**。
- GO111MODULE: mod包管理开启
- GOPROXY: go代理,配合GO111MODULE使用
- go env -w GO111MODULE=on
- eport GO111MODULE=auto
- go mod init [project_name]  # 初始化一个mod管理项目
- go env                      # 查看更多命令
- go build xx.go -o xx -i [build flags]
- go run xx.go
- go clean                    # 删除目标文件和缓存文件
- go test -v                  # 测试项目目标的xxx_test.go文件
- go test -test.bench=".*"                         # 测试总个目录的
- go test xxx_test.go  -test.bench=".*"            # 测试单个文件
- go test xxx_test.go -benchmem  -test.bench=".*"  # 显示内存 
- gofmt -w xx.go             # 格式化某个文件或目录

二、标识符、变量与类据类型

25个关键字

if      for     func    case        struct      import               
go      type    chan    defer       default     package
map     const   else    break       select      interface
var     goto    range   return      switch      continue     fallthrough                

保留字

内建常量:true        false       iota        nil
内建类型:int         int8        int16       int32       int64
         uint        uint8       uint16      uint32      uint64      uintptr
         float32     float64     complex128  complex64
bool:   byte        rune        string      error
内建函数: make        delete      complex     panic       append      copy    
          close       len         cap        real        imag        new      recover

变量与常量的定义

  • 常量定义: const [常量名] [类型]
  • 变量定义: var [变量名] [类型]
//**************************常量定义************************************************
const name = 'ok'                       //隐式类型定义
const name string = "ok"                //显式类型定义
//**************************变量定义************************************************
var dp = [3][5]int{[5]int{1, 2, 3, 4, 5}, [5]int{4, 5, 6}, [5]int{7, 8, 9}}  //多维数组的初始化,不足补0
s := []string{"abc", "ABC"}             //切片初始化
slice1 := s[1:2]                        //[开始:结束] 开始和结束不能大于数组实际长度
var slice2 []type = make([]type, len)

s1 := []int{1, 2}                       //切片增加元素
s1 = append(s1, 3)
s2 := []int{4, 5}
s3 := append(s1, s2...)                 //合并两个切片
//**************************************************************************
res := struct {                         //匿名结构体
    Name string
    Age int
}{Name:"lily", Age:18}
jsons, err := json.Marshall(res)       //json序列化(注:jsons的类型是[]type)
errs = json.Unmarshal(jsons, &res2)    //反序列化
//**************************************************************************
person := map[int]string{              //map集合是无序的 key-value 数据结构
    1 : "Tom",
    2 : "Aaron",
    3 : "John",
}
delete(person, 2)                     //删除
person[2] = "Jack"                    //增加
person[3] = "Kevin"                   //修改

数据类型

  • 值类型: 整型int、浮点型(float32、flaoat64)、复数complex128、bool、string、struct
  • 引用类型:*、slice、map、func、chan、interface
  • 字符型可以使用byte来保存单个字母:byte的实际类型是uint8, rune的实际类型是int32
  • 整数类型:int和uint
  • int8、int16、int32和int64四种有符号整数类型,uint8、uint16、uint32和uint64对应四种无符号整数类型。

strings包提供了字符串的一些常见操作函数

Index(str, s string) int                // 查找s在字符串str中的索引
Contains(str, s string) bool            // 判断str是否包含s
Join(s []string, str string) string     // 通过字符串str连接切片 s
//替换字符串str中old字符串为new字符串,n表示替换的次数,小于0全部替换
Replace(str,old,new string,n int) string
Splite(str,s string)[]string            // 字符串str按照s分割,返回切片
Trim(s string, cutset string) string    // 去除头部、尾部指定的字符串
Fields(s string) []string               // 去除空格,返回切片

strconv包的字符串转换函数: Append、Format、Parse

package main

import (
    "fmt"
    "strconv"
)

func main() {

    // Append 系列函数将整数等转换为字符串后,添加到现有的字节数组中
    str1 := make([]byte, 0, 100)
    str1 = strconv.AppendInt(str1, 4567, 10)
    str1 = strconv.AppendBool(str1, false)
    str1 = strconv.AppendQuote(str1, "abcdefg")
    str1 = strconv.AppendQuoteRune(str1, '单')
    fmt.Println(string(str1))                        // 4567false"abcdefg"'单'

    // Format 系列函数把其他类型的转换为字符串
    a := strconv.FormatBool(false)
    b := strconv.FormatFloat(123.23, 'g', 12, 64)
    c := strconv.FormatInt(1234, 10)
    d := strconv.FormatUint(12345, 10)
    e := strconv.Itoa(1023)
    fmt.Println(a, b, c, d, e)                        // false 123.23 1234 12345 1023

    // Parse 系列函数把字符串转换为其他类型
    f, _ := strconv.ParseBool("false")
    g, _ := strconv.ParseFloat("123.23", 64)
    h, _ := strconv.ParseInt("1234", 10, 64)
    i, _ := strconv.ParseUint("12345", 10, 64)
    j, _ := strconv.Atoi("1023")
    fmt.Println(f, g, h, j, i, j)                    // false 123.23 1234 1023 12345 1023
}

map与sync.Map

  • map是一个无序键值对集合, 底层采用hash数据结构。类似C++中的 unorder_map, 而C++中的map底层是红黑树,天然有序
  • sync.Map是线程安全map
package main
import (
    "fmt"
    "sync"
)
func main() {
    var sM sync.Map           // 特点1: 无段初始化,直接声明即可
    sM.Store("id", 1)         // 特点2: Store表示存储,Load表示获取,Delete表示删除。
    sM.Store("name", "leilei")
    fmt.Println(sM.Load("name"))
    sM.Range(func(k,v interface{}) bool {  //特点3: 使用Range配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值,
        fmt.Println(k, v)                  //        需要继续迭代时,返回true,终止迭代返回false。
        return true
    })
}

浮点数判断相等

//使用 == 号判断浮点数,是不可行的,替代方案如下:
func isEqual(f1,f2,p float64) bool {
    // p为用户自定义精度,如:0.00001
   return math.Abs(f1-f2) < p           
}

三、流程控制

  • fallthrough代表不跳出switch,后面的语句无条件执行
  • break用于函数内跳出当前forswitchselect语句的执行
  • continue用于跳出for循环的本次迭代。
  • goto可以退出多层循环, 可以用来统一错误处理
if i == '3' {                      // 初始化与判断写在一起: if a := 10; a == 10
}
if err := Connect(); err != nil {  // 这里的 err!=nil 才是真正的if判断表达式
}

switch num {
   case 1:                         // case 中可以是表达式
      fmt.Println("111")
   case 2:
      fmt.Println("222")
   default:
      fmt.Println("000")
}

// 传统的for循环
for init;condition;post{
}

// for循环简化
var i int
for ; ; i++ {
   if(i > 10){
      break;
   }
}

// 类似while循环
for condition {}

// 死循环
for{
}

// for range:一般用于遍历数组、切片、字符串、map、管道
for k, v := range []int{1,2,3} {
}

四、运算符

算术运算符:    +    -    *    /    %    ++    --    
关系运算符:    ==    !=    <=    >=    <    >    
逻辑运算符:    !    &&    ||
位运算:        &(按位与)    |(按位或)    ^(按位取反)    <<(左移)    >>(右移)
赋值运算符:    =    +=    -=    *=    /=    %=    <<=    >>=    &=    ^=    |=
其他运算符:    &(取地址)    *(取指针值) <-(Go Channel相关运算符)

&     按位与,参与运算的两个数二进制位相与:同时为1,结果为1,否则为0
|     按位或,参与运算的两个数二进制位相或:有一个为1,结果为1,否则为0
^     按位异或:二进位不同,结果为1,否则为0
<<    按位左移:二进位左移若干位,高位丢弃,低位补0,左移n位其实就是乘以2的n次方
>>    按位右移:二进位右移若干位,右移n位其实就是除以2的n次方

五、函数与闭包

  • 支持有名称的返回值;
  • 不支持默认值参数;
  • 不支持重载;
  • 不支持命名函数嵌套,匿名函数可以嵌套;
  • Go函数从实参到形参的传递永远是值拷贝,有时函数调用后实参指向的值发生了变化,是因为参数传递的是指针的拷贝,实参是一个指针变量,传递给形参的是这个指针变量的副本,实质上仍然是值拷贝;
  • Go函数支持不定参数;

闭包实现累加器

func Accumulate(value int) func() int {
    return func() int {                // 返回一个闭包
        value++
        return value
    }
}
func main() {
    accAdd := Accumulate(1)
    fmt.Println(accAdd())                // 2
    fmt.Println(accAdd())                // 3
}

六、面向对象

  • 封装:用struct实现,通过大小写来控制权限
  • 继承:通过嵌套匿名结构体实现继承特性(嵌套有名结构休 实现组合关系)
  • 多态:通过interface实现

子类和父类构造函数的实现方法

type Person struct {
    Name string
    Age int
}

type Student struct {
    Person
    ClassName string //嵌套匿名结构体
}

//构造父类
func NewPerson(name string, age int) *Person {
    return &Person{
        Name: name,
        Age: age,
    }
}

//构造子类
func NewStudent(classname string) *Student {
    p := &Student{}
    p.ClassName = classname
    return p
}


func main() {
    s := NewStudent("一班")
    fmt.Println(s)                        // &{{ 0} 一班}
}

七、interface{}与多态

//interface{} 定义方法
type 接口类型名 interface {
    方法名1(参数列表) 返回值列表
    方法名2(参数列表) 返回值列表
    ...
}
  • 接口实现:接口的方法与实现接口的类型方法格式一致(方法名、参数类型、返回值类型一致)。所有方法都要被实现。
  • 接口赋值:接口本质上是一个指针类型。实现了该接口的struct和子接口,可以赋值给接口。
  • 接口类型做为参数:如果一个函数有个接口作为参数。那么实现了该接口的struct都可以做为此参数。
  • 空接口: Go也能像其它动态语言一样,在数据结构中存储任意类型的数据。
  • 接口嵌套:内部属性属于外部属性

类型断言

  • 如果不清楚当前struct是什么类型,可以采用类型断言,运行时判断。
package main
func main() {
    switch t := areaIntf.(type) {
    case *Rectangle:
        // do something
    case *Triangle:
        // do something
    default:
        // do something
    }
} 

多态

  • 1、多个类型(结构体)可以实现同一个接口。
  • 2、一个类型(结构体)可以实现多个接口。
  • 3、实现接口的类(结构体)可以赋值给接口。
package main
import "fmt"
type Shaper interface {
    Area() float64
}
type Rectangle struct {
    length float64
    width  float64
}
func (r *Rectangle) Area() float64 {
    return r.length * r.width   // 实现 Shaper 接口中的方法
}
func (r *Rectangle) Set(l float64, w float64) {
    r.length = l    //Set 是属于 Rectangle 自己的方法
    r.width = w
}

type Triangle struct { // ==== Triangle ====
    bottom float64
    hight  float64
}
func (t *Triangle) Area() float64 {
    return t.bottom * t.hight / 2
}
func (t *Triangle) Set(b float64, h float64) {
    t.bottom = b
    t.hight = h
}

func main() {
    rect := new(Rectangle)
    rect.Set(2, 3)
    areaIntf := Shaper(rect) //这种方法只能将指针类型的类示例赋值给接口
    fmt.Printf("The rect has area: %f\n", areaIntf.Area())
 
    triangle := new(Triangle)
    triangle.Set(2, 3)
    areaIntf = Shaper(triangle) //这种方法只能将指针类型的类示例赋值给接口
    fmt.Printf("The triangle has area: %f\n", areaIntf.Area())
}

八、文件读取

文件操作.jpg
  • 内置flag包获取配置
  • config.json文件、ini文件、yaml文件、toml文件
  • 万能的viper

1 配置文件读取

package main
//json文件
import "encoding/json"
import "ioutil"
buf, _ := ioutil.ReadFile(path)
myConfig := &MyConfig{}                    //对应json中的k-v项
_ = json.Unmarshal(buf, myConfig)

// ini文件
import "github.com/go-ini/ini"
myConfig := &MyConfig{}                   //对应ini中的k-v项
err := ini.MapTo(myConfig, "config.ini")

// 另一种常用来读ini文件:
import "gopkg.in/ini.v1"
cfg,_ := ini.Load(path)
cfg.Section("").Key("app_mode").String()   //read
cfg.Section("section_name").Key("port").SetValue("8086") //write
cfg.SaveTo(path)

// yaml文件
import "gopkg.in/yaml.v2"
myConfig := &MyConfig{}                   //对应ini中的k-v项
file, err := ioutil.ReadFile("config.yaml")
err = yaml.Unmarshal(file, myConfig)

// toml文件
import "github.com/BurntSushi/toml"
myConfig := &MyConfig{}                   //对应yaml中的k-v项
toml.DecodeFile("config.toml", myConfig)

GO并发

  • 并发主要由切换时间片来实现<同时>运行。
  • 并行是直接利用多核实现多线程的运行。
  • OS线程一般都有固定的栈内存(一般2MB)
  • goroutine(可增长的栈)的栈不是固定的,他可以按需增大和缩小(典型大小2KB, 限制可达1GB)
  • goroutine 奉行通过通信来共享内存(CSP并发模型),而不是共享内存来通信

Goroutine启动一个协程:(如果main协程退出,case协程自动退出)

package main
func case() {
    fmt.Println("case Goroutine!")
}

func main() {
    go case()   // 启动另外一个goroutine去执行case函数
    fmt.Println("main协程 done!")
}

使用sync.WaitGroup启动多个协程

package main
import "sync"
import "fmt"
var wg sync.WaitGroup

func hello(i int) {
    defer wg.Done()  // goroutine结束就登记-1
    fmt.Println("Hello Goroutine!", i)
}
func main() {
    for i := 0; i < 10; i++ {
        wg.Add(1)   // 启动一个goroutine就登记+1
        go hello(i)
    }
    wg.Wait()       // 等待所有登记的goroutine都结束
}

Channel

  1. CSP并发模型,提倡通过通信共享内存而不是通过共享内存而实现通信
  2. 带缓存的channel不带缓存的channel<阻塞通道>
  3. 创建不带缓存的 ch := make(chan interface{})
  4. 创建带缓存的 ch := make(chan interface{}, num int)
  5. 只读的 ch := make(<-chan interface{})
  6. 只写的 ch := make(chan<- interface{})
  7. 发送 ch <- 10
  8. 接收 x := <- ch // 从ch中接收值并赋值给变量x
    <- ch // 从ch中接收值,忽略结果
  9. 关闭 close(ch) // 管道不存取时一定要关闭 【close以后还可以读取数据】
  10. 长度 len(ch) // 求缓冲通道中元素的数量
  11. 容量 cap(ch) // 求缓冲通道的容量

两者的区别

不带缓冲的通道,发送接收操作都会阻塞当前协程
带缓冲的通道,进一次长度 +1,出一次长度 -1,如果长度等于缓冲长度时,再进就会阻塞

管道会出现panic地场景

  1. close以后的管道,再写入。
  2. 重复close
  3. 只读的chan不能close, 编译会报错。
  4. for-range管道,遍历完后,如果chan是关闭的,遍历完数据,正常退出。
  5. for-range管道,遍历完后,如果chan不是关闭的,遍历完数据,程序会行等待,直到出现死锁。

eg1:采用无缓冲通道进行通信

package main
import "fmt"
func recv(c chan int) {
    ret := <-c
    fmt.Println("接收成功", ret)
}
func main() {
    ch := make(chan int)
    go recv(ch)   // 启用goroutine从通道接收值
    ch <- 10
    fmt.Println("发送成功")
}

无缓冲通道上的收发操作都会阻塞,直到另一个goroutine在该通道上执行收发操作,这时值才能发送成功,两个goroutine将继续执行。
相反,如果接收操作先执行,接收方的goroutine将阻塞,直到另一个goroutine在该通道上发送一个值。
使用无缓冲通道进行通信将导致发送和接收的goroutine同步化。因此,无缓冲通道也被称为同步通道

eg2:按顺序实现输入输出

package main
import "fmt"
func main() {
    ch := make(chan int)
    go func() {
        for i := 0; i < 5; i++ {
            ch <- i
            fmt.Println("write num is :", i)             
        }
        close(ch) //这里必须,否则实现交替输出后,main程无法退出
    }()
    for {                      //主协程只负责读取chan中的数据
        if data, ok := <- ch; ok {
            fmt.Println("read num is :", data)
        } else {
            fmt.Println("no data")
            break
        }
    }
}

eg3:交替打印数字和字母

package main
func PrintNums(f chan int, wg *sync.WaitGroup) {
    defer wg.Done()
    for i := 0; i < 5; i++ {
        fmt.Println("num", i)
        f <- 1
        <- f
    }
}

func PrintChars(f chan int, wg *sync.WaitGroup) {
    defer wg.Done()
    for i := 0; i < 5; i++ {
        fmt.Println("char", string('a' + i))
        <- f
        f <- 1
    }
}

func main() {
    runtime.GOMAXPROCS(8)
    flag := make(chan int)

    var wg sync.WaitGroup
    wg.Add(2)
    go PrintNums(flag, &wg)
    go PrintChars(flag, &wg)
    wg.Wait()
}

eg4: Time包的定时器功能

package main
import (
    "fmt"
    "time"
)
func main() {
    // 1.获取ticker对象
    ticker := time.NewTicker(1 * time.Second)
    i := 0
    // 子协程
    go func() {
        for {
            //<-ticker.C
            i++
            fmt.Println(<-ticker.C)
            if i == 5 {
                //停止
                ticker.Stop()
            }
        }
    }()
    time.Sleep(6 * time.Second)
}

runtime

  • runtime.Gosched() 让出CPU时间片,重新等待调度。
  • runtime.Goexit() 退出当前协程
  • runtime.GOMAXPROCS(int) (最大256)

    调度器使用GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行Go代码。
    默认值是机器上的CPU核心数。
    例如在一个8核心的机器上,调度器会把Go代码同时调度到8个OS线程上(GOMAXPROCS是m:n调度中的n)。

  • goroutine与os线程是M:N的关系

你可能感兴趣的:(golang核心编程)