Post

一起学Go(二)

一起学Go(二)

基础

一些内置函数

  • close 主要用来关闭channel

  • len 求长度

  • new 用于分配基础类型的内存

  • make 用于分配引用类型的内存:chan, map, slice

  • append 用于追加元素到slice中

  • panic, recover 用于做错误处理

panic 像raise一样会主动拉出错误,可以用defer 语句处理,用 err := recover() 可以获取错误,并且recover了,则会恢复现场,但是这个现场是跳出了panic的函数的

strconv包

把字符串转换为相应类型。比如strconv.Atoi(s)

返回实体类型,与err编号

结构体

类型别名

1
type newName oldName

结构体

和C++中的struct类似,相较于class类来说:

  • 结构体中的变量值在内存中的位置和C++类似,会有内存对齐

  • 一般定义为大写开始的变量为共有,小写开始的为私有

1
2
3
4
5
type Personstruct{
    Namestring
    Ageint
    hightint
}

  • struct可以添加函数
1
2
3
4
func(p *Person)SayHI()(retint) {
println("hi")
return0
}

  • 构造函数约定new开头,返回结构体指针
1
2
3
4
5
6
7
funcnewPerson(namestring, ageint) *Person{// 注意返回一个指针
return &Person{
        Name:name,
        Age:age,// 最后需要加上逗号
    }
}


结构体匿名字段

字段类型作为了名字, 但是一般都不会定义int,string这类的匿名,而是一个新的类型,这样有利于提取出公共部分可理解, 而外部访问的时候又可以更直观:

1
2
3
4
5
6
7
8
9
10
11
type addressstruct{
    citystring
}
type personstruct{
    namestring
    address// 嵌套后
}

p :=new(person)
p.city// 从address中自动找到了city


继承机制

嵌套其他struct的时候同时也会把函数也嵌套过来,当然,如果函数名重复的话会忽略,自己声明的优先级更高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type animalstruct{}
func(a *anival)move() {
println("move")
}

type catstruct{
    animal// cat中嵌套了animal
}
func(c *cat)say() {
println("say")
}

// 使用
c :=new(cat)
c.say()
c.move()// 会从嵌套类中找到move()


结构体与json

1
2
3
4
5
6
7
8
import"encoding/json"

p = newPerson("je",12)

s, err := json.Marshal(p)//转为json

json.Unmarshal([]byte(s), &p)// 记住传指针


但是json访问的时候需要p为首字母大写,但有的json需要小写。

此时就要就需要在struct后加反引号,在包解析的时候会自动转换

1
2
3
4
type Personstruct{
    Namestring`json:"name", db:"name", ini:"name`// 在json的名字,db中的名字,init.conf中的名字等。
}


接口

用于抽象化一些类型的共有部分。比如猫狗都能叫,可以抽象出一个类型speak函数,那么在之后调用的时候能直接使用speak,而传入参数能的多种类型的

而其他的struct只要实现了interface中的方法,那么就能传入后调用出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type animalinterface{
    speak()
}
type catstruct{}
type dogstruct{}

func(c *cat)speak() {}
func(d *dog)speak() {}

funcdo(a *animal) {
    a.speak()
}

// use ... 这样就能动态的使用相应的speak函数了
do(new(cat))
do(new(dog))


空接口

空接口是所有类型的父接口,这样做是为了传入一种类型,比如在map中存动态的类型,一个函数接收所有的类型。

注意有一个大括号 interface{}

1
2
3
4
5
6
7
8
9
10
m1 :=make(map[string]interface{},10)
m1["name"] ="je"
m1["age"] =13

funcshow(ainterface{}) {}

// use
show(bool)
show("hi")


类型断言

  • x: 表示为interface{}的变量

  • T: 表示断言x可能的类型

1
2
isTrue, err := x.(T)


package包

包内的变量/函数需要首字母大写才能在外部调用

1
2
3
4
5
package 包名
import (
    别名 jelech.top/PackName
)


这里说明一下,一个go包在运行的时候会进行:

  • 全局声明

  • 运行init()函数。init函数是内置的,和main类似

  • 运行main()函数

在 A import B,B import C时,会先运行initC,再initB和initA。

文件操作

import "os"

  • os.Open()返回一个*File,对这个指针进行操作

  • len, err = os.Read([]byte) 读取一个文件,返回长度和错误标识

import "bufio"

  • line, err := reader.ReadString('\n')一直读取直到遇到某个字符

import "ioutil"

  • ret, err := ioutil.ReadFile(path) 直接打开文件全部读取到ret中

… 有待补充

time包

  • ntime := time.Now() 获取当前时间

  • ntime.Year(), ntime.Month()... 获取时间的详细部分

时间戳

  • ntime.Unix() 获取时间戳

  • ntime.Unix(time int64) 将时间戳转换为标准格式时间

  • ntime.UnixNano() 纳秒时间戳

时间操作

时间间隔在go中已经用常量定义,类型为Duration,纳秒为最小单位1(time.Nanosecond)

Add(d Duration) 时间加上一个间隔

Sub(u Time) 返回距离u时间多久。结果为 t-u

Equal(u Time) 判断时间是否相同,会有时区的影响

Before(u Time), After(u Time) 判断是否在u之前、之后。

time.Tick(d Duration) 设置一个定时器,本质上是一个通道

ntime.Format(%s) 时间格式化,但是格式化的形式是GO诞生时间…[2006/01/02 15:04:05]

反射

程序在运行期间对程序本身进行访问和修改的能力。Go语言使用reflect包提供程序的反射信息。

现有的使用场景可以举个例子:加载ini文件时,需要动态判断某个字符是什么类型的,这个定义是再代码中的。

需要注意的是反射性能并不高,需要尽量少用

reflect

每一个对象都又Type,Value两个属性。分别对应reflect.Type 和 reflect.Value。

也可以使用 TypeOf() 和 ValueOf()两个函数进行对对象的属性获取

而类型又可以分为:类型Name, 种类Kind类型。比如一个结构体的Name是我们取的名字,而kind是struct类型

t := reflect.Type; t.Name, t.Kind()

Elem() 在函数中通过反射的方法修改变量的值,可以使用这个函数来获取对应的指针

IsNil(), IsValid() 判断v持有的值是否为nil,比如指针是否为空。valid判断v是否持有一个值,这个值是否有效

…这里需要再加强学习一下

结构体反射

Field(i int) 用于获取第i个字段的信息,当然也包括.Name.Index.Type.Tag tag是结构体内变量的附加信息,参考json

NumField() 获取结构体中的变量个数

并发

Go天生支持并发,这一章对于Go非常重要,我之后会单独起一篇来记录一下他的原理,这里简要说明一下

goroutine类似与线程,属于用户态的线程。当你需要让某个任务并发执行的时候,只需要把这个任务包装为一个函数,开启goroutine去执行这个函数就可以了。使用方法是在函数前加一个 go 就可以了

Go语言提供了channel在多个goroutine之间进行通信。

注意闭包的情况,在go并发的时候,内部的匿名函数如果使用了外部的变量,那么在真正调用的时候会使得结果并不是我们想要的结果。详细参考一下闭包

结束等待wait

sync.WaitGroup 是一个线程结束标记,类似一个channel,在新建一个线程时候将其加1,每个线程结束都defer一个wg.Done() 这样做直到线程全部结束时,wg.wait()就能通过了

1
2
3
4
5
6
7
8
9
10
11
12
var wg sync.WaitGroup

for i :=0; i <100; i++ {
    wg.Add(1)
gofunc(iint) {
defer wg.Done()
println(i)
    }(i)
}
wg.Wait()
println("done")


GMP调度

OS线程一般都固定的栈内存为2MB,而goroutine的栈一开始设置为2KB,但是并不是固定的,之后可以增大和缩小。

  • G 存放goroutine的信息,以及和P的绑定等信息

  • M 是Go运行时对操作系统内核线程的虚拟,M和内核线程一般是1to1的

  • p 管理一组goroutine队列,其中存放当前goroutine运行的上下文环境,并且自带又自己的队列调度机制。P与M一般也是一一对应的。

会把m个goroutine分配给n个操作系统线程来执行

runtime.GOMAXPROCS() 可以限制当前进程可以使用的线程个数。默认的是CPU的逻辑核心数,会默认跑满整个CPU

更加详细的可以参考其他博客

channel

Go的并发模型是CSP,提倡通过通信共享内存,而不是通过同共享内存来进行通信

通道像一个传送带或者队列。FIFO的顺序,每个通道需要一个具体的类型指定传输的元素类型

channel是一个引用类型,需要先初始化才能使用,所以是使用make来进行初始化

1
2
3
4
5
6
7
8
9
10
var chchanint
ch =make(chanint)// 不带缓冲的
ch =make(chanint,10)// 带缓冲的

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

// 从ch中拿出到cur中
cur := <- ch


缓冲区

如果channel没有设置缓冲区,那么在发送的时候会一直等待,直到有人接收。

close(ch) 关闭一个通道,只有在通知方goroutine所有的数据发送完毕了才需要关闭通道,他是可以被GC回收的,因此关闭通道这个操作并不是必须的。

1
2
- 对关闭了的通道进行读会返回0

for range range支持了从通道中取值

单向通道

  • func f1(ch1 chan<- int) 声明一个单向通道,这里是声明了只能进行写入

  • func f1(ch1 <-chan int) 声明一个单向通道,这里是声明了只能进行读出

多路复用select

从多个通道里进行取值。

1
2
3
4
5
6
7
8
9
10
// 基本语法
select{
case <-ch1:
//...
case data := <-ch2:
//...
default:
//...
}


This post is licensed under CC BY 4.0 by the author.