day02 go基础语法


课前调查

运维开发or 纯rd

go语言基础掌握情况

上课风格

  • 代码手敲 ,最好一起
  • 重点知识手敲,敲完群里同步

基础知识的重要性

goland 讲解

  • 先设置一个GOPATH
  • 在GOPATH中创建一个project,不要带中文
  • 到目录下 go mod init
  • 设置两个环境变量
  • GO111MODULE=on
  • GOPROXY=https://goproxy.io,direct
  • 新建go文件,执行

设置 go mod 和 go proxy

  • 新建一个项目 ,目录下 go mod init

内容大纲

  • 01 基础语法
  • 02 基础数据类型
  • 03 字符串
  • 04 符合数据类型
  • 05 map
  • 06 go中的锁
  • 07 流程控制

01 关键字与标识符

标识符

标识符的作用

  • 标识符是用来标识go中的变量名或函数名的

标识符的命名规范

规范

  • 以字母或者开头,后面跟着字母、数字、

合法标识符举例

  • a_b
  • _a
  • ab_23d

非法标识符举例

  • 1ab 以数字开头
  • case go里面的关键字
  • a+b 带了运算符

空白标识符

  • _ 本身就是一个特殊的标识符,被称为空白标识符
  • 用来导入包,只初始化init函数,不使用内部方法
  • 函数返回值不使用变量,多变量返回

25个关键字

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

## 36个 预定义标识符
- 基本类型的名称
- 一些基本的内置函数

```shell script

append  bool    byte    cap close   complex complex64   complex128  uint16
copy    false   float32 float64 imag    int int8    int16   uint32
int32   int64   iota    len make    new nil panic   uint64
print   println real    recover string  true    uint    uint8   uintptr

02 操作符

算术运算符

        • / % ++ --
  • 代码如下 ```go package main

import "fmt"

func main() {

var a = 21
var b = 10
var c int
fmt.Printf("[初始化的值是什么: a=%d b=%d c=%d]\n", a, b, c)
// 加法
c = a + b
fmt.Printf("[加法操作: a+b=%d]\n", c)
// 减法
c = a - b
fmt.Printf("[减法操作: a-b=%d]\n", c)
// 乘法
c = a * b
fmt.Printf("[乘法操作: a*b=%d]\n", c)
// 减法
c = a / b
fmt.Printf("[除法操作: a/b=%d]\n", c)
// 取余
c = a % b
fmt.Printf("[减法操作: a对b取余=%d]\n", c)
// 自增
c++
fmt.Printf("[c自增: c++=%d]\n", c)

// 自减
c--
fmt.Printf("[c自减: c++=%d]\n", c)
/*
    [初始化的值是什么: a=21 b=10 c=0]
    [加法操作: a+b=31]
    [减法操作: a-b=11]
    [乘法操作: a*b=210]
    [除法操作: a/b=2]
    [减法操作: a对b取余=1]
    [c自增: c++=2]
    [c自减: c++=1]
*/

}

## 关系运算符
- `> < == !=` 
```go
package main

import "fmt"

func main() {
    var a = 10
    var b = 5

    if a > b {
        fmt.Printf("a>b")
    }
}

逻辑运算符

  • && 逻辑 AND 运算符
  • || 逻辑 OR 运算符
  • ! 逻辑 NOT 运算符 ```go package main

import "fmt"

func main() { var a = true var b = false if a && b { fmt.Printf("[ab 同为true才为true]\n") } if a || b { fmt.Printf("[a b 中有一个为true 就是true]\n") } if !(a && b) { fmt.Printf("[a b同为true再取非为true]\n") } }

# 位运算符
- 与操作 & 二进制位同为1 结果为1 
- 或操作 | 二进制位至少一个为1 结果为1 
- 异或操作 ^  二进制位不同时 结果为1
- 左移运算符 <<  左移n位就是乘以2的n次方
- 右移运算符 >> 右移n位就是除以2的n次方
```go
package main

import "fmt"

func main() {

    var a uint = 60
    var b uint = 13
    var c uint = 0
    fmt.Printf("[a=%d 二进制=%08b]\n", a, a)
    fmt.Printf("[b=%d 二进制=%08b]\n", b, b)

    c = a & b
    fmt.Printf("[与操作][%d=%d&%d][%08b=%08b & %08b ]\n", c, a, b, c, a, b)

    c = a | b
    fmt.Printf("[或操作][%d=%d | %d][%08b=%08b | %08b ]\n", c, a, b, c, a, b)

    c = a ^ b
    fmt.Printf("[异或操作][%d=%d^%d][%08b=%08b ^ %08b ]\n", c, a, b, c, a, b)

    c = a << b
    fmt.Printf("[左移放大操作][%d=%d&%d][%08b=%08b & %08b ]\n", c, a, b, c, a, b)

    c = a >> b
    fmt.Printf("[右移动缩小操作][%d=%d&%d][%08b=%08b & %08b ]\n", c, a, b, c, a, b)

    /*
        [a=60 二进制=00111100]
        [b=13 二进制=00001101]
        [与操作][12=60&13][00001100=00111100 & 00001101 ]
        [或操作][61=60 | 13][00111101=00111100 | 00001101 ]
        [异或操作][49=60^13][00110001=00111100 ^ 00001101 ]
        [左移放大操作][491520=60&13][1111000000000000000=00111100 & 00001101 ]
        [右移动缩小操作][0=60&13][00000000=00111100 & 00001101 ]
    */
}

赋值运算符

  • += -= *= ... ```go package main

import "fmt"

func main() { var a = 21 var c int

c = a
fmt.Println(c, a)
c += a
fmt.Println(c, a)
c *= a
fmt.Println(c, a)
/*
    21 21
    42 21
    882 21
*/

}

## 关于指针的运算符
- & 返回变量存储地址 可以使用%p去打印地址
- * 指针变量 `var a *int`
    1. 用来定义变量,类型是指针的
    2. 可以对指针地址再赋值或取值

```go
package main

import "fmt"

func main() {
    var a = 4
    var ptr *int
    fmt.Printf("[a 变量类型为 %T]\n", a)
    fmt.Printf("[ptr 变量类型为 %T]\n", ptr)

    ptr = &a
    fmt.Printf("[a 变量的值为 %d]\n", a)
    fmt.Printf("[*ptr 的值为%d ]\n", *ptr)
    fmt.Printf("[ptr 的值为%v ]\n", ptr)
    fmt.Printf("[a 的指针地址为%p ]\n", &a)

    /*
        [a 变量类型为 int]
        [ptr 变量类型为 *int]
        [a 变量的值为 4]
        [*ptr 的值为4 ]
        [ptr 的值为0xc00001a098 ]
        [a 的指针地址为0xc00001a098 ]
    */
}

03 变量、常量、枚举

变量

方法一:使用var 声明变量并初始化

  • 关键字var可以使用结构类型声明变量,并初始化为零值,举例
    var p int
  • 关键字 var 创建了类型为 int 且名为 p 的变量,p被称作类型 int 的一个实例(instance)。

  • 当声明变量时,这个变量对应的值总是会被初始化。

  • 使用var关键字用零值初始化,对数值类型来说,零值是 0;对字符串来说,零值是空字符串;对布尔类型,零值是 false。
  • 多变量声明 var a,b,c,d string

var 等号 :使用自定义数据初始化

  • var p int = 10

var特点:可以在函数外部使用,可以声明初始化全局变量

  • 举例使用 var=XXXX 初始化全局缓存

方法二:使用短变量声明操作符(:=) 初始化

特点1:使用自定义值初始化

  • p:=10 #### 特点2:不可以在函数外部使用,不可以声明初始化全局变量
  • 可以试一试 .\compute.go:8:1: syntax error: non-declaration statement outside function body

常量 const定义的

  • 定义在全局的位置,函数外部
  • 常量必须赋值,否则报 Missing value in const declaration ```go const Pi = 3.1415 const Max float64 = 1000.11111 const cname = "go编程" const dname
## 枚举
- go中没有enum这么一个关键字来定义,而是依靠const和iota
```go
package main

import "fmt"

const (
    a = iota
    b
    c
    d
)

func main() {

    fmt.Println(a, b, c, d)
}
  • 自定义类型 ```go package main

import "fmt"

type MyType int

const ( T1 MyType = iota T2 T3 T4 )

func main() {

fmt.Println(T1, T2, T3, T4)

}

- _ 可以跳过值
```go
package main

import "fmt"

type MyType int

const (
    T1 MyType = iota
    T2
    _
    _
    T3
    T4
)

func main() {

    fmt.Println(T1, T2, T3, T4) //0 1 4 5

}
  • 中间插值 iota 会被覆盖掉 不再继续自增。但是用另一个 iota 接一下,又会继续自增 ```go package main

import "fmt"

type MyType int

const ( a = iota b = 5 c d = iota e )

func main() {

fmt.Println(a, b, c, d, e) //0 5 5 3 4

}

- 位掩码表达式
```go
package main

import "fmt"

type MyType int

const (
    T1 MyType = 1 << iota
    T2
    _
    _
    T3
    T4
)

func main() {

    fmt.Println(T1, T2, T3, T4) //1 2 16 32


}
  • 定义数量级 ,比如说字节大小 ```go package main

import "fmt"

type MyType int

const ( _ = iota KB float64 = 1 << (10 * iota) MB GB TB )

func main() {

fmt.Println(KB, MB, GB, TB) //1024 1.048576e+06 1.073741824e+09 1.099511627776e+12
fmt.Println(1 << 10)

}

## 04 变量作用域
- 在 Golang 中,变量作用域分为 :本地局部变量、全局变量、参数变量

### 本地局部变量
- 在函数、if 、for等中定义的变量,
- 生效范围:定义在函数里面则在整个函数范围有效; 定义在语义块,则在整个语义块生效
```go
package main

import "fmt"

func T1() string {
    str1 := "函数T1中的字符串变量"
    fmt.Println(str1)
    return str1
}

func main() {
    // .\compute.go:12:14: undefined: str1
    // 试图引用在函数里面定义的变量
    //fmt.Println(str1)

    for i := 0; i < 10; i++ {
        fmt.Println(i)
    }
    // 报错是这个.\compute.go:18:14: undefined: i
    // 试图引用在for里面定义的变量
    //fmt.Println(i)

    if str := T1(); str == "" {
        fmt.Println("[函数返回为空]")
    }
    // 
    // 报错是这个.\compute.go:26:14: undefined: str
    // 试图引用在if里面定义的变量
    //fmt.Println(str)
}

全局变量 var xxx

参数变量

bool

  • 布尔型的值只可以是常量 true 或者 false

整数型

  • 有符号和无符号类型的整数运算

```shell script 数据类型 取值范围 int8 -128 ~ 127 uint8 0 ~ 255 int16 -32768 ~ 32767 uint16 0 ~ 65535 int32 -2147483648 ~ 2147483647 uint32 0 ~ 4294967295 int64 -9223372036854775808 ~ 9223372036854775807 uint64 0 ~ 18446744073709551615

- int8、int16、int32和int64 分别对应 xbit大小的整数 
- x/8就是字节大小
    - math.MinIntxx 代表取值范围
    - unsafe.SizeOf()打印字节大小
```go
package main

import (
    "fmt"
    "math"
    "unsafe"
)

func main() {

    var a int8
    var b int16
    var c int32
    var d int64

    fmt.Printf("[整型:%T 取值范围是:%d ~ %d ][字节大小:%d]\n", a, math.MinInt8, math.MaxInt8, unsafe.Sizeof(a))
    fmt.Printf("[整型:%T 取值范围是:%d ~ %d ][字节大小:%d]\n", b, math.MinInt16, math.MaxInt16, unsafe.Sizeof(b))
    fmt.Printf("[整型:%T 取值范围是:%d ~ %d ][字节大小:%d]\n", c, math.MinInt32, math.MaxInt32, unsafe.Sizeof(c))
    fmt.Printf("[整型:%T 取值范围是:%d ~ %d ][字节大小:%d]\n", d, math.MinInt64, math.MaxInt64, unsafe.Sizeof(d))
    /*
         [整型:int8 取值范围是:-128 ~ 127 ][字节大小:1]
        [整型:int16 取值范围是:-32768 ~ 32767 ][字节大小:2]
        [整型:int32 取值范围是:-2147483648 ~ 2147483647 ][字节大小:4]
        [整型:int64 取值范围是:-9223372036854775808 ~ 9223372036854775807 ][字节大小:8]
    */
}
  • 根据平台不同 ,大小不同的int var sp int 32bit or 64bit

整数型互相转换

  • 低转高没问题,高转低会截断 ```go package main

import "fmt"

func main() {

var b uint16
b = 500
// c是往小转
c := uint8(b)
// d是往大转
d := uint32(b)
fmt.Println(b)
fmt.Printf("[c=uint16-->uint8 :%d]\n", c)
fmt.Printf("[d=uint16-->uint32 :%d]\n", d)

}

# 浮点型
- Go语言中提供了两种精度的浮点数 float32 和 float64。
- float32,也即我们常说的单精度,存储占用4个字节,也即4*8=32位,其中1位用来符号,8位用来指数,剩下的23位表示尾数
![image](./pic/float32.png)
- float64,也即我们熟悉的双精度,存储占用8个字节,也即8*8=64位,其中1位用来符号,11位用来指数,剩下的52位表示尾数
![image](./pic/float64.png)

- 精度主要取决于尾数部分的位数
- float32的精度只能提供大约6个十进制数(表示后科学计数法后,小数点后6位)的精度
- float64的精度能提供大约15个十进制数(表示后科学计数法后,小数点后15位)的精度
```go
package main

import "fmt"

func main() {

    var f1, f2 float32
    f1 = 10000018
    f2 = 100000018
    f11 := f1 + 1
    f12 := f2 + 1
    fmt.Printf("[f1:%v %T]\n", f1, f1)
    fmt.Printf("[f11:%v %T]\n", f11, f11)
    fmt.Printf("[f2:%v %T]\n", f2, f2)
    fmt.Printf("[f12:%v %T]\n", f12, f12)

    fmt.Println(f1 == f11)
    // f2=f12代表f2刚好达到了float32精度上限
    fmt.Println(f2 == f12)
}

指针

基础概念

  • 每一个变量都会分配一块内存,数据保存在内存中
  • 内存有一个地址,就像门牌号,通过这个地址就可以找到里面存储的数据。
  • *两个作用

    • 1.定义指针类型的变量
    • 2.取指针变量内存地址的值
  • &的作用是获取变量的内存地址 ```go package main

import "fmt"

func main() {

var s1 = "hello"

var s1p = &s1 fmt.Printf("[%T %v]\n", s1, s1) fmt.Printf("[%T %v]\n", s1p, s1p) fmt.Printf("[%T %v]\n", s1p, s1p) }

## 在oop语言中如python java是不需要花太多时间操作指针的 ,为何go还需要 ,主要体现在下面三个地方
- Go语言中除了map、slice、chan外,其他类型在函数参数中都是值传递 
- Go语言不是面向对象的语言,很多时候实现结构体方法时需要用指针类型实现引用结构体对象 (后面再讲)
- 指针也是一个类型,在实现接口interface时,结构体类型和其指针类型对接口的实现是不同的 (后面再讲)


## 值传递和引用传递

## 浅拷贝和深拷贝
- 我们在开发中会经常的把一个变量复制给另一个变量,那么这个过程,可能是深浅拷贝


### 定义和区别
- 浅拷贝的是数据地址,只复制指向的对象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化。释放内存地址时,同时释放内存地址。
- 深拷贝的是数据本身,创造一个样的新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值。既然内存地址不同,释放内存地址时,可分别释放。
- 是否真正获取(复制)对象实体,而不是引用。

### go中值类型的数据,默认都是深拷贝
- int、float、string、bool、array、struct

### go中引用类型的数据,默认都是浅拷贝
- slice、map、function、chan

## 上述的目的 :为了解释为何还需要指针

### go中函数传参默认值拷贝,如何解决下面的问题 
- 函数外使用函数内处理后的变量呢?只能通过返回新变量吗? 

- 下面这个例子

```go
package main

import "fmt"

func main() {
    i := 1
    fmt.Printf("[初始化之后的值:%d]\n", i)
    add(i)
    fmt.Printf("[调用完add函数后的值:%d]\n", i)
}

func add(num int) {
    fmt.Printf("[传入add函数参数的值:%d]\n", num)
    num++
    fmt.Printf("[add函数增加后的值:%d]\n", num)
}
  • i在执行前后没有变化
  • 如果希望被函数调用后,i的值产生变化,f函数的参数就应该改为 *int 类型 ```go package main

import "fmt"

func main() { i := 1 fmt.Printf("[初始化之后的值:%d]\n", i) add(&i) fmt.Printf("[调用完add函数后的值:%d]\n", i) }

func add(num int) { fmt.Printf("[传入add函数参数的值:%d]\n", num) num++ fmt.Printf("[add函数增加后的值:%d]\n", num) }

# 组成字符串的字符
- 字符串中的每一个元素叫做“字符”

## Go语言的字符有以下两种
- 第一种是 uint8类型,也叫byte型,代表ASCII码的一个字符
- 第二种是 rune类型,代表一个UTF-8的字符,当我们需要处理中文、日文等其他复合字符时,需要rune,其实等价于int32
- 举例说明两种字符的区别
```go
package main

import "fmt"

func main() {
    //
    var ch1 byte = 'a'
    var ch2 = 'a'
    var ch3 = '你'
    fmt.Printf("[字符 指定byte类型 指定ASCII码: %c id:%v 实际类型:%T]\n", ch1, ch1, ch1)
    fmt.Printf("[字符 没有显示指定byte类型 默认UTF-8编码: %c id:%v 实际类型:%T]\n", ch2, ch2, ch2)
    fmt.Printf("[字符 中文 : %c id:%v 实际类型:%T]\n", ch3, ch3, ch3)
    /*
        [字符 指定byte类型 指定ASCII码: a id:97 实际类型:uint8]
        [字符 没有显示指定byte类型 默认UTF-8编码: a id:97 实际类型:int32]
        [字符 中文 : 你 id:20320 实际类型:int32]
    */
}

字符集

字符集用来做什么

  • 字符集是为每个字符分配一个唯一的ID
  • 在同一个字符集内,字符的ID是唯一的,不同字符集ID可能不同

UTF-8 是编码规则 或者说是Unicode的一种实现

  • UTF-8 将Unicode中的字符ID以某种方式进行编码
  • 变长的编码规则:1-4字节,具体规则:
    • 0xxxxx表示 0~127 代表ascii
    • 128 到0x10ffff表示其他字符

go语言里的字符串的内部实现使用UTF8编码. 默认rune类型

字符串是什么?

定义字符串 双引号和 反引号

golang中单引号 双引号 反引号代表的含义

单引号 在golang中表示一个字符

  • var ch = 'a' 代表utf8编码的a
  • var ch byte = 'a' 代表ascii编码的a
  • var ch = 'abc' more than one character in rune literal

双引号 go创建字符串的

  • 支持转义的 \n \t \xxx
  • 但是不能引用多行

反引号

  • 不支持转义
  • 支持换行
  • 主要用来创建原生的字符串
    • 复杂的json
    • promql
  • 举例 ```go package main

import "fmt"

func main() { // jsonstr := { "region":"bj", "ids":[1,2,3,4] } promql := sum(rate(api_qps{code=~"2xx"}[1m]))*100 fmt.Println(jsonstr)

}

## 计算字符串长度
- len()函数只能表示字符串 ASCII字符的个数或者字节长度
- 如何真实打印字符的个数 `utf8.RuneCountInString`

```go
package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {

    ch1 := "lufei golang!!"
    ch2 := "路飞教育"
    ch3 := "lu飞a哦"
    fmt.Printf("[字符串:%v 字节大小或者字符个数:%d 真实字符个数:%d]\n", ch1, len(ch1), utf8.RuneCountInString(ch1))
    fmt.Printf("[字符串:%v 字节大小或者字符个数:%d 真实字符个数:%d]\n", ch2, len(ch2), utf8.RuneCountInString(ch2))
    fmt.Printf("[字符串:%v 字节大小或者字符个数:%d 真实字符个数:%d]\n", ch3, len(ch3), utf8.RuneCountInString(ch3))

}

字符串遍历

  • 如果是ASCII字符:直接使用下标遍历
  • 如果是unicode字符遍历:使用for range
package main

import "fmt"

func main() {

    ch3 := "lu飞a哦"

    // 下标遍历
    for i := 0; i < len(ch3); i++ {
        fmt.Printf("[ascii:%c %d]\n", ch3[i], ch3[i])
    }
    //  for range遍历
    for _, i := range ch3 {
        fmt.Printf("[unicode:%c %d]\n", i, i)
    }
    /*
        [ascii:l 108]
        [ascii:u 117]
        [ascii:é 233]
        [ascii:£ 163]
        [ascii:  158]
        [ascii:a 97]
        [ascii:å 229]
        [ascii:  147]
        [ascii:¦ 166]
        [unicode:l 108]
        [unicode:u 117]
        [unicode:飞 39134]
        [unicode:a 97]
        [unicode:哦 21734]

    */

}

字符串拼接符 +

  • 支持换行,+拼接 ```go package main

import "fmt"

func main() {

s1 := "http://"
s2 := "localhost:8080"
s3 := s1 + s2
fmt.Println(s3)
s4 := "http://localhost:8080/api/v1" +
    "/login"
fmt.Println(s4)

}

## 字符串修改:通过 []byte和string转换 创建新的字符串达到
- 举例 8080 改为8081
```go
package main

import "fmt"

func main() {

   s2 := "localhost:8080"
   fmt.Println(s2)
   // 强制类型转换 string to []byte
   sByte := []byte(s2)
   // 下标修改
   sByte[len(sByte)-1] = '1'
   // 强制类型转换 []byte to string  
   s3 := string(sByte)
   fmt.Println(s3)

}

strings包

判断是否存在某个字符或子串

  • 查找子串 fmt.Println(strings.Contains("localhost:8080", "8080"))
  • 任意字符 fmt.Println(strings.ContainsAny("localhost 8080", "a b"))
  • 查找rune fmt.Println(strings.ContainsRune("你们好", '你'))

字符串分割(切分)为[]string

  • splitAfter 保留sep
  • splitN 结果切片的长度为n,没切完就不切了
  • Split = splitN(-1) SplitAfter = SplitAfterN(-1)
package main

import (
    "fmt"
    "strings"
)

func main() {
    // 服务标识
    s1 := "inf.bigdata.kafka"
    // restful接口
    s2 := "localhost:8080/api/v1/host/1"
    ss1 := strings.Split(s1, ".")
    ss2 := strings.SplitAfter(s1, ".")

    ps1 := strings.Split(s2, "/")
    psn := strings.SplitN(s2, "/", 2)
    fmt.Printf("[切割服务标识]%v\n", ss1)
    fmt.Printf("[切割服务标识][SplitAfter]%v\n", ss2)
    fmt.Printf("[切割uri][]%v\n", ps1)
    fmt.Printf("[切割uri][SplitN]%v\n", psn)
    /*
        [切割服务标识][inf bigdata kafka]
        [切割服务标识][SplitAfter][inf. bigdata. kafka]
        [切割uri][][localhost:8080 api v1 host 1]
        [切割uri][SplitN][localhost:8080 api/v1/host/1]

    */
}

字符串是否有某个前缀或后缀

package main

import (
    "fmt"
    "strings"
)

func main() {
    // 服务标识
    s1 := "inf.bigdata.kafka"
    // restful接口
    fmt.Println(strings.HasPrefix(s1, "inf"))
    fmt.Println(strings.HasSuffix(s1, "kafka"))
    fmt.Println(strings.HasSuffix(s1, ""))
}

字符串格式化输出 fmt.Sprintf

```shell script %c 单一字符 %T 动态类型 %v 本来值的输出 %+v 字段名+值打印 %d 十进制打印数字 %p 指针,十六进制 %f 浮点数 %b 二进制 %s string

- 实际举例: 拼接报警信息、拼接uri
```go
package main

import (
    "fmt"
    "time"
)

func main() {
    // 服务标识
    want := `
[报警触发类型 :%s]
[报警名称 :%s]
[级别 :%d]
[机器ip列表 :%s]
[表达式 :%s]
[告警次数 :%d]
[触发时间:%s]
    `
    alarmContent := fmt.Sprintf(
        want,
        "普罗米修斯",
        "支付接口qps大于1000",
        1,
        "1.1.1.1,2.2.2.2",
        `sum(rate(login_qps[1m]))>100`,
        2,
        time.Unix(time.Now().Unix(), 0).Format("2006-01-02 15:04:05"),
    )
    fmt.Println(alarmContent)

    /*
         [报警触发类型 :普罗米修斯]
        [报警名称 :支付接口qps大于1000]
        [级别 :1]
        [机器ip列表 :1.1.1.1,2.2.2.2]
        [表达式 :sum(rate(login_qps[1m]))>100]
        [告警次数 :2]
        [触发时间:2021-07-11 14:55:20]
    */
}

字符串修剪

  • 举例
  • TrimLeft会去掉连续的cutset
  • TrimPrefix会去掉的单一的 ```go package main

import ( "fmt" "strings" "unicode" )

func main() {

x := "@@@@abc_hello_xxx@"
fmt.Println(strings.Trim(x, "@"))
fmt.Println(strings.TrimLeft(x, "@"))
fmt.Println(strings.TrimRight(x, "@"))
fmt.Println(strings.TrimSpace(x))
fmt.Println(strings.TrimPrefix(x, "@@@"))
fmt.Println(strings.TrimSuffix(x, "@"))

f := func(r rune) bool {
    // r如果是汉字返回true
    return unicode.Is(unicode.Han, r)
}
fmt.Println(strings.TrimFunc("你好啊abc啊啊", f))

// 对比一下TrimLeft和 TrimPrefix 的区别
x = "@a@ahello_xxx@"
fmt.Printf("[TrimLeft:%v]\n", strings.TrimLeft(x, "@a"))
fmt.Printf("[TrimPrefix:%v]\n", strings.TrimPrefix(x, "@a"))

}

## 字符串连接 join
```go
package main

import (
    "fmt"
    "strings"
)

func main() {

    baseUri := "http://localhost:8080/api/v1/query?"
    args := strings.Join([]string{"name=lufei", "id=1", "env=online"}, "&")
    fulluri := baseUri + args
    fmt.Println(fulluri)
}

字符串拼接的优化

image

使用 string.builder

package main

import (
    "fmt"
    "strings"
)

func main() {

    ss := []string{
        "A",
        "说",
        "我要",
        "升职加薪",
    }
    var b strings.Builder
    for _, s := range ss {
        b.WriteString(s)
    }
    fmt.Println(b.String())
}

数组

定义

  • 数组是用来存储相同唯一类型的,一组已编号且长度固定的序列

声明 && 初始化数组

  • 数组声明需要指定元素类型及元素个数,语法格式如下:
    var arr_name [num] arr_type
    
  • 声明长度为5 类型为string的数组 var arr1 [5]string

  • 声明并初始化,不写长度用...代替

  • 声明并初始化,写长度 ```go package main

import "fmt"

func main() {

var arr1 [5]string
fmt.Println(arr1)
arr1[1] = "golang"
fmt.Println(arr1)

var arr2 = [...]int{1, 2, 3}
fmt.Println(arr2)

}

## 访问数组元素
- 数组元素可以通过索引(位置)来读取和赋值
- 索引从 0 开始
```go
package main

import "fmt"

func main() {

    var arr1 [10]int
    // 根据索引赋值
    for i := 0; i < 10; i++ {
        arr1[i] = i

    }
    // 根据索引查询数据
    for i := 0; i < 10; i++ {
        fmt.Println(arr1[i])

    }
}
  • 索引越界 编译检查报错 invalid array index 20 (out of bounds for 10-element array)
  • fmt.Println(arr1[20])
  • 索引越界 panic ,用个变量逃避编译检查
    • panic: runtime error: index out of range [20] with length 10
    • 用一个变量表示索引,访问

指针数组

  • 数组的元素除了是某个类型外,还可以是某个类型的指针
  • new函数返回一个TYPE 类型的数据结构划分内存并执行默认的初始化操作,然后返回这个数据对象的指针 ```go package main

import "fmt"

func main() {

var arr1 [5]*int
// 根据索引赋值
arr1[0] = new(int)
arr1[1] = new(int)
arr1[2] = new(int)
arr1[3] = new(int)
arr1[4] = new(int)
fmt.Println(arr1)
*arr1[0] = 10
*arr1[1] = 2
fmt.Println(arr1)
for i := 0; i < len(arr1); i++ {
    fmt.Printf("[索引:%d 值是: %d]\n", i, *arr1[i])
}
/*
   [0xc00001a098 0xc00001a0b0 0xc00001a0b8 0xc00001a0c0 0xc00001a0c8]
   [0xc00001a098 0xc00001a0b0 0xc00001a0b8 0xc00001a0c0 0xc00001a0c8]
   [索引:0 值是: 10]
   [索引:1 值是: 2]
   [索引:2 值是: 0]
   [索引:3 值是: 0]
   [索引:4 值是: 0]
*/

}

- 只声明不初始化 ,必须用new,空值 panic: runtime error: invalid memory address or nil pointer dereference

- 普通数据深拷贝的例子
    - 判定依据就是 新老对象的指针%p &var是一致的,说明是浅拷贝,否则是深拷贝
```go
package main

import "fmt"

func main() {

    arr1 := [2]int{1, 2}
    var arr2 [2]int
    arr2 = arr1
    fmt.Printf("[%v %p]\n", arr1, &arr1)
    fmt.Printf("[%v %p]\n", arr2, &arr2)
    arr2[1] = 20
    fmt.Printf("[%v %p]\n", arr1, &arr1)
    fmt.Printf("[%v %p]\n", arr2, &arr2)

}
  • 两个数组指针直接复制
    • 原因是内部存放的是指针,指向同一块地址,直接赋值,内容都一样,看起来是浅拷贝
    • 但是其实数据copy是深拷贝
package main

import "fmt"

func main() {

    var arr1 [3]*string
    arr2 := [3]*string{new(string), new(string), new(string)}

    *arr2[0] = "k1"
    *arr2[1] = "k2"
    *arr2[2] = "k3"
    arr1 = arr2
    fmt.Println(arr1)
    fmt.Println(arr2)
    for i := 0; i < 3; i++ {
        fmt.Printf("[arr1 :%d :%v %v]\n", i, *arr1[i], arr1[i])
        fmt.Printf("[arr2 :%d :%v %v]\n", i, *arr2[i], arr2[i])
    }
    fmt.Printf("[%v %p]\n", arr1, &arr1)
    fmt.Printf("[%v %p]\n", arr2, &arr2)
    /*
        [0xc00004c240 0xc00004c250 0xc00004c260]
       [0xc00004c240 0xc00004c250 0xc00004c260]
       [arr1 :0 :k1 0xc00004c240]
       [arr2 :0 :k1 0xc00004c240]
       [arr1 :1 :k2 0xc00004c250]
       [arr2 :1 :k2 0xc00004c250]
       [arr1 :2 :k3 0xc00004c260]
       [arr2 :2 :k3 0xc00004c260]
    */
}

数组的特点

  • 固定长度:这意味着数组不可增长、不可缩减。想要扩展数组,只能创建新数组,将原数组的元素复制到新数组。
  • 内存连续:这意味可以在缓存中保留的时间更长,搜索速度更快,是一种非常高效的数据结构,同时还意味着可以通过数值的方式(arr[index])索引数组中的元素。
  • 固定类型:固定类型意味着限制了每个数组元素可以存放什么样的数据,以及每个元素可以存放多少字节的数据。

数组是值类型,就是深拷贝

  • 举例 ```go package main

import "fmt"

func main() {

arr1 := [2]int{1, 2} var arr2 [2]int arr2 = arr1 fmt.Printf("[%v %p]\n", arr1, &arr1) fmt.Printf("[%v %p]\n", arr2, &arr2) arr2[1] = 20 fmt.Printf("[%v %p]\n", arr1, &arr1) fmt.Printf("[%v %p]\n", arr2, &arr2)

}

## 所有的值类型变量在赋值和作为参数传递时都将产生一次复制

## 把数组传递给函数 数组指针
- 数组是一个值类型,所有的值类型变量在赋值和作为参数传递时都将产生一次复制操作
- 从内存和性能上来看,在函数间传递数组是一个开销很大的操作。因为无论这个数组有多长,都会完整复制,并传递给函数
- 数组指针只需要很小传递

```go
package main

import (
    "fmt"
    "unsafe"
)

func bigArrPoint(arr *[1e6]int64) {
    fmt.Printf("[数组指针复制:大小:%d字节]\n", unsafe.Sizeof(arr))
}

func bigArr(arr [1e6]int64) {
    fmt.Printf("[数组复制:大小:%d字节]\n", unsafe.Sizeof(arr))
}

func main() {

    var arr [1e6]int64
    bigArr(arr)
    bigArrPoint(&arr)
}

多维数组

  • 多维数组的典型用例是平面坐标(二维数组)和三维坐标(三维数组)
  • Golang 的数组本身只有一个维度,但是我们可以组合多个数组从而创建出多维数组 ```go package main

import ( "fmt" )

func main() {

arr1 := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
fmt.Println(arr1)
fmt.Println(arr1[3][1])

}

# 切片
- 切片是围绕动态数据来构建的
- 数组一旦创建就不能更改长度,但是切片可以按需求自动增长和缩小
- 增长是使用内置的append函数来实现的
- 缩小通过对切片的再次切片来实现

## 切片内部实现

![image](./pic/slice.png)

## 声明和初始化

> var 直接声明 
```go
package main

import "fmt"

func main() {
    var s1 []int
    fmt.Println(s1)
    s1 = append(s1, 1)
    s1 = append(s1, 2)
    fmt.Println(s1)

    var s2 = []int{1, 2, 3}
    fmt.Println(s2)
}

使用make

  • make([]int,长度,容量)
  • 以类型0值+长度的个数填充slice
  • 容量不填默认=长度,如果填了不能小于长度
package main

import "fmt"

func main() {

    // 使用make初始化一个长度为0的slice
    s1 := make([]int, 0)
    s1 = append(s1, 1)
    s1 = append(s1, 2)
    s1 = append(s1, 3)
    fmt.Println(s1)

    // 使用make 初始化一个长度为5 容量为5的slice
    s2 := make([]int, 5, 5)
    s2 = append(s2, 1)
    s2 = append(s2, 2)
    s2 = append(s2, 3)
    fmt.Println(s2) // [0 0 0 0 0 1 2 3]
}

new和make对比

  • 简单说 new只分配内存,make用于slice,map,和channel的初始化。
  • 对比表格
函数名 适用范围 返回值 填充值
new new可以对所有类型进行内存分配 new 返回指针 new 填充零值
make make 只能创建类型(slice map channel) make 返回引用 make 填充非零值

通过切片创建新的切片

  • 语法如下 ```go slice[start:end:cap]
- 其中 start 表示从 slice 的第几个元素开始切
- end 控制切片的长度(end-start)
- cap 控制切片的容量 ,如果没有给定 cap ,slice的长度值,则表示切到底层数组的最尾部
- 新切片的长度  = end-start
- 新切片的容量  = cap-start
- cap不能大于原切片的cap
```go
package main

import "fmt"

func main() {

    s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
    fmt.Printf("[s1][值:%v][新切片的长度=%d 容量=%d]\n", s1, len(s1), cap(s1))
    s2 := s1[2:6]
    fmt.Printf("[s2][从索引为2 就是3号往后切4个元素][值:%v][新切片的长度=%d 容量=%d]\n", s2, len(s2), cap(s2))
    s3 := s1[5:]
    fmt.Printf("[s3][从索引为5 就是6号 切到最后][值:%v][新切片的长度=%d 容量=%d]\n", s3, len(s3), cap(s3))
    s4 := s1[:4]
    fmt.Printf("[s4][从开头切4个元素][值:%v][新切片的长度=%d 容量=%d]\n", s4, len(s4), cap(s4))
    s5 := s1[:]
    fmt.Printf("[s5][复制整个切片][值:%v][新切片的长度=%d 容量=%d]\n", s5, len(s5), cap(s5))

    s6 := s1[2:6:8]
    fmt.Printf("[s6][从索引为2 就是3号往后切4个元素][值:%v][新切片的长度=%d 容量=%d]\n", s6, len(s6), cap(s6))

    s7 := s1[2:6:9]
    fmt.Printf("[s7][从索引为2 就是3号往后切4个元素][值:%v][新切片的长度=%d 容量=%d]\n", s7, len(s7), cap(s7))

}

改变某一个切片的元素

  • 说明所有切片的值都变了
    fmt.Printf("[改变某一个切片的元素。看看其他切片会受影响吗]\n")
      s6[1] = 80
      fmt.Println(s1)
      fmt.Println(s2)
      fmt.Println(s3)
      fmt.Println(s4)
      fmt.Println(s5)
      fmt.Println(s6)
      fmt.Println(s7)
    
    image

切片是引用类型,浅拷贝

package main

import "fmt"

func main() {
   a1 := []int{1, 2, 3}
   a2 := a1
   a2[0] = 10
   fmt.Println(a1, a2)
   a1[2] = 30
   fmt.Println(a1, a2)
}

使用copy函数进行深copy

  • 它表示把切片 src 中的元素拷贝到切片 dst 中
  • 返回值为拷贝成功的元素个数
  • 如果 src 比 dst 长,就截断
  • 如果 src 比 dst 短,则只拷贝 src 那部分: ```go package main

import "fmt"

func main() { a1 := []int{1, 2, 3} a2 := make([]int, 3) a3 := make([]int, 5) copy(a2, a1) copy(a3, a1)

a2[0] = 10
fmt.Println(a1, a2)
a1[2] = 30
fmt.Println(a1, a2)
fmt.Println(a3)

}

## 切片作为参数传给函数
- 虽然函数传参是指传递,应该是深拷贝
- 但是slice属于引用类型,浅拷贝,在函数内部的修改还是会影响外部
```go
package main

import "fmt"

func showSlice(s []int) {
    fmt.Printf("[传入的切片为:%v]\n", s)
    s[2] = 30
}

func main() {
    a1 := []int{1, 2, 3}
    showSlice(a1)
    fmt.Printf("[showSlice处理后的切片为:%v]\n", a1)
}

切片遍历

for range 遍历

  • 返回索引和值
  • range 创建了每个元素的副本,而不是直接返回对该元素的引用 ```go package main

import "fmt"

func main() { a1 := []int{10, 20, 30} // 遍历查看值 for index, num := range a1 { fmt.Printf("[index :%d,num:%d]\n", index, num) } //遍历修改值 for index := range a1 { a1[index] += 100 } for index, num := range a1 { fmt.Printf("[index :%d,num:%d]\n", index, num) } }

## 切片扩容和cap字段作用
- 假设没有cap只有len,怎么扩容
    1. slice长度为10,len=10,元素已经满了,现在要插入第11个元素
    2. slice长度扩为20,len=20,此时有用的元素为11个,还有9个空位。
    3. slice对外界暴露的接口只有ptr和len=20,如果此时再需要插入元素
    4. slice到底应该扩容还是在原有的基础上直接插入呢,如果直接插入从哪个索引插入
- cap字段的作用就是为了方便扩容


```go
package main

import "fmt"

func main() {

    a1 := []int{10, 11, 12, 13}
    fmt.Printf("len:%d cap:%d\n", len(a1), cap(a1))
    a1 = append(a1, 22)
    fmt.Printf("len:%d cap:%d\n", len(a1), cap(a1))
}
  • 当这个 append 操作完成后,newSlice 拥有一个全新的底层数组,这个数组的容量是原来的两倍
  • append() 会智能地处理底层数组的容量增长。在切片的容量小于 1000 个元素时,总是会成倍地增加容量。一旦元素个数超过 1000,容量的增长因子会设为 1.25,也就是会每次增加 25%的容量(随着语言的演化,这种增长算法可能会有所改变)。 ```go package main

import "fmt"

func main() {

a1 := []int{10, 11, 12, 13}
fmt.Printf("len:%d cap:%d\n", len(a1), cap(a1))
a1 = append(a1, 22)
fmt.Printf("len:%d cap:%d\n", len(a1), cap(a1))
a2 := make([]int, 1000)
a1 = append(a1, a2...)
fmt.Printf("len:%d cap:%d\n", len(a1), cap(a1))
a3 := make([]int, 1000)
a1 = append(a1, a3...)
fmt.Printf("len:%d cap:%d\n", len(a1), cap(a1))

}

# map使用


## 声明和初始化 
### 使用var只声明
- `// 只声明
   var gMap map[string]string`
### 使用var声明同时初始化
- `// 声明初始化
   var hMap = map[string]string{"a": "b"}`

### 使用make 初始化


## 增删改查
### 增加和修改


### 删除

### 读取数据
- 两种手段 第一种 单变量 lang:=m["lang"],没有key就附一个value类型的0值
- 两种手段 第二种 双变量 app2,exists := m["app2"],可以根据exists判断 true代表有key


### 增删改查举例
- 举例
```go
package main

import "fmt"

// 只声明
var m1 map[string]string

// 声明又初始化
var m2 = map[string]string{"a": "b"}

func main() {
   m := make(map[string]string)
   // 增加
   m["app"] = "taobao"
   m["lang"] = "golang"
   // 删除
   delete(m, "app")
   fmt.Println(m)
   // 改
   m["lang"] = "python"
   // 查
   // 单变量形式
   lang := m["lang"]
   fmt.Println(lang)
   // 双变量形式
   lang1, exists := m["lang"]
   if exists {
      fmt.Printf("[lang存在 值:%v]\n", lang1)
   } else {
      fmt.Println("lang不存在\n")
      m["lang"] = "java"
   }

}

遍历map和按key的顺序遍历

  • 举例 ```go package main

import "fmt"

// 只声明 var m1 map[string]string

// 声明又初始化 var m2 = map[string]string{"a": "b"}

func main() { m := make(map[string]int) keys := make([]string, 0) for i := 0; i < 20; i++ { key := fmt.Sprintf("key_%d", i) keys = append(keys, key) m[key] = i } fmt.Println(m) // range 遍历 keys //for k := range m { // fmt.Printf("[key=%s]\n", k) //}

fmt.Println("无序遍历")
// range 遍历
for k, v := range m {
    fmt.Printf("[%s=%d]\n", k, v)
}
// 有序key遍历
fmt.Println("有序遍历")
for _, k := range keys {
    fmt.Printf("[%s=%d]\n", k, m[k])
}

}

## key的类型:float64可以作为key吗
- bool、int、string
- 特征是 支持 == 和!=比较
- float型可以做为key的,写入map时会做math.Float64bits()的转换,认为2.4=2.40000xxxx1 ,看起来是同一个key
```go
package main

import "fmt"

func main() {
    m := make(map[float64]int)
    m[2.4] = 2
    fmt.Printf("k: %v, v: %d\n", 2.4000000000000000000000001, m[2.4000000000000000000000001])
    fmt.Println(m[2.4000000000000000000000001] == m[2.4])
}

value的类型:任意类型

  • map的value是个map,每一层map都要make
package main

func main() {
    var doubleM map[string]map[string]string
    // panic: assignment to entry in nil map
    //doubleM = make(map[string]map[string]string)
    v1 := make(map[string]string)
    v1["k1"] = "v1"
    doubleM["m1"] = v1

}

go原生的map线程不安全

fatal error :concurrent map read and map write

  • 举例 ```go package main

import "time"

func main() {

c := make(map[int]int)
// 匿名goroutine 循环写map
go func() {
    for i := 0; i < 10000; i++ {
        c[i] = i
    }
}()

// 匿名goroutine 循环读map
go func() {
    for i := 0; i < 10000; i++ {
        _ = c[i]
    }
}()

time.Sleep(30 * time.Minute)

}

### fatal error: concurrent map writes
- 举例
```go
package main

import "time"

func main() {

    c := make(map[int]int)
    // 匿名goroutine 循环写map
    go func() {
        for i := 0; i < 10000; i++ {
            c[i] = i
        }
    }()

    // 匿名goroutine 循环写map
    go func() {
        for i := 0; i < 10000; i++ {
            c[i] = i
        }
    }()
    //
    //// 匿名goroutine 循环读map
    //go func() {
    //  for i := 0; i < 10000; i++ {
    //      _ = c[i]
    //  }
    //}()

    time.Sleep(30 * time.Minute)
}

上述问题原因

  • go原生的map线程不安全

解决方法之一 加锁

  • 使用读写锁

解决方法之二 使用sync.map

  • go 1.9引入的内置方法,并发线程安全的map
  • sync.Map 将key和value 按照interface{}存储
  • 查询出来后要类型断言 x.(int) x.(string)
  • 遍历使用Range() 方法,需要传入一个匿名函数作为参数,匿名函数的参数为k,v interface{},每次调用匿名函数将结果返回。

  • 举例

sync.map使用 总结

sync.map 性能对比

  • https://studygolang.com/articles/27515

  • 性能对比结论 shell script 只读场景:sync.map > rwmutex >> mutex 读写场景(边读边写):rwmutex > mutex >> sync.map 读写场景(读80% 写20%):sync.map > rwmutex > mutex 读写场景(读98% 写2%):sync.map > rwmutex >> mutex 只写场景:sync.map >> mutex > rwmutex

  • sync.Map使用场景的建议

    • 读多:给定的key-v只写一次,但是读了很多次,只增长的缓存场景
    • key不相交: 覆盖更新的场景比少
  • 结构体复杂的case多不用sync.Map

分片锁 并发map github.com/orcaman/concurrent-map

  • 基础用法

带过期时间的map

  • 为什么要有过期时间
  • map做缓存用的 垃圾堆积k1 k2
  • 希望缓存存活时间 5分钟,
  • 将加锁的时间控制在最低,
  • 耗时的操作在加锁外侧做

带过期时间的缓存 github.com/patrickmn/go-cache

map的实际应用

image image image

map的原理

image image image

map底层原理文章推荐

go中的锁

互斥锁

  • sync.mutex
  • 获取到互斥锁的任务,阻塞其他任务来获取锁
  • 意味着同一时间只能有一个任务 才能持有互斥锁
package main

import (
    "log"
    "sync"
    "time"
)

// HcMutex 它是一个互斥锁
var HcMutex sync.Mutex

func runMutex(id int) {
    log.Printf("[任务id :%d][尝试获取锁]", id)
    HcMutex.Lock()
    log.Printf("[任务id :%d][获取到了锁]", id)
    time.Sleep(20 * time.Second)
    HcMutex.Unlock()
    log.Printf("[任务id :%d][干完活了 释放锁]", id)
}

func runHcLock() {
    go runMutex(1)
    go runMutex(2)
    go runMutex(3)
}

func main() {
    runHcLock()

    time.Sleep(6 * time.Minute)
    /*
       2021/07/11 18:11:02 [任务id :3][尝试获取锁]
       2021/07/11 18:11:02 [任务id :3][获取到了锁]
       2021/07/11 18:11:02 [任务id :1][尝试获取锁]
       2021/07/11 18:11:02 [任务id :2][尝试获取锁]
       2021/07/11 18:11:22 [任务id :1][获取到了锁]
       2021/07/11 18:11:22 [任务id :3][干完活了 释放锁]
       2021/07/11 18:11:42 [任务id :1][干完活了 释放锁]
       2021/07/11 18:11:42 [任务id :2][获取到了锁]

    */
}

读写锁

  • sync.RWmutex
  1. 同时多个写锁任务,说明如果并非使用读写锁的写锁时,退化成了互斥锁

  2. 同时多个读锁任务,说明使用读写锁的读锁,可以同时施加多把读锁

  3. 先启动写锁任务,后并发5个读锁任务. 当有写锁存在时,读锁是施加不了的。写锁释放完,读锁可以施加多个

  4. 先并发5个读锁任务,后启动一个写锁任务. 当有读锁时,阻塞写锁。

  • 举例 ```go package main

import ( "log" "sync" "time" )

var rwMutex sync.RWMutex

func runReadLock(id int) { log.Printf("[读任务id :%d][进入读方法尝试获取读锁]", id) rwMutex.RLock() log.Printf("[读任务id :%d][获取到了读锁][开始干活 睡眠10秒]", id) time.Sleep(10 * time.Second) rwMutex.RUnlock() log.Printf("[读任务id :%d][完成读任 释放读锁]", id) }

func runWriteLock(id int) { log.Printf("[写任务id :%d][进入写方法尝试获取写锁]", id) rwMutex.Lock() log.Printf("[写任务id :%d][获取到了写锁][开始干活 睡眠10秒]", id) time.Sleep(10 * time.Second) rwMutex.Unlock() log.Printf("[写任务id :%d][完成写任务 释放写锁]", id) }

// 全是写任务 func allWriteWorks() { for i := 1; i < 6; i++ { go runWriteLock(i) } }

// 全是读任务 func allReadWorks() { for i := 1; i < 6; i++ { go runReadLock(i) } }

// 先启动写任务 func writeFirst() { go runWriteLock(1) time.Sleep(1 * time.Second) go runReadLock(1) go runReadLock(2) go runReadLock(3) go runReadLock(4) go runReadLock(5) }

// 先启动写任务 func readFirst() { go runReadLock(1) go runReadLock(2) go runReadLock(3) go runReadLock(4) go runReadLock(5)

time.Sleep(1 * time.Second) go runWriteLock(1) }

func main() { log.Printf("执行读写锁效果的函数") // 1. 同时多个写锁任务,说明如果并非使用读写锁的写锁时,退化成了互斥锁 //allWriteWorks()

// 2. 同时多个读锁任务,说明使用读写锁的读锁,可以同时施加多把读锁 //allReadWorks()

// 3. 先启动写锁任务,后并发5个读锁任务. 当有写锁存在时,读锁是施加不了的。写锁释放完,读锁可以施加多个 //writeFirst()

// 4. 先并发5个读锁任务,后启动一个写锁任务. 当有读锁时,阻塞写锁。 readFirst()

time.Sleep(1 * time.Hour) }

# 什么是流程控制
- 控制逻辑走向和执行次序的重要部分

# go语言流程控制
- Go中流程控制分三大类:条件判断,循环控制和无条件跳转。

## 01 条件判断
- if 判断
```go
package main

import "fmt"

func judgeNum(x int) {
    if x > 10 {
        fmt.Printf("[x比10大]\n")
    } else if x == 10 {
        fmt.Printf("[x等于10]\n")
    } else {
        fmt.Printf("[x比10小]\n")
    }
}

func main() {
    judgeNum(1)
    judgeNum(11)
    judgeNum(10)

}
  • if 多条件 ```go package main

import "fmt"

func judgeNum(x int, y string) { if x > 10 && y == "ok" { fmt.Printf("[同时触发了]\n") } else { fmt.Printf("[不满足]\n") } }

func main() { judgeNum(1, "ok") judgeNum(11, "no") judgeNum(11, "ok")

}

- 条件判断语句里面允许声明一个变量
- 这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了
```go
package main

import "fmt"

func main() {
    m := map[string]string{
        "region": "北京",
        "idc":    "世纪互联",
    }
    if idc := m["idc"]; idc == "世纪互联" {
        fmt.Printf("[机房:%s]\n", "世纪互联")
    }
    fmt.Println(idc) // 编译错误undefined: idc

}

02 循环控制

03 无条件跳转

  • goto代表无条件跳转
  • 下面的例子会一直打印 ```go package main

import "fmt"

func main() { i := 0 sum: fmt.Printf("[i=%d]\n", i) i++ goto sum }

- 结合if可以控制
```go
package main

import "fmt"

func main() {
    i := 0

sum:
    {
        fmt.Printf("[i=%d]\n", i)
        i++
    }

    if i < 100 {
        goto sum
    }

}
  • goto 副作用 在结构化程序设计中一般不主张使用 goto 语句, 以免造成程序流程的混乱,使理解和调试程序都产生困难。