2021-05-15 go基础备注

1、数组是值类型

b1 := [3]int{1, 2, 3} 
b2 := b1       
b2[0] = 100 
fmt.Println(b1, b2) 

运行结果

image.png

2、切片是引用类型

  • 切片的容量是底层数组的容量
  • 切片是引用类型,都指向了底层的一个数组。修改数组或切片的值,会影响到所有切片和底层数组上面
  • 判断一个切片是否是空的,要是用len(s) == 0来判断
// 由数组得到切片,左闭右开
    a1 := [...]int{0, 1, 2, 3, 4, 5, 6}
    s6 := a1[3:] //   [3 4 5 6] 
    s7 := a1[2:5]  // [2 3 4]
    fmt.Println(s6, s7)
    // 切片的容量是指底层数组的容量
    fmt.Printf("len(s6):%d cap(s6):%d\n", len(s6), cap(s6))
    //  cap 就是整个数组从切片起始index开始后的长度
    fmt.Printf("len(s7):%d cap(s7):%d\n", len(s7), cap(s7))
    // 切片再切割
    s8 := s6[3:] 
    fmt.Println("s8:", s8) // s8: [6]
    fmt.Printf("len(s8):%d cap(s8):%d\n", len(s8), cap(s8))
    // 切片是引用类型,都指向了底层的一个数组。
    a1[6] = 1300 // 修改了底层数组的值
    fmt.Println("s6:", s6)
    fmt.Println("s8:", s8)
    // 通过切片修改
    s6[3] = 8888
    fmt.Println("s6:", s6)
    fmt.Println("s8:", s8)

运行结果

image.png

append 函数可能重新分配内存

此关键字用来追加元素到数组、slice中

    s1 := []string{"北京", "上海", "深圳"}
    fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))
    // 调用append函数必须用原来的切片变量接收返回值
    // append追加元素,原来的底层数组放不下的时候,Go底层就会把底层数组换一个
    // 必须用变量接收append的返回值
    s1 = append(s1, "广州")
    fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))
    ss := []string{"武汉", "西安", "苏州"}
    s1 = append(s1, ss...) // ...表示拆开
    fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))

    a1 := [...]int{1, 3, 5, 7, 9, 11, 13, 15, 17}
    s1 := a1[:]
    // 删掉索引为1的那个3
    s1 = append(s1[0:1], s1[2:]...)
    fmt.Println(s1) // [1 5 7 9 11 13 15 17]
    fmt.Println(a1) // [1 5 7 9 11 13 15 17 17]

运行结果

image.png

3、make和new的区别

  • make和new都是用来申请内存的
  • new很少用,一般用来给基本数据类型申请内存(string、int, struct等[结构体也是值类型]),返回的是对应类型的指针(string、int)。
  • make是用来给slice、map、chan申请内存的,make函数返回的的是对应的这三个类型本身(引用类型)
    var a1 *int // nil pointer
    fmt.Println(a1)  // <nil>
    var a2 = new(int) // new函数申请一个内存地址
    fmt.Println(a2) // 0xc0000140c0
    fmt.Println(*a2) // 0
    var m1 map[string]int
    fmt.Println(m1 == nil)        // true 还没有初始化(没有在内存中开辟空间)
    m1 = make(map[string]int, 10) // 要估算好该map容量,避免在程序运行期间再动态扩容
    m1["ww"] = 18
    m1["kkj"] = 35

    fmt.Println(m1) // map[kkj:35 ww:18]
    // 如果不存在这个key拿到对应值类型的零值
    fmt.Println(m1["naz"]) // 0
    value, ok := m1["naz"]
    if !ok {
        fmt.Println("查无此key")
    } else {
        fmt.Println(value)
    }

    // 删除
    delete(m1, "ww")
    fmt.Println(m1) // map[kkj:35]
    delete(m1, "lalar") // 删除不存在的key,不会报错

4、defer

Go语言中函数的return不是原子操作,在底层是分为两步来执行
第一步:返回值赋值
defer
第二步:真正的RET返回
函数中如果存在defer,那么defer执行的时机是在第一步和第二步之间
defer会把后面的语句延迟调用
把当时的状态都保存
defer多用于函数结束之前释放资源(文件句柄、数据库连接、socket连接)
多个defer存在时,按照先进后出的方式去执行。
func calc(index string, a, b int) int {
    ret := a + b
    fmt.Println(index, a, b, ret)
    return ret
}

func main() {
    a := 1
    b := 2
    defer calc("1", a, calc("10", a, b))
    a = 0
    defer calc("2", a, calc("20", a, b))
    b = 1
}

运行情况及分析

image.png

1. a:=1
2. b:=2
3. defer calc("1", 1, calc("10", 1, 2))
4. calc("10", 1, 2) // defer 状态保存,到时候运行需要这里的数据。所以这句会直接执行
5. defer calc("1", 1, 3) // defer 状态保存,不执行
6. a = 0
7. defer calc("2", 0, calc("20", 0, 2))
8. calc("20", 0, 2) // defer 状态保存,到时候运行需要这里的数据。所以这句会直接执行
9. defer calc("2", 0, 2) // defer 状态保存,不执行
10. b = 1
calc("2", 0, 2) //  倒序执行 defer, 入参是当时保存的状态
calc("1", 1, 3) // 倒序执行 defer, 入参是当时保存的状态

5、panic && recover

panic用于抛出一个异常
revocer用于捕获错误

func funcA() {
    fmt.Println("a")
}

func funcB() {
    // 刚刚打开数据库连接
    defer func() {
        err := recover()
        fmt.Println(err)
        fmt.Println("释放数据库连接...")
    }()
    panic("出现了严重的错误!!!") // 程序崩溃退出
    fmt.Println("b")
}

func funcC() {
    fmt.Println("c")
}
func main() {
    funcA()
    funcB()
    funcC()
}

运行结果

image.png

6、类型断言

做类型断言的前提是一定要是一个接口类型的变量
x.(T)

image.png

7、map非并发安全

Go内置的map不是并发安全的,超过21个并发的写入肯定报错
import (
    "fmt"
    "strconv"
    "sync"
)

var m = make(map[string]int)

//var lock sync.Mutex

func get(key string) int {
    return m[key]
}

func set(key string, value int) {
    m[key] = value
}

func main() {
    wg := sync.WaitGroup{}
    for i := 0; i < 21; i++ {
        wg.Add(1)
        go func(n int) {
            key := strconv.Itoa(n)
            //lock.Lock()
            set(key, n)
            //lock.Unlock()
            fmt.Printf("k=:%v,v:=%v\n", key, get(key))
            wg.Done()
        }(i)
    }
    wg.Wait()
}

运行结果

image.png

加锁可以解决:将代码里面lock相关的注释恢复,在运行就正常了
image.png
或者使用 sync.Map

sync.Map 是一个开箱即用的并发安全的map


import (
    "fmt"
    "strconv"
    "sync"
)

var m2 = sync.Map{}

func main() {
    wg := sync.WaitGroup{}
    for i := 0; i < 21; i++ {
        wg.Add(1)
        go func(n int) {
            key := strconv.Itoa(n)
            m2.Store(key, n)         // 必须使用sync.Map内置的Store方法设置键值对
            value, _ := m2.Load(key) // 必须使用sync.Map提供的Load方法根据key取值
            fmt.Printf("k=:%v,v:=%v\n", key, value)
            wg.Done()
        }(i)
    }
    wg.Wait()
}
image.png

8、atomic原子操作包

Go - atomic包使用及atomic.Value源码分析

9、包管理

依赖管理:超详细解读 Go Modules 应用

10、常用方法和命令

11、一个简单的tcp示例

  • 自定义协议编码解码
package proto

import (
    "bufio"
    "bytes"
    "encoding/binary"
)

// Encode 将消息编码
func Encode(message string) ([]byte, error) {
    // 读取消息的长度,转换成int32类型(占4个字节)
    var length = int32(len(message))
    var pkg = new(bytes.Buffer)
    // 写入消息头
    err := binary.Write(pkg, binary.LittleEndian, length)
    if err != nil {
        return nil, err
    }
    // 写入消息实体
    err = binary.Write(pkg, binary.LittleEndian, []byte(message))
    if err != nil {
        return nil, err
    }
    return pkg.Bytes(), nil
}

// Decode 解码消息
func Decode(reader *bufio.Reader) (string, error) {
    // 读取消息的长度
    lengthByte, _ := reader.Peek(4) // 读取前4个字节的数据
    lengthBuff := bytes.NewBuffer(lengthByte)
    var length int32
    err := binary.Read(lengthBuff, binary.LittleEndian, &length)
    if err != nil {
        return "", err
    }
    // Buffered返回缓冲中现有的可读取的字节数。
    if int32(reader.Buffered()) < length+4 {
        return "", err
    }

    // 读取真正的消息数据
    pack := make([]byte, int(4+length))
    _, err = reader.Read(pack)
    if err != nil {
        return "", err
    }
    return string(pack[4:]), nil
}
  • client
package main

import (
    "fmt"
    "net"
    proto "xxxx/protocol"
)

// 黏包 client
func main() {
    conn, err := net.Dial("tcp", "127.0.0.1:30000")
    if err != nil {
        fmt.Println("dial failed, err", err)
        return
    }
    defer conn.Close()
    for i := 0; i < 20; i++ {
        msg := `Hello, Hello. How are you?`
        // 调用协议编码数据
        b, err := proto.Encode(msg)
        if err != nil {
            fmt.Println("encode failed,err:", err)
            return
        }
        conn.Write(b)
        // time.Sleep(time.Second)
    }
}
  • server
package main

import (
    "bufio"
    "fmt"
    "io"
    "net"
    proto "xxxx/protocol"
)

func process(conn net.Conn) {
    defer conn.Close()
    reader := bufio.NewReader(conn)
    for {
        recvStr, err := proto.Decode(reader)
        if err == io.EOF {
            return
        }
        if err != nil {
            fmt.Println("decode failed,err:", err)
            return
        }
        fmt.Println("收到client发来的数据:", recvStr)
    }
}

func main() {
    listen, err := net.Listen("tcp", "127.0.0.1:30000")
    if err != nil {
        fmt.Println("listen failed, err:", err)
        return
    }
    defer listen.Close()
    for {
        conn, err := listen.Accept()
        if err != nil {
            fmt.Println("accept failed, err:", err)
            continue
        }
        go process(conn)
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,539评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,594评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,871评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,963评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,984评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,763评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,468评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,850评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,002评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,144评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,823评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,483评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,026评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,150评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,415评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,092评论 2 355

推荐阅读更多精彩内容

  • Go的优点高效垃圾回收机制类型安全和内存安全(没有隐式转换,只能显示转换)快速编译(未使用包检测)轻松实现高并发支...
    Jarily阅读 505评论 0 0
  • 运行 hello world 报错 go run test.go go run: cannot run non-m...
    Edmond_33阅读 671评论 0 0
  • 安装和环境配置 自行百度解决 go项目的目录结构 go命令依赖一个重要的环境变量:$GOPATH一般的,一个Go项...
    名字刚好七个字阅读 365评论 0 0
  • 第2章 顺序编程 2.1 变量 变量声明 变量声明的类型信息放在变量之后,放在数组的中括号之后,作返回值类型放在方...
    fjxCode阅读 428评论 0 1
  • Go语言做Web编程非常方便,并且在开发效率和程序运行效率方面都非常优秀。相比于Java,其最大的优势就是简便易用...
    暗黑破坏球嘿哈阅读 9,009评论 6 66