Skip to content

Latest commit

 

History

History
787 lines (605 loc) · 19.9 KB

学习笔记.md

File metadata and controls

787 lines (605 loc) · 19.9 KB

概述

Go语言的源码文件有三大类,即:命令源码文件、库源码文件和测试源码文件。他们的功能各不相同,而写法也各有各的特点。

  • 命令源码文件总是作为可执行的程序的入口。
  • 库源码文件一般用于集中放置各种待被使用的程序实体(全局常量、全局变量、接口、结构体、函数等等)
  • 测试源码文件主要用于对前两种源码文件中的程序实体的功能和性能进行测试。
/**
  关键字
    package    import
    select     switch    case     fallthrough     default
    go         defer     goto
    map        chan      struct
    for        range     continue    break
    if         else
    var        type      func    const   return

  内建常量
    true 	false  iota   nil

  内建类型
    int      int8      int16     int32     int64
    uint     uint8     uint16    uint32    uint64
    float32  float64
    bool     byte      rune      string    error
    uintptr

  内建函数
    make      初始化内置的数据结构,slice、map、channel。
    new       作用是根据传入的类型分配一片内存空间并返回指向这片内存空间的指针
    append    可以为切片动态添加元素
    close     关闭了一个通道,这个通道必须是双向的或只发送的。
    panic
    recover
    copy    len    cap    delete
*/

变量

// 声明
  var intA int = 9
  var stringA string = "abc"
  var floatA float32 = 9.87

// 批量声明
  var i, j, k int

// 简短变量声明,简短变量声明被广泛用于大部分的局部变量的声明和初始化。
  intC := 9

//批量初始化
  i, j := 0, 1

赋值

  var x int = 1

  v := 1
  v++    // 等价方式 v = v + 1;v 变成 2
  v--    // 等价方式 v = v - 1;v 变成 1

//元组赋值
  x, y = y, x

//隐式的赋值行为
  medals := []string{"gold", "silver", "bronze"}

类型

  1. 一个类型可以看作是值的模板。一个值可以看作是某个类型的实例。
  2. uintptr 类型的值的尺寸能够存下任意一个内存地址。

基本类型

布尔类型bool
整形byte int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr
浮点float32 float64
字符rune
字符串string
错误类型error

自定义类型

type Duration int64

其他

  1. Go 中有两种内置类型别名

    • byteuint8 的内置别名
    • runeint32 的内置别名
  2. 布尔值有两种状态使用预声明的常量(falsetrue)来表示。

  3. 类型别名声明

    type Name = string

指针

  1. 任何类型的指针零值都是nil。

  2. 指针可以被赋值为nil,但是不能被赋值为其他类型的值。

  3. 指针之间也是可以进行相等测试的,只有当它们指向同一个变量或全部是nil时才相等。

    var x, y *int
    fmt.Println(x,y)
    fmt.Println(x == x, x == y, x == nil) //true true true
  4. 在 Go 中返回一个局部变量的地址是安全的。编译器会做逃逸分析(escape analysis),当发现变量的作用域没有跑出函数范围,就可以在栈上,反之则必须分配在堆。

    func newInt() *int {
    	c := 3
    	return &c
    }

常量

无类型常量和有类型常量,无类型常量灵活度更高。

package main

import "fmt"

const hello = "hello, world"
//字符串类型 string 起了别名后是一个新的字符串类型,如 typeHello,尽管意义是一样的,但是在规则上,是不同的类型
const typeHello string = "Hello, World"

func main() {
	type myString string
	var a myString
	a = hello //这样是可以的
	a = typeHello //这样是错误的
  fmt.Println(a)
}

控制结构

for

// 类似 C 语言中的 for 用法
for init; condition; post { }

// 类似 C 语言中的 while 用法
for condition { }

// 类似 C 语言中的 for(;;) 用法
for { }

//eg:1
  for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
      a[i], a[j] = a[j], a[i]
  }

switch

//eg:1
  func unhex(c byte) byte {
    switch {
    case '0' <= c && c <= '9':
      return c - '0'
    case 'a' <= c && c <= 'f':
      return c - 'a' + 10
    case 'A' <= c && c <= 'F':
      return c - 'A' + 10
    }
    return 0
  }

//eg:2 switch 并不会自动下溯,但 case 可通过逗号分隔来列举相同的处理条件。
  func shouldEscape(c byte) bool {
    switch c {
    case ' ', '?', '&', '=', '#', '+', '%':
      return true
    }
    return false
  }

//eg:3 类型断言
  var t interface{}
  t = functionOfSomeType()
  switch t := t.(type) {
  default:
    fmt.Printf("unexpected type %T\n", t)     // %T 打印任何类型的 t
  case bool:
    fmt.Printf("boolean %t\n", t)             // t 是 bool 类型
  case int:
    fmt.Printf("integer %d\n", t)             // t 是 int 类型
  case *bool:
    fmt.Printf("pointer to boolean %t\n", *t) // t 是 *bool 类型
  case *int:
    fmt.Printf("pointer to integer %d\n", *t) // t 是 *int 类型
  }

数组

  1. 数组是值。将一个数组赋予另一个数组会复制其所有元素。
  2. 特别地,若将某个数组传入某个函数,它将接收到该数组的一份副本而非指针。
  3. 数组的大小是其类型的一部分。类型 [1]int[2]int 是不同的。
// 声明
  var q [3]int = [3]int{1,2,3}
// 如果在数组的长度位置出现的是 ... 省略号,则表示数组的长度是根据初始化值的个数来计算。
  q := [...]int{1,2,3}

切片

  1. 切片的零值为 nil 。对于切片的零值, lencap 都将返回0。
  2. 切片是一种数据结构,描述与切片变量本身分开存储的数组的一段连续的部分;切片不是数组,切片描述一块数组。
//声明
  var s1 []int

// 简短声明
  s2 := []int{0,1,2,3,4,5}

// make 创建一个指定元素类型、长度和容量的slice,容量部分可以省略。
// 在底层,make 创建了一个匿名的数组变量,然后返回一个slice;只有通过返回的 slice 才能引用底层匿名的数组变量。
  s3 = make([]int,3)
  s4 = make([]int,3,4)

//slice 唯一合法的比较
  if summer == nil {}

//append 函数,内置函数用于向 slice 函数追加元素
  s1 := append(s2,6,7,8)

/*
  1. 切片也可以基于现有的切片或数组生成
  2. 切分的范围由两个由冒号分割的索引对应的半开区间指定
  3. 切片的开始和结束的索引都是可选的;它们分别默认为零和数组的长度。
*/
  b := []byte("golang") // or b := []byte{'g', 'o', 'l', 'a', 'n', 'g'}
	fmt.Printf("%s\n",b[1:4]) //ola
  fmt.Printf("%s\n",b[:]) //golang

//字符串:它们只是只读的字节切片。
  usr := "/usr/ken"[0:4]
  slice := []byte(usr) //转换

结构体

//声明
  type tree struct {
    value       int
    left, right *tree
  }

函数

//声明
func name(parameter-list) (result-list){
}

//遇到没有函数体的函数声明
package math
func Sin(x float64) float

//匿名函数
func squares() func() int{
  var x int 
  return func() int {
    x++
    return x * x
  }
}

//可变参数
func sum(vals ...int) int{
  total := 0
  for _,val := range vals {
    total += val
  }
  return total
}
fmt.Println(sum(1, 2, 3, 4))

defer 函数

  1. defer 的执行顺序与声明顺序相反,越后面的defer函数越先被执行。
  2. 当执行到(defer)该语句时,函数和参数表达式得到计算,但直到包含该 defer 语句的函数执行完毕时,defer 后的函数才会被执行,不论包含 defer 语句的函数是通过 return 正常结束,还是由于 panic 导致的异常结束。
//eg.1
  func main() {
      defer fmt.Println(1)
      defer fmt.Println(2)
      defer fmt.Println(3)
      defer fmt.Println(4)
  }
  //result 4 3 2 1

//eg.2
  func f()(r int){
    defer func(r int){
      r = r + 5
    }(r)
    return 1
  }
  // result 1

//eg.3
  func f()(result int){
    defer func(){
      result ++
    }()
    return 0
  }
  //result 1

//eg.4
  func f()(r int) {
    t :=5
    defer func(){
      t = t + 5
    }()
    return t
  }
  //result 5
  1. panic 是一个内置函数,它用来停止正常的流程并使程序处于 panicking 状态。当函数F调用 panic 时,F 函数立即停止执行,然后运行在 F 中定义的所有 defer 函数并返回调用者,对于调用者来说,F 函数的行为就像 panic 一样。该进程继续向上执行,直到当前 goroutine 中的所有函数都返回,此时程序奔溃。
  2. Recover 是一个内置函数,可重新控制 panicking 状态下的 goroutine。Recover只在延迟函数(defer)内部有用。在正常执行期间,调用 recover 将返回nil,并且没有其他效果。如果当前 goroutine处于 panicking,调用 recover 将捕获指定给 panic 的值并恢复正常执行。
package main

import "fmt"

func main(){
	f()
	fmt.Println("Returned normally from f.")
}

func f(){
	defer func(){
		if r := recover(); r != nil {
			fmt.Println("Recovered in f",r)
		}
	}()
	fmt.Println("calling g.")
	g(0)
	fmt.Println("Returned normally from g.")
}

func g(i int){
	if i > 3 {
		fmt.Println("panicking!")
		panic(fmt.Sprintf("%v",i))
	}
	defer fmt.Println("defer in g",i)
	fmt.Println("printing in g",i)
	g(i+1)
}

/*
  calling g.
  printing in g 0
  printing in g 1
  printing in g 2
  printing in g 3
  panicking!
  defer in g 3
  defer in g 2
  defer in g 1
  defer in g 0
  Recovered in f 4
  Returned normally from f
*/

方法

  1. 方法能给用户定义的类型添加新的行为。

  2. 可以为任何已命名的类型(除了指针或接口)定义方法。

//slice
type mySlice []int

func (s mySlice) Len() int {
	return len(s)
}

//string
type myStr string

func (dist myStr) strJoin(src myStr) string {
	return string(dist + src)
}

func TestFunc(t *testing.T) {
	var s mySlice
	fmt.Println(s.Len())

  var strA myStr = "hello"
	fmt.Println(strA.strJoin("world"))
}

接收者

Go 语言里有两种类型的接收者:值接收者和指针接收者。

type user struct {
  name string
  email string
}

//值接收
  func (u user) notify() {
    fmt.Printf("Sending User Email To %s<%s>\n", u.name,u.email)
  }

//指针接收者
  func (u *user) notify() {
    fmt.Printf("Sending User Email To %s<%s>\n", u.name,u.email)
  }

Goroutines

  1. 在 Go 语言中,每一个并发的执行单元叫作一个 goroutine。
  2. 主函数(main)返回时,所有的 goroutine 都会被直接打断,程序退出。

channels

声明

//创建 channel;
  ch := make(chan int)
  // or
  var ch chan int
  ch = make(chan int)

// 发送操作
  ch <- 9

//接收操作
  x := <-ch
  <-ch

//内置函数 close 函数关闭一个channel
// 1. 重复关闭 channel 将导致 panic 异常
// 2. 只有当需要告诉接收者 goroutine,所有的数据已经全部发送才需要关闭 channel。
// 3. 不管一个 channel 是否被关闭,当它没有被引用时将会被 go 语言的垃圾自动回收器回收。
  close(ch)

//channel 的缓存
  ch = make(chan int)    //无缓存
  ch = make(chan int,3)  //有缓存

无缓存

一个基于无缓存 Channels 的发送操作将导致发送者 goroutine 阻塞,直到另一个 goroutine 在相同的 Channels 上执行接收操作,当发送的值通过 Channels 成功传输之后,两个 goroutine 可以继续执行后面的语句。反之,如果接收操作先发生,那么接收者 goroutine 也将阻塞,直到有另一个 goroutine 在相同的 Channels 上执行发送操作。

//死锁,不能成功运行
package main

import "fmt"

func main() {
  ch := make(chan int)
  ch <- 9
  receive := <-ch
  fmt.Println(receive)
}

管道

//管道的实现,即一个 Channel 的输出作为下一个 Channel 的输入。
package main

import "fmt"

func main() {
  naturals := make(chan int)
  squares := make(chan int)

  go func() {
    for x := 0; x < 10; x++ {
      naturals <- x
    }
    close(naturals)
  }()

  go func() {
    //使用 range 方式,它依次从 channel 接收数据,当 channel 被关闭并且没有值可接收时跳出循环。
    for x := range naturals {
      squares <- x * x
    }
    close(squares)
  }()

  for x := range squares {
    fmt.Println(x)
  }
}

单方向

/**
	1. chan<- int 表示一个只发送 int 的 channel,只能发送不能接收。
	2. <-chan int 表示一个只接收 int 的 channel,只能接收不能发送。
*/
package main

import "fmt"

func main() {
	naturals := make(chan int)
	squares := make(chan int)
	go counter(naturals)
	go squarer(naturals, squares)
	printer(squares)
}

func counter(naturals chan<- int) {
	for x := 0; x < 10; x++ {
		naturals <- x
	}
	close(naturals)
}

func squarer(naturals <-chan int, squares chan<- int) {
	//使用 range 方式,它依次从 channel 接收数据,当 channel 被关闭并且没有值可接收时跳出循环。
	for x := range naturals {
		squares <- x * x
	}
	close(squares)
}

func printer(squares <-chan int) {
	for x := range squares {
		fmt.Println(x)
	}
}

带缓存

  1. 带缓存的 Channel 内部持有一个元素队列。队列的最大容量是在调用 make 函数创建 channel 时通过第二个参数指定的。

    ch := make(chan string, 3)
  2. 向缓存 Channel 的发送操作就是向内部缓存队列的尾部插入元素,接收操作则是从队列的头部删除元素。如果内部缓存队列是满的,那么发送操作将阻塞直到因另一个 goroutine 执行接收操作而释放了新的队列空间。相反,如果 channel 是空的,接收操作将阻塞直到有另一个 goroutine 执行发送操作而向队列插入元素。

    //无阻塞的发送数据
      ch <- "hello"
      ch <- "world"
      ch <- "test"
  3. 获取 channel 内部缓存的容量。

    fmt.Println(cap(ch))
  4. Goroutines 泄漏

    //多个 goroutines 并发向同一个 channel 发送数据。
      func mirroredQuery() string {
          responses := make(chan string, 3)
          go func() { responses <- request("asia.gopl.io") }()
          go func() { responses <- request("europe.gopl.io") }()
          go func() { responses <- request("americas.gopl.io") }()
          return <-responses // return the quickest response
      }
    
      func request(hostname string) (response string) { /* ... */ }
    //如果使用了无缓存的 channel,那么两个慢的 goroutines 将会因为没有人接收而被永远卡住。这种情况称为 goroutines 泄漏。

select

  1. select 也能够让 Goroutine 同时等待多个 Channel 可读或者可写。
  2. select 是 go 中的一个控制结构。每个 case 必须是一个通信操作,要么是发送要么是接收。
  3. select 随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。
func main() {
	ch := make(chan int)
	select {
	case i := <-ch:
		println(i)
	default:
		println("default")
	}
}

一个简单的规则是:如果一个名字是大写字母开头的,那么该名字是导出的。即可以在外部使用的。

语言特性

  1. 所有导入的包必须在每个文件的开头显式声明,这样的话编译器就没有必要读取和分析整个源文件来判断包的依赖关系。
  2. 禁止包的环状依赖,因为没有循环依赖,包的依赖关系形成一个有向无环图,每个包可以被独立编译,而且很可能是被并发编译。
  3. 编译后包的目标文件不仅仅记录包本身的导出信息,目标文件同时还记录了包的依赖关系。因此,在编译一个包的时候,编译器只需要读取每个直接导入包的目标文件,而不需要遍历所有依赖的的文件。

包声明

例如,math/rand包的每个源文件的开头都包含package rand包声明语句,所以当你导入这个包,你就可以用rand.Intrand.Float64 类似的方式访问包的成员。

package main

import (
    "fmt"
    "math/rand"
)

func main() {
    fmt.Println(rand.Int())
}

导入声明

//第一种方式
    import "fmt"
    import "os"

//第二种方式
    import (
        "fmt"
        "os"
    )

//同时导入两个有着名字相同的包,例如math/rand包和crypto/rand包,那么导入声明必须至少为一个同名包指定一个新的包名以避免冲突。这叫做导入包的重命名。
//导入包的重命名只影响当前的源文件。其它的源文件如果导入了相同的包,可以用导入包原本默认的名字或重命名为另一个完全不同的名字。
    import (
        "crypto/rand"
        mrand "math/rand"
    )

注意

重复分配

在满足下列条件时,已被声明的变量 v 可出现在 := 声明中

  1. 本次声明与已声明的 v 处于同一作用域中(若 v 已在外层作用域中声明过,则此次声明会创建一个新的变量 )。
  2. 在初始化中与其类型相应的值才能赋予 v
  3. 在此次声明中至少另有一个变量是新声明的。
f, err := os.Open(name)
if err != nil {
    return err
}
d, err := f.Stat()
if err != nil {
    f.Close()
    return err
}

GMP 并发模型

先阅读一下这个介绍,有一个简单的认识

在操作系统提供的内核线程之上,Go 搭建了一个特有的两级线程模型。goroutine 代表着可以并发执行的 Go 代码片段。

GMP

  1. M(machine):一个M代表一个内核线程,或称"工作线程"。
  2. P(processor):一个P代表执行一个Go 代码片段所必需的资源。
  3. G(goroutine):一个G代表一个Go代码片段。

一个G的执行需要 P 和 M的支持。一个M在与一个 P 关联之后,就形成了一个有效的 G 运行环境(内核线程+ 上下文环境)。每个 P 都会包含一个可运行的 G 的队列。该队列中的 G 会被依次传递给本地 P 关联的 M,并获得运行时机。

MPG

MPG_KSE

环境

  1. 为了从任意目录运行 GO 相关命令,GO官方工具链安装目录下的bin 子目录路径必须配置在PATH 环境变量中。

  2. GOPATH 此环境变量的默认值为当前用户的HOME 目录下的名为go 文件夹对应的目录路径。

  3. GOPATH 文件夹中的pkg 子文件夹用来缓冲被本地项目所依赖的GO模块。

  4. GOBIN 环境变量用来指定 go install 子命令产生的GO应用程序二进制可执行文件应该存储在何处。他的默认值为GOPATH 文件夹中的bin 子目录所对应的目录路径。GOBIN路径需配置在PATH 环境变量中,以便从任意目录运行这些GO应用程序。

    /* 
      /home/machunyu/go       默认的 GOPATH 目录
      /home/machunyu/go/pkg   来缓冲被本地项目所依赖的GO模块/home/machunyu/go/bin   二进制的可执行文件
    
    	1. $GOPATH 用来指定当前工作目录
    	2. GOROOT 用来指定Go的安装目录
    	3. 工作区目录
          GOPATH/
            src/   		src子目录用于存储源代码
            bin/			bin子目录用于保存编译后的可执行程序
            pkg/			pkg子目录用于保存编译后的包的目标文件
    /*

其他

竞态条件探测器

  1. -race 命令行标志,可以检查。
  2. 只能在运行代码实际触发竞态条件时才检测竞态条件。
go test -race mypkg //测试程序
go run -race mysrc.go //编译并运行程序
go build -race mycmd //构建命令
go install -race mypkg //安装程序包。

例子

func main() {
	start := time.Now()
	var t *time.Timer
	t = time.AfterFunc(randomDuration(), func() {
		fmt.Println(time.Now().Sub(start))
		t.Reset(randomDuration())
	})
	time.Sleep(5 * time.Second)
}

func randomDuration() time.Duration {
	return time.Duration(rand.Int63n(1e9))
}