Go语言学习笔记

Go语言总结



Go 编程查看标准包函数方法: ctrl + . + h 或者: ctrl + . + g


  1. 运行方式()编程

    Golang提供了go run“解释”执行和go build编译执行两种运行方式,所谓的“解释”执行其实也是编译出了可执行文件后才执行的。数组

  2. Package管理()缓存

    Golang约定:咱们能够用./或../相对路径来引本身的package;若是不是相对路径,那么go会去$GOPATH/src下查找。安全

  3. 格式化输出()网络

    相似C、Java等语言,Golang的fmt包提供了格式化输出功能,并且像%d、%s等占位符和\t、\r、\n转义也几乎彻底一致。但Golang的Println不支持格式化,只有Printf支持,因此咱们常常会在后面加入\n换行。此外,Golang加入了%T打印值的类型,%v打印数组等集合的全部元素。数据结构

  4. Go语言基本类型闭包

    bool
    string
    int int8 int16 int32 int64
    uint uint8 uint16 uint32 uint64 uintptr
    byte // uint8 的别名
    rune // int32 的别名
    // 表示一个 Unicode 码点
    float32 float64
    complex64 complex128并发

  5. 变量和常量()app

    1.虽然Golang是静态类型语言,却用相似JavaScript中的var关键字声明变量。并且像一样是静态语言的Scala同样,支持类型自动推断。有一点很重要的不一样是:若是明确指明变量类型的话,类型要放在变量名后面。这有点别扭吧?!后面会看到函数的入参和返回值的类型也要这样声明。
    2.短变量声明
    在函数中,简洁赋值语句 := 可在类型明确的地方代替 var 声明。
    注意: 函数外的每一个语句都必须以关键字开始( var 、 func 等等), 所以 := 结构不能在函数外使用。函数

  6. 类型转换

    表达式 T(v) 将值 v 转换为类型 T.
    var i int = 42 var f float64 = float64(i) var u uint = uint(f) 或者这样写 i := 42 f := float64(i) u := uint(f)

  7. 常量

    常量的声明与变量相似,只不过是使用 const 关键字。
    常量能够是字符、字符串、布尔值或数值。
    常量不能用 := 语法声明。
    const ( f = 12i ) func main(){ const ( a = 2 b = 3.12 c = true d = "sssss" ) fmt.Println(a, b, c, d, f) }

  8. 控制语句()

    做为最基本的语法要素,Golang的各类控制语句也是特色鲜明。在对C继承发扬的同时,也有本身的想法融入其中:
    5.1 if/switch/for 的条件部分都没有圆括号(能够写),但必须有花括号。
    5.2 switch的case中不须要break(默认break);
    5.3 若是case语句后,想继续下一个case语句执行,需加入fallthrogh
    5.4 没有条件的 switch 同 switch true 同样。
    5.5 switch的case条件能够是多个值。
    5.6 Golang中没有while。

  9. 分号和花括号()

    注意: "分号和花括号 "
    分号由词法分析器在扫描源代码过程自动插入的,分析器使用简单的规则:若是在一个新行前方的最后一个标记是一个标识符(包括
    像int和float64这样的单词)、一个基本的如数值这样的文字、或break continue fallthrough return ++ – ) }中的一个时,它就
    会自动插入分号。 分号的自动插入规则产生了“蝴蝶效应”:全部控制结构的左花括号不都能放在下一行。由于按照上面的规则,这样
    作会致使分析器在左花括号的前方插入一个分号,从而引发难以预料的结果。因此Golang中是不能随便换行的。

  10. 函数()

    7.1 func关键字。
    7.2 最大的不一样就是“倒序”的类型声明。
    7.3 不须要函数原型,引用的函数能够后定义。这一点很好,不相似C语言里要么将“最底层抽象”的函数放在最前面定义,要么写一堆函数原型声明在最前面。
    7.4 函数的定义:
    func 关键字 函数名(参数1..)(返回值1, 返回值2 ){
    函数体
    }
    如:
    func add(a int, b int)(ret int, err int){ return a+b, b }
    7.5 函数也是值,它们能够像其它值同样传递。函数值能够用做函数的参数或返回值。
    func main() { toSqrt := func(x, y float64) float64 { return math.Sqrt(x*x + y*y) } fmt.Println(toSqrt(12, 5)) fmt.Println(autoSqrt(toSqrt)) fmt.Println(autoSqrt(math.Pow)) } func autoSqrt(fn func(x, y float64) float64) float64 { return fn(4, 3) }
    7.6 函数的闭包
    Go 函数能够是一个闭包。闭包是一个函数值,它引用了其函数体以外的变量。 该函数能够访问并赋予其引用的变量的值,换句话说,该函数被“绑定”在了这些变量上。

  11. 集合()基本数据结构 slice, struct

    Golang提供了数组和Map做为基本数据结构:

    • 8.1 数组中的元素会自动初始化,例如int数组元素初始化为0
    • 8.2 切片(借鉴Python)的区间跟主流语言同样,都是 “左闭右开”, 用 range()遍历数组和Map
      例如:
      func test02() { source := []int{1, 2, 3, 4, 5, 6} var sliceTmp []int = source[2:5] //注意 [2:n] 它为左闭右开, 例子即便: 从 下标2 开始至 下标4 fmt.Printf("%v\n", sliceTmp) }
  • 8.3 切片就像数组的引用
    切片并不存储任何数据, 它只是描述了底层数组中的一段。
    更改切片的元素会修改其底层数组中对应的元素。
    与它共享底层数组的切片都会观测到这些修改。
    例如
    func main() { source := [6]int{1, 2, 3, 5, 4} var s []int = source[2:6] fmt.Println(s) source[5] = 7 fmt.Println(s) s[0] = 88 fmt.Println(source) } 输出: [3 5 4 0] [3 5 4 7] [1 2 88 5 4 7]
  • 8.4 切片的初始化
    变量名 := []类型{...}
    例如
    a := []int{1, 2, 3 } s := []struct{ age int name string }{ {1, "xiaoming"}, {21. "xiaohua"}, {23, "nhao"}, ** 注意, 最后一个逗号不能省 ** }
  • 8.5 切片的长度与容量
    切片拥有 长度 和 容量 。
    切片的长度就是它所包含的元素个数
    切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
    切片 s 的长度和容量可经过表达式 len(s) 和 cap(s) 来获取。
    func main() { // a 是切片 a := []int{12, 5, 3, 6, 8, 6} // 让切片的长度为 0 a = a[:0] printSlice(a) // 扩充切片的长度 a = a[:3] printSlice(a) // 丢掉开始的两个元素 a = a[2:] printSlice(a) } func printSlice(s []int) { fmt.Printf("len = %d, cap = %d, value = %v\n", len(s), cap(s), s) } 输出: len = 0, cap = 6, value = [] len = 3, cap = 6, value = [12 5 3] len = 1, cap = 4, value = [3]
  • 8.6 nil 切片
    切片的零值是 nil 。
    nil 切片的长度和容量为 0 且没有底层数组。
    func main() { var s []int fmt.Println(s, len(s), cap(s)) if s == nil { fmt.Println("nil!") } } 输出: [] 0 0 nil!
  • 8.7 用 make 建立切片
    切片能够用内建函数 make 来建立,这也是你建立动态数组的方式。
    make 函数会分配一个元素为零值的数组并返回一个引用了它的切片.
    func main() { a := make([]int, 6) printSlice("a", a) b := make([]int, 0, 5) printSlice("b", b) c := make([]int, 3, 5) printSlice("c", c) d := b[:2] printSlice("d", d) e := d[2:5] printSlice("e", e) } func printSlice(flag string, s []int) { fmt.Printf("%s, len = %d, cap = %d, value = %v\n", flag, len(s), cap(s), s) } 输出: a, len = 6, cap = 6, value = [0 0 0 0 0 0] b, len = 0, cap = 5, value = [] c, len = 3, cap = 5, value = [0 0 0] d, len = 2, cap = 5, value = [0 0] e, len = 3, cap = 3, value = [0 0 0]
  • 8.8 Go的数组 与 C语言数组的区别
    Go的数组是值语义。一个数组变量表示整个数组,它不是指向第一个元素的指针(不像 C 语言的数组)。 当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。 (为了不复制数组,你能够传递一个指向数组的指针,可是数组指针并非数组。) 能够将数组看做一个特殊的struct,结构的字段名对应数组的索引,同时成员的数目固定。

  • 8.9 map结构的使用
    例如:
    type People struct { age int name string } var m map[string]People func test04() { m = make(map[string]People) fmt.Println(m)= m["Afra55"] = People{ 22, "Victor", } m["xiaohuo"] = People{ 24, "nihao", //注意:这种写法,最后一个','必定不能少,不然为语法错误 } fmt.Println(m) fmt.Println(m["Afra55"]) } 输出: map[] map[Afra55:{22 Victor} xiaohuo:{24 nihao}] {22 Victor}
    修改 map 映射
    在映射 m 中插入或修改元素: m[key] = elem
    获取元素: elem = m[key]
    删除元素: delete(m, key)
    经过双赋值检测某个键是否存在: elem, ok = m[key]
    若 key 在 m 中, ok 为 true ;不然, ok 为 false 。
    若 key 不在映射中,那么 elem 是该映射元素类型的零值。
    一样的,当从 映射 中读取某个不存在的键时,结果是 映射 的元素类型的零值。

  1. 指针和内存分配()

    Golang中可使用指针,并提供了两种内存分配机制:
    9.1 new:分配长度为0的空白内存,返回类型T*。new 返回的是一个函数指针。
    9.2 make:仅用于 切片、map、chan消息管道,返回类型T而不是指针

  2. 面向对象编程("不太懂")

    Golang的结构体跟C有几点不一样:
     10.1 结构体能够有方法,其实也就至关于OOP中的类了。
      10.1.1 支持带名称的初始化。
      10.1.2 用指针访问结构中的属性也用”.”而不是”->”,指针就像Java中的引用同样。
      10.1.3 没有public,protected,private等访问权限控制。C也没有protected,C中默认是public的,private须要加static关键字限定。Golang中方法名大写就是public的,小写就是private的。
     10.2 同时,Golang支持接口和多态,并且接口有别于Java中继承和实现的方式,而是采起了相似Ruby中更为新潮的DuckType。只要struct与interface有相同的方法,就认为struct实现了这个接口。就比如只要能像鸭子那样叫,咱们就认为它是一只鸭子同样。
     10.3 Go 没有类。然而,能够在结构体类型上定义方法。
     10.4 能够对包中的任意类型(以type定义的类)定义任意方法,而不只仅是针对结构体。可是,不能对来自其余包的类型或基础类型定义方法。
     10.5 有时候咱们须要将接受方法的对象定义为指针,这样能够有两个效果:
    1) 能够提升参数传递的效率,不用拷贝。
    2) 修改接收者指向的值。

  3. 异常处理 (不太懂)

    11.1 Golang中异常的使用比较简单,能够用errors.New建立,也能够实现Error接口的方法来自定义异常类型,同时利用函数的多返回值特性能够返回异常类。比较复杂的是defer和recover关键字的使用。Golang没有采起try-catch“包住”可能出错代码的这种方式,而是用 延迟处理的方式.
    11.2 用defer调用的函数会之后进先出(LIFO)的方式,在当前函数结束后依次顺行执行。defer的这一特色正好能够用来处理panic。当panic被调用时,它将当即中止当前函数的执行并开始逐级解开函数堆栈,同时运行全部被defer的函数。若是这种解开达到堆栈的顶端,程序就死亡了。
    11.3 可是,也可使用内建的recover函数来从新得到Go程的控制权并恢复正常的执行。因为仅在解开期间运行的代码处在被defer的函数以内,recover仅在被延期的函数内部才是有用的。
    11.4 defer语句会将函数推迟到外层函数返回以后执行。
    推迟调用的函数其参数会当即求值,但直到外层函数返回前该函数都不会被调用。
    func main() { defer sqrt(9) fmt.Println("9 * 9: ") } func sqrt(x float64) { fmt.Println(x * x) } 打印的值: 9 * 9; 81

  4. goroutine(协程)

    goroutine使用Go关键字来调用函数,也可使用匿名函数。能够简单的把go关键字调用的函数想像成pthread_create。若是一个goroutine没有被
    阻塞,那么别的goroutine就不会获得执行。也就是说goroutine阻塞时,Golang会切换到其余goroutine执行,这是很是好的特性!Java对相似
    goroutine这种的协程没有原生支持,像Akka最惧怕的就是阻塞。由于协程不等同于线程,操做系统不会帮咱们完成“现场”保存和恢复,因此要实现
    goroutine这种特性,就要模拟操做系统的行为,保存方法或函数在协程“上下文切换”时的Context,当阻塞结束时才能正确地切换回来。像Kilim等
    协程库利用字节码生成,可以胜任,而Akka彻底是运行时的。
    注意:"若是你要真正的并发,须要调用runtime.GOMAXPROCS(CPU_NUM)设置".
    "本身的观察: Go程相似线程,执行完后, 从本身的函数中就直接退出, 不会回到主进程空间,同时不须要回收资源"

  5. 原子操做()

    像Java同样,Golang支持不少CAS操做。运行结果是unsaftCnt可能小于200,由于unsafeCnt++在机器指令层面上不是一条指令,而多是从内存加载
    数据到寄存器、执行自增运算、保存寄存器中计算结果到内存这三部分,因此不进行保护的话有些更新是会丢失的。

  6. Channel管道()

    14.1 经过前面能够看到,尽管goroutine很方便很高效,但若是滥用的话极可能会致使并发安全问题。而Channel就是用来解决这个问题的,它是goroutine
    之间通讯的桥梁,相似Actor模型中每一个Actor的mailbox。多个goroutine要修改一个状态时,能够将请求都发送到一个Channel里,而后由一个
    goroutine负责顺序地修改状态。
    14.2 Channel默认是阻塞的,也就是说select时若是没有事件,那么当前goroutine会发生读阻塞。同理,Channel是有大小的,当Channel满了时,
    发送方会发生写阻塞。Channel这种阻塞的特性加上goroutine能够很容易就能实现生产者-消费者模式。
    14.3 用case能够给Channel设置阻塞的超时时间,避免一直阻塞。而default则使select进入无阻塞模式。
    14.4 有缓存管道与无缓存管道的区别
    14.4.1 对于无缓冲的channel,放入操做和取出操做不能再同一个routine中,并且应该是先确保有某个routine对它执行取出操做,而后才能在另外一个routine中执行放入操做
    14.4.2 在使用带缓冲的channel时必定要注意放入与取出的速率问题
    14.4.3 使用channel控制goroutine数量

  7. 缓冲流()

    15.1 Golang的bufio包提供了方便的缓冲流操做,经过strings或网络IO获得流后,用bufio.NewReader/Writer()包装:
    15.2 缓冲区:Peek()或Read时,数据会从底层进入到缓冲区。缓冲区默认大小为4096字节。
    15.3 切片和拷贝:Peek()和ReadSlice()获得的都是切片(缓冲区数据的引用)而不是拷贝,因此更加节约空间。可是当缓冲区数据变化时,切片也会随之变化。
    而ReadBytes/String()获得的都是数据的拷贝,能够放心使用。
    15.4 Unicode支持:ReadRune()能够直接读取Unicode字符。有意思的是Golang中Unicode字符也要用单引号,这点与Java不一样。
    15.5 分隔符:ReadSlice/Bytes/String()获得的包含分隔符,bufio不会自动去掉。
    15.6 Writer:对应地,Writer提供了WriteBytes/String/Rune。
    15.7 undo方法:能够将读出的字节再放回到缓冲区,就像什么都没发生同样。


Go 语言开发中的坑

1. slice(切片) 的坑:

由于:当原始切片的容量不够,增长后,新的切片指向了一个新的地址,开发者此时极容易使用 slice 特性,致使返回的结果不是所指望的。
//1.错误写法
    func test(s []int){     
    s.append(s, 3);         //此时slice将与传入的slice指向不一样内存地址,因此想获得理想的结果,就须要将新的slice地址传出。
}
    func main(){
    s := make([]int, 0)     //建立一个容量为 0 的slice;
    fmt.Println(s)
    test(s)                 //对这个建立的slice进行扩容
    fmt.Println(s)
}
    打印结果为: [0] [0]
    
    //2.正确写法
    func test(s []int) []int {
        s.append(s, 3)
        return s
    }
    func main(){
        s := make([]int, 0)
        fmt.Println(s)
        s = test(s)
        fmt.Println(s)
    }
     打印结果为: [0] [3]

因此若是在操做slice时, 可能会使容量增大, 此时就必定要把新的slice返回出来。

2. time时间的坑

由于:Go语言的设计时,提供了一组常量layout,来格式化它的时间输出。可是,可是:要么使用layout中提供的常量,要么直接拷贝它的常量字符串,千万不要对它的字符串进行修改,不然或形成输出时间不正确。(语言设计时的硬坑)

3. for range 与闭包函数的坑

//1.错误写法
func closures() {
    s := make([]int, 3)
    for i := 0; i < 3; i++ {
        s[i] = i + 1
    }
    for _, v := range s {   //轮询切片s,将取出的值,从地址中取出该值进行打印 ,由于主线程先运行完,等打印时全部的v都已变为最后一个元素的地址,因此打印全是 3
        go func() {
            fmt.Println(v)
        }()
    }
}
打印结果为: 3, 3, 3

//2.正确的写法
func closures() {
    s := make([]int, 3)
    for i := 0; i < 3; i++ {
        s[i] = i + 1
    }
    for _, v := range s {   //轮询切片s,将取出的值以值传递的方式传入闭包函数中;
        go func(v int) {
            fmt.Println(v)
        }(v)
    }
}
打印结果为: 1, 2, 3