golang初学笔记

学习网站

A Tour of Go

Go by Example

Learn Go with tests - learn-go-with-tests (gitbook.io)

基本数据类型

Go基本数据类型_TCatTime的博客-CSDN博客_go 数据类型

基本语法

Go语言基础语法-Golang基础语法-Golang中国 (qfgolang.com)

声明变量

声明方式 特点
var 数据名称 type 数据值为默认值
数据名 := 值 :=让编译器根据值自动判断数据类型)省去关键字var

变量不可重复声明!

命名首字母是否大小写决定是否能够跨包调用,包内定义方法的方法名若是大写则表名可以被其他包调用,否则只能包内调用,类名,属性名同理;

特性

import

  1. import _ "包路径"因为go语言存在不使用即声明或直接导入则无法通过编译的规则,所以为了满足某些情况下仅需要调用某包的init函数的情景,可以进行匿名导入,即在包路径前添加”_ “ ;
  2. import 别名 "包路径",起别名也是在包路径前添加”别名 “;
  3. import . "包路径",将包内所有方法导入到本包中,这样调用导入包中方法时,可以直接通过方法名调用;

iota

在 Go 中枚举常量是使用 iota 枚举器创建的,在功能上,iota 关键字表示从 0 开始的整数常量;在作用上可以简化使用自动递增数字的常量定义,非常方便。

以前定义一个枚举值:

1
2
3
4
5
const (
a = 0
b = 1
c = 2
)

Go 有了 iota 关键字后:

1
2
3
4
5
const (
a = iota
b
c
)

对应的值结果:

1
2
3
a=0
b=1
c=2

甚至还可以跳着来:

1
2
3
4
5
6
const (
a = iota
_
b
c
)

对应的值结果:

1
2
3
a=0
b=2
c=3

也可以玩出花来:

1
2
3
4
5
6
const (
bit0, mask0 = 1 << iota, 1<<iota - 1//x << y表示x左移y位
bit1, mask1
_, _
bit3, mask3
)

对应的值结果:

1
2
3
4
bit0 == 1, mask0 == 0  (iota == 0)
bit1 == 2, mask1 == 1 (iota == 1)
(iota == 2, unused)
bit3 == 8, mask3 == 7 (iota == 3)

defer关键字

  1. 用于修饰方法,执行代码时会将defer修饰的方法输出结果依次压入栈中,在执行时完中括号内所有内容后再按出栈顺序输出结果;

  2. deferreturn的执行顺序问题:

    结论:同一方法内,被defer修饰的语句,其执行顺序在return动作之后;

声明数组

[数组长度]type{值}

若值的个数小于数组长度,则剩余元素默认值为0,若大于则无法通过编译,且不同长度的数组无法互相传递;

切片(动态数组)

​ 当数组声明时大括号内没有声明数组长度,则数组为动态数组;

声明方式
  1. 声明方式一:

    1
    2
    var slice1 []int 
    slice2 := []int

    声明slice是一个动态数组,但是并没有给slice分配空间,要使用slice1需要先用make([]int, len)为其开辟空间 PS:用:= 代替 = 则可省略关键字var;

  2. 声明方式二:

    1
    slice1 := make([]type, len) 

    其中len为数组的初始长度;

  3. 声明方式三:

    1
    myArray[] := []int{1,2,3,4} 
使用方式
  1. 传递方式:切片的传递是引用传递,方法内对形参数组元素的修改会生效在实参上,且不同长度的动态数组都可以进行引用传递;

  2. for range 遍历:

    1
    for index,value := range slice{}
  3. len()和cap()函数:len()可以获取切片当前长度,cap()可以获取切片当前容量,容量即切片实际开辟空间,长度则为读取切片的索引最大值,且cap也是动态扩容时的cap追加的数量(重要扩容机制)

  4. append(slice, key):向切片末尾添加元素;

  5. 取切片元素 s1 := slice[0:2],表明取切片的第1至第2个元素并赋值给s1,区间是左闭右开;

  6. copy(copySlice2, slice1),将slice1的内容copy到copySlice2中,为值传递

map

底层

GO中对map排序_雨雨不怕雨的博客-CSDN博客_go map 排序

GO语言中,map是哈希表,能够将特定类型的key映射到特定类型的Value上。在查询Map里面的内容时,其时间复杂度为O(1)非常高效。但其存储并不是线性的,遍历输出时,也没有顺序可言。

声明方式
  1. 声明方式一:

    1
    var myMap map[keyType]valueType

    mapslice的特性一致,可以动态开辟空间,声明若采用此方式,则需要通过make(myMap, len)为其开辟空间进行初始化后才能使用;

  2. 声明方式二:

    1
    myMap := make(map[string]string, len)

    其中len可以省略,省略编译器会默认给myMap分配一定空间;

  3. 声明方式三:

1
2
3
4
5
6
7
8
9
10
11
myMap := map[string]string {
"key1": "value1",
"key2": "value2",
"key3": "value3"
}

var myMap = map[string]string {
"key1": "value1",
"key2": "value2",
"key3": "value3"
}
使用方式
  1. 传递方式:同切片,为引用传递

  2. 添加:

    1
    2
    3
    myMap["key1"] = "value1"
    myMap["key2"] = "value2"
    myMap["key3"] = "value3"
  3. 遍历:

    1
    2
    3
    4
    //for range遍历
    for key, value := range myMap{}
    //print
    printMap(myMap)
  4. 删除:

    1
    delete(myMay, "key1")
  5. 修改:

    1
    myMap["key1"] = "value1_new"
  6. 查找:

    1
    2
    3
    4
    5
    6
    7
    //演示map查找
    val , ok :=cities["no2"]
    if ok{
    fmt.Printf("找到了 值为%v",val)
    }else{
    fmt.Printf("没有找到")
    }

struct,封装

  1. 给已定义数据类型起别名

    1
    type myStruct int
  2. 定义结构体

    1
    2
    3
    4
    5
    type myStruct struct {
    Key int
    Value string
    Length int
    }
  3. getter和setter方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    type myStruct struct {
    Key int
    Value string
    Length int
    }
    func (this *myStruct) GetKey() {
    fmt.Println("Key = " this.Key)
    }
    func (this *myStruct) SetKey(newKey int) {
    this.Key = newKey
    }
  4. 创建对象

    1
    demo := myStruct{Key:11, Value:"test", Length:10}//属性名亦可省略不写

继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func(this *myStruct) Sort(){
fmt.Println("this is Sort of myStruct")
}

type superStruct struct{
myStruct //superStruct继承了myStruct的方法

Height int
}

//继承并重写父类myStruct的方法
func(this *superStruct) Sort(){
fmt.Println("this is Sort of superStruct")
}

//创建子类对象 方式一
demo1 := superStruct{myStruct{11,"test",10},120}
//创建子类对象 方式二
var demo2 superStruct
demo2.Key = 11
demo2.Value = "test"
demo2.Length = 10
demo2.Height = 20

多态

  1. interface

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    //interface的本质是一个指针,指向结构体及其内部的函数列表
    type AnimalIf interface{
    Sleep()
    Eat()
    GetColor() String//获取动物颜色
    }
    //具体的类
    type Cat struct{
    Color string
    }
    func(this *Cat) Sleep(){
    fmt.Println("Cat is sleeping")
    }
    func(this *Cat) Eat(){
    fmt.Println("Cat is eating")
    }
    func(this *Cat) GetColor() string{
    return this.color
    }

    type Dog struct{
    Color string
    }
    func(this *Dog) Sleep(){
    fmt.Println("Dog is sleeping")
    }
    func(this *Dog) Eat(){
    fmt.Println("Dog is eating")
    }
    func(this *Dog) GetColor() string{
    return this.color
    }

    var animal AnimalIf
    //因为interface本质是指针,所以传值给接口对象时需要用取地址符&
    animal = &Cat{"Black"}
    animal.Sleep()
    animal = &Dog{"White"}
    animal.Sleep()

    func animalSleep(animal AnimalIf){
    animal.Sleep()
    }

万能类型

interface{} : 空接口

基本数据类型都实现了空接口

  1. 作为形参时,可以接收所有类型的参数

  2. interface{}提供 “断言” 的机制

    1
    2
    3
    4
    5
    6
    value, ok := demo.(string)//demo是interface类型,返回 value和ok
    if !ok{
    fmt.Println("demo is not a string type")
    }else{
    fmt.Println("demo is a string type, value= ", value)
    }

pair

变量内置的pair结构:

1
2
var a int = 100// a: pair<statictype:int, value:100> 
var demo myStruct = {10,"test",99.9}//demo: pair<type:myStruct, value{10,"test",99.9}>

pair的结构用于断言判断类型

defer

defer定义的语句会在程序结束时,即return或panic之前执行,这意味着,即便程序出现error并panic了,也依旧会执行defer语句。如果要判断是因为何种情况要结束程序才执行的defer,则需要通过recover()去捕获panic,若返回值不为nil则说明来自panic。

1
2
3
4
5
6
7
8
9
10
11
func example() {
defer func() {
if err := recover(); err != nil {
log.Println("panic occurred:", err)
}
}()
err := findErr()
if err != nil{
panic(err)
}
}

如果定义了多个 defer 语句,它们会以逆序执行(Last In First Out,即后入先出)的顺序依次执行。这意味着最后一个定义的 defer 语句将是第一个执行的,并且最先定义的 defer 语句将是最后一个执行的。

reflct

反射通过pair获取变量的类型和值。

方法:

  1. reflct.ValueOf()

  2. reflct.TypeOf()

使用场景:通过interface{}万能类型接收变量,再通过TypeOf()获得变量的动态类型,并通过ValueOf()获得参数运行时的值。

Struct Tag

通过reflect.Type获取结构体成员信息reflect.StructField结构中的 Tag 被称为结构体标签(Struct Tag)。结构体标签是对结构体字段的额外信息标签。使用场景有如定义结构体别名,使得在参数绑定时更好的一一对应

1
2
3
4
5
//书写格式
type myStruct struct{
Name string `tag1:"name" tag2:"名字"`//以键值对的方式书写并用反引号括起来
Sex string `tag1:"sex" tag2:"性别"'`
}

并发

Go语言基础之并发 | 李文周的博客 (liwenzhou.com)

goroutine

goroutine是用户态的线程,由Go语言的运行时(runtime)调度完成。

使用goroutine

在调用函数的时候在前面加上 go 关键字,就可以为函数创建一个goroutine

一个goroutine必定对应一个函数,可以创建多个goroutine去执行相同的函数。

特殊机制

在程序启动时,Go程序就会为main()函数创建一个默认的goroutine

当main()函数返回的时候该goroutine就结束了,所有在main()函数中启动的goroutine会一同结束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var wg sync.WaitGroup//sync.WaitGroup可以让main等待所有goroutine结束后再结束main()

func hello(i int) {
defer wg.Done() // goroutine结束就登记-1
fmt.Println("Hello Goroutine!", i)
}
func main() {

for i := 0; i < 10; i++ {
wg.Add(1) // 启动一个goroutine就登记+1
go hello(i)
}
wg.Wait() // 等待所有登记的goroutine都结束
}
!!!需要注意的点!!!

若以闭包的方式运行goroutine时(如下所示)

闭包函数:关于闭包,最简单的描述就是 ECMAScript 允许使用内部函数--即函数定义和函数表达式位于另一个函数的函数体内。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数和声明的其他内部函数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。

也就是说,内部函数会在外部函数返回后被执行。而当这个内部函数执行时,它仍然必需访问其外部函数的局部变量、参数以及其他内部函数。这些局部变量、参数和函数声明(最初时)的值是外部函数返回时的值,但也会受到内部函数的影响。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var wg sync.WaitGroup//sync.WaitGroup可以让main等待所有goroutine结束后再结束main()

func main() {
wg.Add(10000) // 计数牌+1
for i := 0; i < 10000; i++ {
go func() {
defer wg.Done() // goroutine结束就登记-1
fmt.Println("Hello Goroutine!", i)
}()
}
fmt.Println("hello main")
wg.Wait() // 等待所有登记的goroutine都结束
}
/*
闭包内含有一个作用域外部的变量,所以执行对应goroutine内容时需要再去访问该变量的值,此时该变量作为flag值已经在循环中并行执行了很多次,故绝大多数goroutine取到的变量值都是最后一次循环的变量值。
*/

解决方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
var wg sync.WaitGroup//sync.WaitGroup可以让main等待所有goroutine结束后再结束main()

func main() {
wg.Add(10000) // 计数牌+1
for i := 0; i < 10000; i++ {
go func(i int) {
defer wg.Done() // goroutine结束就登记-1
fmt.Println("Hello Goroutine!", i)
}(i) // 解决方式:显式传递值给匿名函数,避免闭包
}
fmt.Println("hello main")
wg.Wait() // 等待所有登记的goroutine都结束
}
goroutine调度

深入Golang调度器之GMP模型 - sunsky303 - 博客园 (cnblogs.com)

channel

channel用于实现在多个goroutine间进行通信。

虽然goroutine可以使用共享内存进行数据交换,但是共享内存在不同的goroutine中容易发生竞态问题。为了保证数据交换的正确性,必须使用互斥量对内存进行加锁,这种做法势必造成性能问题。

声明

channel是一种类型,一种引用类型。声明通道类型的格式如下:

1
var 变量 chan 元素类型

举几个例子:

1
2
3
var ch1 chan int   // 声明一个传递整型的通道
var ch2 chan bool // 声明一个传递布尔型的通道
var ch3 chan []int // 声明一个传递int切片的通道
创建channel

通道是引用类型,通道类型的空值是nil

1
2
var ch chan int
fmt.Println(ch) // <nil>

声明的通道后需要使用make函数初始化之后才能使用。

创建channel的格式如下:

1
make(chan 元素类型, [缓冲大小])

channel的缓冲大小是可选的。

举几个例子:

1
2
3
ch4 := make(chan int)
ch5 := make(chan bool)
ch6 := make(chan []int)
channel操作

通道有发送(send)、接收(receive)和关闭(close)三种操作。

发送和接收都使用<-符号。

现在我们先使用以下语句定义一个通道:

1
ch := make(chan int)

发送

将一个值发送到通道中。

1
ch <- 10 // 把10发送到ch中

接收

从一个通道中接收值。

1
2
x := <- ch // 从ch中接收值并赋值给变量x
<-ch // 从ch中接收值,忽略结果

关闭

我们通过调用内置的close函数来关闭通道。

1
close(ch)

关于关闭通道需要注意的事情是,只有在通知接收方goroutine所有的数据都发送完毕的时候才需要关闭通道。通道是可以被垃圾回收机制回收的,它和关闭文件是不一样的,在结束操作之后关闭文件是必须要做的,但关闭通道不是必须的。

关闭后的通道有以下特点:

  1. 对一个关闭的通道再发送值就会导致panic。
  2. 对一个关闭的通道进行接收会一直获取值直到通道为空。
  3. 对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。
  4. 关闭一个已经关闭的通道会导致panic。

无缓冲的通道

无缓冲的通道又称为阻塞的通道。我们来看一下下面的代码:

1
2
3
4
5
func main() {
ch := make(chan int)
ch <- 10
fmt.Println("发送成功")
}

上面这段代码能够通过编译,但是执行的时候会出现以下错误:

1
2
3
4
5
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
.../src/github.com/Q1mi/studygo/day06/channel02/main.go:8 +0x54

为什么会出现deadlock错误呢?

因为我们使用ch := make(chan int)创建的是无缓冲的通道,无缓冲的通道只有在有人接收值的时候才能发送值。就像你住的小区没有快递柜和代收点,快递员给你打电话必须要把这个物品送到你的手中,简单来说就是无缓冲的通道必须有接收才能发送。

上面的代码会阻塞在ch <- 10这一行代码形成死锁,那如何解决这个问题呢?

一种方法是启用一个goroutine去接收值,例如:

1
2
3
4
5
6
7
8
9
10
func recv(c chan int) {
ret := <-c
fmt.Println("接收成功", ret)
}
func main() {
ch := make(chan int)
go recv(ch) // 启用goroutine从通道接收值
ch <- 10
fmt.Println("发送成功")
}

无缓冲通道上的发送操作会阻塞,直到另一个goroutine在该通道上执行接收操作,这时值才能发送成功,两个goroutine将继续执行。相反,如果接收操作先执行,接收方的goroutine将阻塞,直到另一个goroutine在该通道上发送一个值。

使用无缓冲通道进行通信将导致发送和接收的goroutine同步化。因此,无缓冲通道也被称为同步通道

有缓冲的通道

解决上面问题的方法还有一种就是使用有缓冲区的通道。我们可以在使用make函数初始化通道的时候为其指定通道的容量,例如:

1
2
3
4
5
func main() {
ch := make(chan int, 1) // 创建一个容量为1的有缓冲区通道
ch <- 10
fmt.Println("发送成功")
}

只要通道的容量大于零,那么该通道就是有缓冲的通道,通道的容量表示通道中能存放元素的数量。就像你小区的快递柜只有那么个多格子,格子满了就装不下了,就阻塞了,等到别人取走一个快递员就能往里面放一个。

我们可以使用内置的len函数获取通道内元素的数量,使用cap函数获取通道的容量,虽然我们很少会这么做。

从通道中取值的方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import "fmt"

// 开启goroutine将0~100的数发送到ch中
func f1(ch chan int) {
for i := 0; i < 100; i++ {
ch <- i
}
close(ch)
}

// 开启goroutine从ch1中接收值,并将该值的平方发送到ch2中
func f2(ch1 chan int, ch2 chan int) {
for {
//从通道中取值的方式1
i, ok := <-ch1 // 通道关闭后再取值ok=false
if !ok {
break
}
ch2 <- i * i
}
close(ch2)
}

func main() {
ch1 := make(chan int, 100)
ch2 := make(chan int, 200)
go f1(ch1)
go f2(ch1, ch2)
//从通道中取值的方式2
for ret := range ch2 {
fmt.Println(ret)
}
}