Go语言 Golang学习笔记

Golang笔记

这个学期选了Go语言与分布式开发课程,这篇博客是准备期末考试时所总结的笔记,内容比较基础。参考了github上的the-way-to-go。

  • Golang笔记
    • 第一部分:基本语法
      • 变量
      • 常量
      • 条件语句if
      • 条件语句switch
      • 循环语句for
      • goto
      • break
      • 整型
      • 复数
      • 字符类型
      • 字符串类型
    • 第二部分:集合
      • 数组
      • 切片slice
      • 映射map
    • 第三部分:函数
      • 基本格式
      • 可变参数函数
      • 函数类型
      • 匿名函数
      • 闭包
      • defer关键字
    • 第四部分:结构体
      • 基本特点
      • 基本格式
      • 初始化
      • 访问方法
      • 匿名字段
      • 嵌套结构体
      • 方法
    • 第五部分:接口
      • 接口的命名格式
      • 匿名接口
      • 接口值
      • 接口的注意事项
      • 嵌套接口
      • 接口类型的断言
      • 类型判断:type-switch
      • 反射
      • 结构体的反射
      • 通过反射调用函数
    • 第六部分:错误处理
      • error 接口
      • 函数返回错误处理模式
      • 错误信息
      • 使用goto集中处理错误
      • panic
      • panicking终止过程
      • recover
      • 错误处理总结
    • 第七部分:并发
      • 并发和并行
      • 进程、线程和协程
      • channel通道
      • 定时器Timer
      • 定时器Ticker
    • 第八部分:包

第一部分:基本语法

变量

  1. 声明了一定要使用,否则会报错

  2. var name dataType:立即分配内存空间,初始化为零值

  3. var name = value

  4. 可在函数内部用如下形式初始化并赋值name := value

  5. var a int
    var b = 10
    c := "yy"
    fmt.Println(a)//0
    fmt.Println(b)//10
    fmt.Println(c)//yy
    

常量

  1. 常量的声明和赋值要同时进行const name = value
  2. 常常使用枚举
const (
    name1 = iota
    name2
    name3
)
fmt.Println(name1)//0
fmt.Println(name2)//1
fmt.Println(name3)//2

条件语句if

  1. if条件语句中,条件外没有()

    if 条件{
    	statement
    }
    
  2. go语言中不支持三元条件语句

  3. if可带一个初始化子句,用;和条件子句分隔开

if i:=5;i<=10{//使用【分号】将条件子句分开
	statement
}

条件语句switch

  1. switch语句可以带初始化子句,用;分隔开

    switch score:=78;math.floor(score/10){//使用【分号】将条件子句分开
    	case 10:statement
    	case 9:statement
    	case 8:statement
    	case 7,6:statement//case后可以有多个值
    	default:statement
    }
    
  2. break可以省略

循环语句for

  1. for后没有()

    for i:=1;i<=10;i++{
    	statement
    }
    
  2. 初始子句、判断子句、操作子句任意一个被省略了,;都不能被省略

  3. 当只有判断子句时,可以不使用分号

  4. 三个子句都省略时,表示这时一个无限循环

goto

goto end// 【跳至】end,执行end之后的语句a
end:
	...a

break

end:
	...a
	break end//【跳出】end语句,执行之后的语句b
...b

整型

  1. intuint分别对应有符号和无符号
  2. 不同类型的整型数据之间无法进行比较,也不能直接进行运算

复数

  1. 由两个float组成,complex64:两个float32
  2. complex(float,float)
  3. real():获取实部
  4. image():获取虚部

字符类型

  1. byte:代表ASCII
  2. rune:代表Unicode
  3. 不是char

字符串类型

字符串String中:一个ASCII占一个字节、一个中文占三个字节

第二部分:集合

数组

  1. var name [length]dataType:全部初始化为零值

  2. name := [length]dataType{value1...}:有初始化值的数组

  3. name := [...]dataType{value1...}:根据值的数量来确定数组的长度

  4. name := [length]dataType{index:value...}:将相应位置的数组初始化为相应的值,其余初始化为零值

  5. 数组的复制,以及函数传参的调用都是值拷贝

  6. 遍历

    for i,v := range arr{// i是数组中的下标序号,v是数组相应序号的值
    	statement//只使用i或v中的一个会出现编译错误
    }
    for _,v := range arr{
    	statement//可以只使用v
    }
    

切片slice

  1. 切片在声明时,不能给定底层数组的长度,否则会变成数组声明。

    //根据指定的数组创建切片
    a := [5]int{1,2,3,4,5}// 只创建数组
    b := [...]int{1,2,3,4,5}// 只创建数组
    silce := a[1:2] //创建切片
    var silce2 := a[2,3] //创建切片
    
    //同时创建数组和切片
    silce := []int{1,2,3,4,5}
    
  2. 声明语法var name []dataType

  3. 在声明之后创建切片变量,但底层数组指针是nil,所以切片也是nil

  4. 也可使用make函数来声明和创建切片

    i:=make([]int,5,5)//[0,0,0,0,0]     i := make([]type, len, cap)
    

    new函数用于普通类型数据的内存分配

    make函数用于slice、map、channel类型数据的内存分配

  5. len()返回slice的长度;cap()返回slice的底层数组的容量

  6. 多个切片可以共享同一个底层数组

  7. 使用函数append可以扩展slice,同时也会直接覆盖掉底层数组上相对应的数据;使用函数append可以合并两个slice

    slice = append(slice, 100)//扩展slice,在slice尾部添加一个100,同时覆盖原数组相应位置的值
    newSlice = append(slice1,slice2...)//合并两个slice
    
  8. 切片的函数传参是引用传递

  9. 多维切片:pls := [][]int{{1,2,3},{4,5},{6},}每行的元素的个数可以不同

映射map

  1. map是引用类型,不支持==操作(除了和nil)

    map中的键只支持值类型(支持==操作)

    map中的值不做限制,但是要求所有的值的类型都是同一种

    go中的map的底层是哈希数组链表

  2. var name map[keyType]valueType:只声明不初始化,不能添加元素,并且此时name==nil

    var m map[string]int //只声明
    fmt.Println(m == nil) //true
    
  3. name2 := name[keyType]valueType:用字面量或make函数进行初始化后可以添加元素,name2[key]=value

    m := map[string]int{}//字面量
    fmt.Println(m == nil)//false
    m["yy"] = 22//添加元素
    fmt.Println(m["yy"])//22
    
    m := make(map[string]int)//make函数
    fmt.Println(m == nil)//false
    m["yy"] = 22//添加元素
    fmt.Println(m["yy"])//22
    
  4. 也可使用make函数来初始化name := make(map[keyType]valueType)

  5. 也可以在初始化的同时进行赋值:

    m := map[String]int{//这时候一定要使用字面量
    	"1":1,
    	"2":2,
    }
    
  6. 映射元素的查找:value,exist = m["1"],如果对应的key值存在,那么exist为true,并且在value中返回相对应的值;否则exist为false

    m := map[string]int{
    	"yy": 22,
    }
    if v, exist := m["yy"]; exist {//exist==true
    	fmt.Println(v)//22
    }
    if v, exist := m["ljl"]; exist {//exist==fasle
    	fmt.Println(v)
    }
    
  7. 可以使用range遍历map,但是不保存顺序

    for key,value := range m{
    	statement
    }
    
  8. 删除键值对

    delete(m,"1")//将m中的键“1”对应的键值对删除;如果对应的键不存在,那么什么都不发生
    
  9. map的键的值也可以是map,即映射的映射

    m := map[String]map[String]int{
    	"1":{
    		"2":2,
    		"3":3,
    	},
    }
    // m["1"]["2"]=2
    

第三部分:函数

基本格式

func funcName(paramList)(returnList){// 返回值列表可以省略
	codind...
}
func add(a,b int) int {// a和b都是int,多个相邻相同类型参数可以使用简写
	return a+b
}
//返回值可以有变量名
//go不支持函数的重载

可变参数函数

函数可以传递可变参数:形参的数目可变

  • 格式:在需要声明可变参数函数时, 只能在参数列表的最后一个参数类型之前加上省略符号“…”,这表示:该函数会接收任意数量的该类型参数

    func getSum(vals...int) int {//在参数列表的最后一个参数类型之前加上省略符号“...”
        sum := 0
        for _,v:= range vals{//不定参数的形参在函数内是【切片】
            sum += v
        }
        return sum
    }
    
  • 如果原始参数已经是切片类型, 则需要在最后一个参数后加上省略符。 下面的两段代码作用相同:

    int result1 = getSum(1,2,3,4)
    
    int[] values = []int{1,2,3,4}
    int result2 = getSum(values...)//原始参数已经是切片,需要在需要给切片添加省略号
    
    

函数类型

函数类型又称函数签名。

  1. 显示函数类型:fmt.Printf("%T\n",funcname)

    fmt.Printf("%T\n", getSum)//func(int, ...int) int
    
  2. 函数类型包括形参列表和返回值列表

  3. 函数类型的零值是nil

  4. 标准定义的函数名为常量,不可修改指向

  5. 函数是第一公民,函数变量可赋值、传参等

匿名函数

匿名函数相当于函数字面量,可以使用函数变量的地方就可以使用匿名函数

//匿名函数直接调用
func(a,b int)int{
    return a-b
}(5,4)

//匿名函数赋值给函数变量
var sum = func(a,b int)int{
    return a+b
}
sum(5,4)

//函数作为返回值
func getFun(op string) func(a,b int)int{
    return func(a,b int)int{
        return a+b
    }
}

闭包

  1. 每次调用函数都会为局部变量分配内存
  2. 每次调用闭包都会对影响局部变量

defer关键字

关键字defer允许我们推迟到函数返回之前(或任意位置执行 return 语句之后)一刻才执行某个语句或函数

  1. defer实参:在注册defer函数 时,会把当时的实参值传递给形参,后续实参的变化不影响函数结果,知道函数返回时(或者执行return)才进行执行

    a := 5
    defer fmt.Println(a)
    a = 10
    fmt.Println(a)
    return  
    /*
    10
    5
    */
    
    
  2. 使用多个defer时,这些defer 调用以先进后出(FILO)顺序在函数返回前被执行

    name := "Naveen"
    fmt.Printf("Original String: %s\n", string(name)) //Original String: Naveen
    fmt.Printf("Reversed String: ")
    for _, v := range []rune(name) {
    	defer fmt.Printf("%c", v)//Reversed String: neevaN
    }
    

第四部分:结构体

基本特点

  1. 结构体支持嵌套
  2. 结构体的存储空间连续,按声明时的顺序存放

基本格式

type Employee struct{
	firstName,lastName string//没有逗号
	age,salary int
}

//匿名类型结构(直接创建结构变量):用var代替type
var Employee struct{
	firstName,lastName string
	age,salary int
}

//结构体中的字段除了名字和类型外,还可以有一个可选的标签(tag)用于描述结构体字段的信息
type Employee struct{
	firstName,lastName string "姓名"
	age,salary int
}

初始化

  1. 利用字段名初始化,不用按照顺序,没有被初始化的值默认为零值

    e := Employee{
        firstName: "y",//使用逗号
        age: 22,
        lastName: "y",
    }
    
  2. 利用字面量进行初始化,需要按照顺序并且需要全部设置

    e := Employee{"y","y",22,0}
    
  3. 可以使用new,返回指向该结构体变量的指针,初始值全部为零值

    s := new(student)//s是指向student的指针
    s.name = "yy"
    s.age = 22
    fmt.Println(*s)
    

访问方法

  1. e := Employee{"y","y",22,0}
    fmt.Println(e.age)
    
  2. e := &Employee{"y","y",22,0}
    fmt.Println((*e).age) //不支持->
    

匿名字段

结构体字段也可以省略字段名,字段名默认为对应数据类型名称(数据类型不能重复

type Student struct{
	int
	string
}
s := Student{22,"yy"}
fmt.Println(s.int)

嵌套结构体

type Address struct {
    city, state string
}
type Person struct {
    name    string
    age     int
    address Address//嵌套结构体
}
func main() {
    var p Person
    p.name = "Naveen"
    p.age = 50
    p.address = Address{
        city:  "Chicago",
        state: "Illinois",
    }
    fmt.Println("Name:", p.name)
    fmt.Println("Age:", p.age)
    fmt.Println("City:", p.address.city)//调用嵌套结构体
    fmt.Println("State:", p.address.state)
}

//使用匿名子结构字段,在没有重名的情况下,使得子结构体中的字段可以像父结构体中的字段一样被访问
type Address struct {
    city, state string
}
type Person struct {
    name    string
    age     int
    Address//匿名子结构体
}
func main() {
    var p Person
    p.name = "Naveen"
    p.age = 50
    p.address = Address{
        city:  "Chicago",
        state: "Illinois",
    }
    fmt.Println("Name:", p.name)
    fmt.Println("Age:", p.age)
    fmt.Println("City:", p.city)//使得子结构体中的字段可以像父结构体中的字段一样被访问
    fmt.Println("State:", p.state)
}

方法

方法是对具体类型行为的封装,本质上是绑定到该类型的函数

【方法】的格式是:func (t type) funcName(paramList)(returnList){coding...}

【函数】的格式是:func funcName(paramList)((returnList){coding...}

例子:

type Employee struct {
    name,currenc	string
    salary   int
}
//定义方法
func (e Employee) displaySalary() {
    fmt.Printf("Salary of %s is %s%d", e.name, e.currency, e.salary)
}
//如果是函数则是func displaySalary(e Employee){}
func main() {
    emp1 := Employee{
        name:     "Sam Adolf",
        salary:   5000,
        currency: "$",
    }
    emp1.displaySalary()//Salary of Sam Adolf is $5000
}

//方法可以使用函数进行代替,但是使用方法可以解决函数不能重载的问题
func displaySalary(e Employee) {
    fmt.Printf("Salary of %s is %s%d", e.name, e.currency, e.salary)
}

func main() {
    emp1 := Employee{
        name:     "Sam Adolf",
        salary:   5000,
        currency: "$",
    }
    displaySalary(emp1)//Salary of Sam Adolf is $5000
}

注意事项:

  1. 方法接收者的本质是形参,需要在内存中复制一份对象,所以方法修改对象属性将不能成功

  2. 方法提升:匿名子结构的方法可以像父结构方法一样被使用

    package main
    
    import (
    	"fmt"
    )
    
    type add struct {
    	country string
    	city    string
    }
    
    type student struct {
    	name    string
    	age     int
    	address add
    }
    
    func (a add) shuchu() {
    	// fmt.Printf(s.name + " " + s.age + " " + s.address.country + " " + s.address.city)
    	fmt.Printf("%s %s", a.country, a.city)
    }
    
    func main() {
    
    	var s student
    	s.name = "yy"
    	s.age = 22
    	s.address = add{
    		country: "china",
    		city:    "ningbo",
    	}
    	s.address.shuchu()
    }
    
  3. 方法并非结构体专有,所有自定义类型都可以定义方法

第五部分:接口

接口的命名格式

type interfaceName interface{
    methodName(paramList)(returnList)
  	otherMethod
}

匿名接口

匿名接口的命名格式:

interface{
	methodName(paramList)(returnList)
 	otherMethod
}

接口值

Go中的接口是可以有值的:var ai interfaceName,本质是一个指针,初始值是nil。实现接口interfaceName的变量可以赋值给ai

接口的注意事项

  1. 类型不需要显式声明它实现了某个接口:接口被隐式地实现。多个类型可以实现同一个接口
  2. 实现某个接口的类型,除了实现接口方法外,可以有其他的方法
  3. 一个类型可以实现多个接口
  4. ???接口类型可以包含一个实例的引用, 该实例的类型实现了此接口(接口是动态类型)

使用接口的例子

package main

import "fmt"

type Shaper interface {//定义一个接口Shaper,接口内有一个方法Area()
	Area() float32
}

type Square struct {//定义一个结构体Square
	side float32
}

func (sq *Square) Area() float32 {//结构体的方法,定义了方法Area(),实现了接口Shaper
	return sq.side * sq.side
}

func main() {
	sq1 := new(Square)//使用new返回一个指向Square的指针
	sq1.side = 5

	var areaIntf Shaper//定义一个接口变量
	areaIntf = sq1//将实现了Shaper接口的结构体变量sq1复制给接口变量areaIntf
	fmt.Printf("The square has area: %f\n", areaIntf.Area())//25
}

嵌套接口

一个接口可以嵌套另一个或者多个接口

type ReadWrite interface {//ReadWrite接口
    Read(b Buffer) bool
    Write(b Buffer) bool
}

type File interface {//File接口中嵌套了ReadWrite接口
    ReadWrite
    Close()
}

接口类型的断言

v,ok := interfaceVar.(typeName)
/*
	判断某个接口变量,是否为某个类型,interfaceVar必须是接口变量
	是,则返回true和值
	否,则返回false和零值
*/

接口类型断言的例子

package main

import (
	"fmt"
	"math"
)

type Square struct {//结构体Square
	side float32
}

type Shaper interface {//接口Shaper
	Area() float32
}

func main() {
	var areaIntf Shaper//声明一个接口变量
	sq1 := new(Square)//一个结构体变量
	sq1.side = 5

	areaIntf = sq1//初始化接口变量
	//判断结构变量areaIntf是否为*Square
	if t, ok := areaIntf.(*Square); ok {
		fmt.Printf("The type of areaIntf is: %T\n", t)
	}
}

func (sq *Square) Area() float32 {//结构体变量Square实现了接口Shaper
	return sq.side * sq.side
}

类型判断:type-switch

func findType(i interface{}) {//空接口,所有类型都实现了空接口
  switch t := i.(type) {//i是一个接口变量,注意:.(type)只能用于switch表达式i是一个接口变量
    case string:
        fmt.Printf("string and value is %s\n", i.(string))
    case int:
    	fmt.Printf("int and value is %d\n", t)//用t来代替i.(int),通过t还可以调用接口中的方法
    default:
        fmt.Printf("Unknown type\n")
    }
}
findType("Naveen")
findType(77)
findType(89.98)
//string and value is Naveen 
// int and value is 77 
// Unknown type

反射

  • 一个变量最基本的两个属性就是:类型和值。

  • 反射可以在运行时检查变量的类型和值,是元编程的一种形式,在没有源代码时帮助调试程序。

  • 反射包“reflect”通过空接口获取变量的类型和值

func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
/*
	reflect.TypeOf(x):返回变量x的类型
	reflect.ValueOf(x):返回变量x的值
*/

reflect.Typereflect.Value 都有许多方法用于检查和操作它们:TypeValue 都有 Kind 方法返回一个常量来表示类型:UintFloat64Slice 等等。

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var x float64 = 3.4
	fmt.Println("type:", reflect.TypeOf(x))//type: float64
	v := reflect.ValueOf(x)
	fmt.Println("value:", v)//value: 3.4
	fmt.Println("type:", v.Type())//type: float64
	fmt.Println("kind:", v.Kind())//kind: float64
}

结构体的反射

package main

import (
	"fmt"
	"reflect"
)

type TagType struct {
	field1 bool   "1"
	field2 string "2"
}

func main() {
	tt := TagType{true, "Barak Obama"}
	for i := 0; i < 2; i++ {
		refTag(tt, i)
	}
}

func refTag(tt TagType, ix int) {
	ttType := reflect.TypeOf(tt)//获取tt的类型
	ixField := ttType.Field(ix)//结构体变量可以通过Field来索引结构体的各个字段
	fmt.Printf("%v\n", ixField.Tag)//输出各个字段的Tag属性
}
//1
//2

通过反射调用函数

func add(a, b int) int {
	return a + b
}
// 将 函数 包装为反射值对象
funcValue := reflect.ValueOf(add)
// 生成函数参数, 传入两个整型值
paramList := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)}
// 反射调用函数
retList := funcValue.Call(paramList)
// 获取第一个返回值, 取整数值
fmt.Println(retList[0].Int())//30

第六部分:错误处理

  • GO 语言里没有异常机制,只有错误处理,错误通过函数的多返回值来处理

  • GO 语言的错误主要有:

    1. 编译错误
    2. 运行时错误
    3. 逻辑错误
  • GO错误处理方式:

    1. 错误可处理,通过函数返回错误进行处理
    2. 错误不可处理,通过panic抛出错误,退出程序

error 接口

type error interface {
	Error() string
}

函数返回错误处理模式

  • 可能出错的函数的最后一个返回值为错误类型

  • 检查该返回值是否为nil:是,则没有出错;否,则出错。

  • f, err := os.Open("/test.txt")//os.Open可能出错,最后一个返回值为错误类型
    if err != nil {//检查err是否为nil,不是nil则出错
      fmt.Println(err)
      return
    }
    fmt.Println(f.Name(), "opened successfully")//是nil,则没有出错
    

错误信息

  • 错误至少包含文本说明信息

  • 部分错误通过结构体更加详尽地说明错误

    type PathError struct {//定义一个结构体
        Op   string
        Path string
        Err  error
    }
    //结构体实现了Error()接口
    func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
    
  • 例子:

    f, err := os.Open("/test.txt")
    if perr, ok := err.(*os.PathError); ok {
      fmt.Println("File at path", perr.Path, "failed to open")
      return
    }
    fmt.Println(f.Name(), "opened successfully")
    
  • 有些错误则通过方法可以提供更多的信息

    addr, err := net.LookupHost("www.abc.com")
    if err, ok := err.(*net.DNSError); ok {
    	if err.Timeout() {//调用方法
    		fmt.Println("operation timed out")
    	} else if err.Temporary() {//调用方法
    		fmt.Println("temporary error")
    	} else {
    		fmt.Println("generic error: ", err)
    	}
    		return
    }
    fmt.Println(addr)
    

使用goto集中处理错误

// 正常处理代码
err := firstCheckError()
if err != nil {
    fmt.Println(err)
    exitProcess()
    return
}
err = secondCheckError()
if err != nil {
    fmt.Println(err)
    exitProcess()
    return
}

// 使用goto集中处理错误
err := firstCheckError()
if err != nil {
    goto onExit
}
err = secondCheckError()
if err != nil {
    goto onExit
}
onExit:
	fmt.Println(err)
	exitProcess()

panic

  • 当遇到不可恢复的错误状态,导致程序不能继续执行,引发panic。该错误提供RuntimeError()方法用于区别普通错误

  • 引发panic的两种情况:

    1. 主动调用panic 函数,会产生一个运行时错误。panic 函数接收一个任意类型的参数,通常是字符串,在程序死亡时被打印出来。

      package main
      
      import "fmt"
      
      func main() {
      	fmt.Println("Starting the program")
      	panic("A severe error occurred: stopping the program!")
      	fmt.Println("Ending the program")
      }
      
    2. 程序运行时出现未处理错误自动触发,比如当发生像数组下标越界或类型断言失败等运行时错误时,Go 运行时会自动触发panic

  • 不应通过调用panic函数来报告普通的错误,而应该只把它作为报告致命错误的一种方式

panicking终止过程

在多层嵌套的函数调用中调用panic,可以马上中止当前函数的执行,所有的 defer 语句都会保证执行,并把控制权交还给接收到 panic的函数调用者。这样向上冒泡直到最顶层,并执行(每层的)defer,在栈顶处程序崩溃,并在命令行中用传给panic的值报告错误情况。

recover

  • panic一旦被引发就会导致程序崩溃,但无法保证程序不会发生任何运行时错误。

  • recover专用于“拦截”运行时panic,让进入恐慌的程序恢复过来并重新获得流程控制权。

  • recover 可以阻止panic继续向上传递

  • 为确保捕获panicrecover 必须在defer函数中执行

func protect(g func()) {
	defer func() {
		log.Println("done")
		// 即使出现panic,printF也会继续执行
		if err := recover(); err != nil {
			log.Printf("run time panic: %v", err)
		}
	}()
	log.Println("start")
	g() //   possible runtime-error
}

错误处理总结

  1. 程序发生的错误导致程序不能容错继续执行,应主动调用panic或由运行时抛出panic
  2. 程序发生错误,但能容错继续执行的,正常情况用错误返回值,运行时错误非关键分支 用recove 捕获panic

第七部分:并发

并发和并行

  • 并行:同一时刻同时运行
  • 并发:同一时间段内同时运行。底层是分时操作。

进程、线程和协程

  • 进程:是程序在内存中运行时,操作系统对其进行资源分配和调度的独立单位
  • 线程:是进程的一个执行实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。一个进程至少有一个线程。
  • 协程:是轻量级的线程,一个线程可以拥有多个协程。协程不被操作系统内核管理,而完全由程序控制,因此没有线程切换的开销。

channel通道

  • 通道是一种特殊的类型,同时只能有一个 goroutine 访问通道进行发送和获取数据;

  • 通道是一个队列,遵循先入先出(FIFO)的规则;

  • 通道默认是阻塞的,使goroutine有效通信;

  • 通道是引用类型,需要使用chan关键字内置函数make 进行创建

  • 通道写入和读取使用 <- 运算符

    1. 写入 :通道<-变量
    2. 读取: 变量<-通道
  • 缓冲通道

    通道包括无缓冲通道和有缓冲通道:

    1. 无缓冲通道:只能存储一条消息: make(chan datatype)

    2. 有缓冲通道 make(chan datatype,capacity),可以根据capacity参数存储多条消息,按FIFO的原则进行读取

      func receiver(c chan string) {
          for msg := range c {
              fmt.Println(msg)
          }
      }
      
      func main() {
          messages := make(chan string, 2)//创建一个有缓冲通道messages,容量=2
          messages <- "hello"
          messages <- "world"
          go receiver(messages)   
          time.Sleep(1e9)
      }//hello world
      
    3. 获取缓冲通道的状态:len() :获取通道当前缓存数;cap():获取通道缓存容量

      ch := make(chan string, 3)
      ch <- "1"
      ch <- "2"
      fmt.Println(cap(ch))              // 3
      fmt.Println(len(ch))              // 2
      fmt.Println("read value: ", <-ch) //1
      fmt.Println(cap(ch))              // 3
      fmt.Println(len(ch))              // 1
      
  • 缓冲和阻塞

    • 无缓冲通道,写入等待读取,读取等待写入,在双方准备好之前是阻塞的

    • 有缓冲通道,在通道已满时:写入会等待;通道已空时候:读取会等待

  • 死锁

    • 通道无数据或未写入时,未启动写入协程 就进行读取

    • 通道已满时,未启动读取协程 就继续写入

  • 单向通道

    • 通道默认为双向的,单向通道只能用于发送或接收数据

    • 所谓单向通道只是对通道作为函数参数的一种使用限制。通常先创建双向通道,在函数形参中利用<-运算符修饰通道,使之变为只读或只写通道

    • func pump(ch chan<- int)  //只写
      func pull(ch <-chan int) //只读
      
  • 关闭通道

    • 关闭通道使用内置函数close(),实际上是关闭写入,即发送者告诉接收者不会再有数据发往通道

    • 接收者能够在通道接收数据的同时,获取通道是否已关闭的参数

    • func producer(chnl chan int) {  
          for i := 0; i < 10; i++ {
              chnl <- i
          }
          close(chnl)
      }
      func main() {  
          ch := make(chan int)//建立无缓冲通道
          go producer(ch)
          for {
              v, ok := <-ch//接收数据的同时,获取通道是否已经关闭的参数
              if ok == false {
                  break
              }
              fmt.Println("Received ", v, ok)
          }
      }
      
    • for range 语句能自动判断通道是否已关闭

      func producer(chnl chan int) {  
          for i := 0; i < 10; i++ {
              chnl <- i
          }
          close(chnl)
      }
      func main() {  
          ch := make(chan int)
          go producer(ch)
          for v := range ch {
              fmt.Println("Received ",v)
          }
      }
      

定时器Timer

一次性定时器:定时器只计时一次,结束便停止

定时器Ticker

周期性定时器:定时器周期性进行计时,除非主动停止,否则将永久运行

第八部分:包

  • GO使用包来组织源代码和代码编译,实现代码复用

  • 任何源代码必须属于某个包,同时源码文件的第一行有效代码必须是package pacakgeName 语句,声明自己所在的包。

  • 包名为 main 是应用程序的入口包,编译不包含 main 包的源码文件时不会得到可执行文件

  • 一个文件夹下的所有源码文件只能属于同一个包,同样属于同一个包的源码文件不能放在多个文件夹下

  • GOROOT:GO语言环境在计算机的安装位置,包含GO标准库的源代码

  • GOPATH GO语言工作目录,可以有多个。GOPATH指定的工作目录通常包括3个子目录:

    1. src : 代码保存目录,每个包一个文件夹
    2. bin:产生的二进制可执行文件存放目录
    3. pkg: 非main包生成后存放路径
  • GOPATH太不方便,在项目目录下用go.mod 文件来记录依赖包具体版本,方便依赖包、源代码和版本控制的管理。go.mod文件的内容:

    1. module:指定包的名字
    2. go:用于标识当前模块的 Go 语言版本,值为初始化模块时的版本
    3. require:指定的依赖项模块
    4. replace:可以替换依赖项模块
    5. exclude:可以忽略依赖项模块
  • 包的导入:

    • 使用 import "包的路径"导入使用的包

    • 包的路径使用/ 进行分隔:GOROOT/src/或GOPATH/src/或 go.mod代表的目录后面包的存放路径,比如"fmt" 位于GOROOT/src/fmt 目录,"crypto/sha256" 位于GOROOT/src/crypto/sha256 目录

    • 单行导入

      import "crypto/sha256"
      import "fmt"
      import "math/big"
      
    • 多行导入

      import (//没有逗号
      	"crypto/sha256"
      	"fmt"
      	"math/big"
      )
      
  • 包的引用格式:

    • 标准引用格式

      import "fmt"
      fmt.Printf("Hello world!")
      
    • 自定义别名引用格式

      import F "fmt"
      F.Printf("Hello world!")
      
    • 省略引用格式

      import ."fmt"
      Printf("Hello world!")
      
    • 匿名引用格式

      引用包,在代码中却没有进行使用,编译器会报错。所以,如果只是希望执行包初始化的init函数,而不使用包内部的数据时,可以使用匿名引用格式

      import _ "fmt"
      
  • 包内标识符的导出

    • 一个包里的标识符(如类型、变量、常量等)要被外部访问,需将要导出的标识符的首字母大写
    • 在被导出的结构体或接口中,如果它们的字段或方法首字母是大写外部可以访问这些字段和方法

你可能感兴趣的:(其他,go,golang)