综合
变量声明和赋值
- go自己推断类型:
var d = true
- 初始变量为零值:
var e int
- 初始化声明:
:=
(右侧可以调用有返回值的函数,该变量的值则为返回值)
常量声明和赋值
- 对应变量
var
, 用const
关键词 - 数值声明时若未指定类型,可以使用
int64
等给定一个类型,也能根据上下文需要给定一个类型
数组
创建:var a [5]int
;var twoD [2][3]int
长度:len(a)
数组将以[..., ..., ..., ...]
的形式fmt.Println
指针
没有使用指针类型时,传入函数的是形参的拷贝,函数中的赋值不会改变实参的值
使用指针类型时,引用的是该变量的内存地址,将会改变该变量的值,同时避免调用时产生拷贝
struct(结构类型)
struct是带类型的字段集合
声明:
type person struct {
name string
age int
}
操作:
- 创建元素:
person{"Bob", 20}
,打印出来为{Bob 20}
- 指定字段:
person{name: "Alice", age: 30}
,打印格式同上,没有指定的字段为零值 - 生成结构体指针
&person{name: "Ann", age: 40}
- 访问某字段:
person.name
- struct中的字段值是可变的
struct tag,用来辅助反射
方法
func (r *rect) area() int
其中(r *rect)
为指针类型的接收器,同理,还有值类型的接收器
调用:
//声明一个结构类型
type rect struct {
width, height int
}
//定义方法,其接收者为结构类型的值或指针
func (r *rect) area() int {
return r.width * r.height
}
func (r rect) perim() int {
return 2*r.width + 2*r.height
}
func main() {
//为该类型创建元素
r := rect{width: 10, height: 5}
//调用这里两个方法,go会自动处理方法调用时的值和指针转化
fmt.Println("area: ", r.area())
fmt.Println("perim:", r.perim())
rp := &r
fmt.Println("area: ", rp.area())
fmt.Println("perim:", rp.perim())
}
错误处理
使用一个独立、明确的返回值来传递错误信息
error是一个类型(内建的接口,有其方法
Error
。New方法是errors包里的)也可以自定义一个错误结构类型,和其方法
定时器
time.NewTimer(Duration)
创建定时器
用<-time.NewTimer(d).C
来阻塞实现定时
区别于time.Sleep
的地方是可以用time.NewTimer.Stop()
来取消
打点器
time.NewTicker(Duration)
创建一个打点器
for循环遍历time.NewTicker(Duration).C
的通道消息实现按时间间隔重复执行。
如果没用goroutine,将会一直执行;如果用goroutine而没有设置持续时间,则会在调用ticker.Stop()
或main
函数执行完毕时停止。
工作池
实现方法:
创建通道
(可以用for循环)创建出几个goroutine,处于阻塞状态,等待通道中的任务
(可以用for循环)将任务发送到通道
这些gorourine将以协程的方式执行任务。
注意需要用阻塞的方式等待任务完成。
速率限制
主要目的为控制资源利用,通过协程、通道、打点器实现。
limiter := time.Tick(d)
该limiter
通道每隔d段时间接收一个值使用
<-limiter
阻塞
在速率限制中临时突破限制:
- 创建一个
time.Time
类型的缓冲通道burstyLimiter
,然后再传入需要数量的time.Time
类型的值(time.Now()
) - 用一个goroutine实现
time.Tick(d)
通道向该通道传入burstyLimiter
- 在之前需要
<-limiter
的地方使用<-burstLimiter
panic(异常处理)
panic 的一个基本用法就是在一个函数返回了错误值但是我们并不知道(或 者不想)处理时终止运行。
panic 是一个函数,接收一个interface{}
类型(任何类型)的参数,然后在defer之后终止程序,抛出异常。
panic 之后逻辑走到defer时,可以调用recover函数捕捉panic,但程序仍然会在defer之后返回
defer
defer 被用来确保一个函数调用在程序执行结束前执行。同样用来执行一些清理工作。
被defer调用的函数会在main结束时执行。
组合函数
函数接收一个匿名函数或具名函数作为参数,例:
func Any(vs []string, f func(string) bool) bool {
for _, v := range vs {
if f(v) {
return true
}
}
return false
}
匿名函数的实现可以在调用时写出来,例:
fmt.Println(Any(strs, func(v string) bool {
return strings.HasPrefix(v, "p")
}))
正则表达式(备查)
这个测试一个字符串是否符合一个表达式。 | match, _ := regexp.MatchString("p([a-z]+)ch", "peach") fmt.Println(match) |
---|---|
上面我们是直接使用字符串,但是对于一些其他的正则任 务,你需要 Compile 一个优化的 Regexp 结构体。 |
r, _ := regexp.Compile("p([a-z]+)ch") |
这个结构体有很多方法。这里是类似我们前面看到的一个 匹配测试。 | fmt.Println(r.MatchString("peach")) |
这是查找匹配字符串的。 | fmt.Println(r.FindString("peach punch")) |
这个也是查找第一次匹配的字符串的,但是返回的匹配开 始和结束位置索引,而不是匹配的内容。 | fmt.Println(r.FindStringIndex("peach punch")) |
Submatch 返回完全匹配和局部匹配的字符串。例如, 这里会返回 p([a-z]+)ch 和 `([a-z]+) 的信息。 |
fmt.Println(r.FindStringSubmatch("peach punch")) |
类似的,这个会返回完全匹配和局部匹配的索引位置。 | fmt.Println(r.FindStringSubmatchIndex("peach punch")) |
带 All 的这个函数返回所有的匹配项,而不仅仅是首 次匹配项。例如查找匹配表达式的所有项。 |
fmt.Println(r.FindAllString("peach punch pinch", -1)) |
All 同样可以对应到上面的所有函数。 |
fmt.Println(r.FindAllStringSubmatchIndex( "peach punch pinch", -1)) |
这个函数提供一个正整数来限制匹配次数。 | fmt.Println(r.FindAllString("peach punch pinch", 2)) |
上面的例子中,我们使用了字符串作为参数,并使用了 如 MatchString 这样的方法。我们也可以提供 []byte 参数并将 String 从函数命中去掉。 |
fmt.Println(r.Match([]byte("peach"))) |
创建正则表达式常量时,可以使用 Compile 的变体MustCompile 。因为 Compile 返回两个值,不能用于常量。 |
r = regexp.MustCompile("p([a-z]+)ch") fmt.Println(r) |
regexp 包也可以用来替换部分字符串为其他值。 |
fmt.Println(r.ReplaceAllString("a peach", "<fruit>")) |
Func 变量允许传递匹配内容到一个给定的函数中, |
in := []byte("a peach") out := r.ReplaceAllFunc(in, bytes.ToUpper) fmt.Println(string(out)) |
输出如下:
true
true
peach
[0 5]
[peach ea]
[0 5 1 3]
[peach punch pinch]
[[0 5 1 3] [6 11 7 9] [12 17 13 15]]
[peach punch]
true
p([a-z]+)ch
a <fruit>
a PEACH
同步状态
原子计数
使用 sync/atomic
包管理状态
在goroutine中使用 atomic.Addxxx
、 atomic.Loadxxx
、 atomic.Storexxx
来保证同一时刻只有一个goroutine来操作变量
**Tips: **runtime.Gosched
为释放该goroutine,使其回到队列中。
互斥锁
mutex = &sync.Mutex{}
在goroutine中使用 mutex.Lock()
和 mutex.Unlock
来保证同一时刻只有一个goroutine来操作变量
Go状态协程
通过goroutine和通道的同步特性来让共享的state跨多个goroutine同步访问。
思路:
- 共享的state由一个单独的goroutine拥有
- 创建两个无缓冲通道分别实现读和写请求与操作之间的通信
- 读和写分别创建若干个goroutine,每个goroutine将请求(struct)发送到对应通道
- 拥有state的goroutine单线程处理请求,将结果发送到struct中的通道
- 本次通信的goroutine会用struct中的通道阻塞,直到接收到结果值
请求的发送和单独的goroutine的接收是同步的,且同一时间只有这两个goroutine在利用通道通信
接口
命名了的方法签名的集合
是一组方法,也是一种类型
//创建一个接口
type geometry interface {
area() float64
perim() float64
}
//创建了两个struct
type rect struct {
width, height float64
}
type circle struct {
radius float64
}
//rect的area()和perim()两个方法
func (r rect) area() float64 {
return r.width * r.height
}
func (r rect) perim() float64 {
return 2*r.width + 2*r.height
}
//circle的area()和perim()两个方法
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}
//measure接受一个geometry的接口类型
func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.perim())
}
//rect和circle都实现了geometry接口
func main() {
r := rect{width: 3, height: 4}
c := circle{radius: 5}
measure(r)
measure(c)
}
五个关键点
interface是一种类型
不带任何方法的interface叫empty interface
如果一个类型实现了一个interface中所有方法,我们说类型实现了该Interface
interface变量存储的是实现者的值
- 如果有多种类型实现了某个interface,这些类型的值都可以直接使用interface的变量存储
- 在使用 interface 时不需要显式在 struct 上声明要实现哪个 interface ,只需要实现对应 interface 中的方法即可,go 会自动进行 interface 的检查,并在运行时执行从其他类型到 interface 的自动转换
- 实现了多个 interface时,go会在使用对应 interface 时实现自动转换
如何判断 interface 变量存储的是哪种类型
value, ok := em.(T)
:em 是 interface 类型的变量,T代表要断言的类型,value 是 interface 变量存储的值,ok 是 bool 类型表示是否为该断言的类型 T
可以用if判断,也可以用switch(这里的t是要断言的类型):
switch t := i.(type) {
case *S:
fmt.Println("i store *S", t)
case *R:
fmt.Println("i store *R", t)
}
空的interface
- 所有类型都实现了空的interface:如果定义一个函数参数是
interface{}
类型,这个函数应该可以接受任何类型作为它的参数 - go 不会对 类型是interface{} 的 slice 进行转换,所以《Go的类型》中的一个不常用的例子对
[]string
进行了转换
原因:interface{}
会占用两个字长的存储空间,一个是自身的 methods 数据,一个是指向其存储值的指针,也就是 interface 变量存储的值,因而 slice []interface{} 其长度是固定的N*2
,但是 []T 的长度是N*sizeof(T)
,两种 slice 实际存储值的大小是有区别的(文中只介绍两种 slice 的不同,至于为什么不能转换猜测可能是 runtime 转换代价比较大)。
interface 的实现者的 receiver 如何选择
- interface 定义时并没有严格规定实现者的方法 receiver 是个 value receiver 还是 pointer receiver,但在调用时如果方法的receiver是pointer,而传入的是一份拷贝,也就没有实现该interface;相反,则可以正确执行
原因:有了指针总是能得到指针指向的值是什么,如果是 value 调用,go 将无从得知 value 的原始值是什么,因为 value 是份拷贝。go 会把指针进行隐式转换得到 value,但反过来则不行。
- 对于 receiver 是 value 的 method,任何在 method 内部对 value 做出的改变都不影响调用者看到的 value,这就是按值传递
切片
切片和数组是不同的类型,但打印结果类似
append()方法
B = append(B, i * i)
第一个参数是原切片,第二个参数是要增加的值,中间是“=”而不是“:=”!
切片排序
【Go语言】基本类型排序和 slice 排序
根据类型使用如下方法:
sort.Ints(intList)
sort.Float64s(float8List)
sort.Strings(stringList)
直接对原切片排序,没有返回值。
sort.IntsAreSorted()
检查是否已经排序,返回一个bool值。
自定义排序
创建对应类型,让该类型实现Len()
、 Less()
、 Swap()
方法以实现sort.Interface
需要自定义的地方一般在
Less()
调用
sort.Sort(ByLength)
进行排序,需要使用如ByLength(fruits)
对切片转型
切片的最后一个索引
感觉不是很优雅。。。
B[len(B) - 1]
切片去重
没有现成的函数可用,找到了个性能好的函数:
func removeDuplicateElement(addrs []string) []string {
result := make([]string, 0, len(addrs))
temp := map[string]struct{}{}
for _, item := range addrs {
if _, ok := temp[item]; !ok {
temp[item] = struct{}{}
result = append(result, item)
}
}
return result
}
判断某元素是否在切片内
只能用遍历的方式
copy()
copy(c, s)
:将s赋值给c,c需要先声明c := make([]string len(s)
字符串
几乎都是在strings
这个包里,注意新字符串要用“:=”声明赋值
字符串大小写转换
strings.ToUpper(str)
strings.ToLower(str)
统计字符在字符串中出现的次数
strings.Count(str, s)
遍历字符串
range
在字符串中迭代 unicode 码点(code point)。 第一个返回值是字符的起始字节位置,然后第二个是字符本身。
以字节数组的方式遍历:
for i := 0; i < len(str); i++
以Unicode字符遍历:
for index, val := range str
使用string()
将结果转化为字符串
分割字符串
strings.Split()
返回字符串数组,跟迭代不同,索引出来的值类型是字符串
替换字符串中字符
strings.Replace()
返回的是字符串
字符串拼接
同python,用“+”拼接
查找子字符串
strings.Index(str, substr)
跟Python一样,如果包含子字符串返回开始的索引值,否则返回-1。
整数转为字符串
strconv.Itoa()
字符串函数(strings包中的常用函数)
p("Contains: ", s.Contains("test", "es")) //是否包含 true
p("Count: ", s.Count("test", "t")) //字符串出现字符的次数 2
p("HasPrefix: ", s.HasPrefix("test", "te")) //判断字符串首部 true
p("HasSuffix: ", s.HasSuffix("test", "st")) //判断字符串结尾 true
p("Index: ", s.Index("test", "e")) //查询字符串位置 1
p("Join: ", s.Join([]string{"a", "b"}, "-"))//字符串数组 连接 a-b
p("Repeat: ", s.Repeat("a", 5)) //重复一个字符串 aaaaa
p("Replace: ", s.Replace("foo", "o", "0", -1)) //字符串替换 指定起始位置为小于0,则全部替换 f00
p("Replace: ", s.Replace("foo", "o", "0", 1)) //字符串替换 指定起始位置1 f0o
p("Split: ", s.Split("a-b-c-d-e", "-")) //字符串切割 [a b c d e]
p("ToLower: ", s.ToLower("TEST")) //字符串 小写转换 test
p("ToUpper: ", s.ToUpper("test")) //字符串 大写转换 TEST
//其它非strings包
p("Len: ", len("hello")) //字符串长度
p("Char:", "hello"[1]) //标取字符串中的字符,类型为byte
字符串格式化(备查)
Go 为常规 Go 值的格式化设计提供了多种打印方式。例 如,这里打印了 point 结构体的一个实例。 |
p := point{1, 2} fmt.Printf("%v\n", p) |
---|---|
如果值是一个结构体,%+v 的格式化输出内容将包括 结构体的字段名。 |
fmt.Printf("%+v\n", p) |
%#v 形式则输出这个值的 Go 语法表示。例如,值的 运行源代码片段。 |
fmt.Printf("%#v\n", p) |
需要打印值的类型,使用 %T 。 |
fmt.Printf("%T\n", p) |
格式化布尔值是简单的。 | fmt.Printf("%t\n", true) |
格式化整型数有多种方式,使用 %d 进行标准的十进 制格式化。 |
fmt.Printf("%d\n", 123) |
这个输出二进制表示形式。 | fmt.Printf("%b\n", 14) |
这个输出给定整数的对应字符。 | fmt.Printf("%c\n", 33) |
%x 提供十六进制编码。 |
fmt.Printf("%x\n", 456) |
对于浮点型同样有很多的格式化选项。使用 %f 进 行最基本的十进制格式化。 |
fmt.Printf("%f\n", 78.9) |
%e 和 %E 将浮点型格式化为(稍微有一点不 同的)科学记数法表示形式。 |
fmt.Printf("%e\n", 123400000.0) fmt.Printf("%E\n", 123400000.0) |
使用 %s 进行基本的字符串输出。 |
fmt.Printf("%s\n", "\"string\"") |
像 Go 源代码中那样带有双引号的输出,使用 %q 。 |
fmt.Printf("%q\n", "\"string\"") |
和上面的整型数一样,%x 输出使用 base-16 编码的字 符串,每个字节使用 2 个字符表示。 |
fmt.Printf("%x\n", "hex this") |
要输出一个指针的值,使用 %p 。 |
fmt.Printf("%p\n", &p) |
当输出数字的时候,你将经常想要控制输出结果的宽度和 精度,可以使用在 % 后面使用数字来控制输出宽度。 默认结果使用右对齐并且通过空格来填充空白部分。 |
` fmt.Printf(“ |
你也可以指定浮点型的输出宽度,同时也可以通过 宽度. 精度 的语法来指定输出的精度。 | ` fmt.Printf(“ |
要左对齐,使用 - 标志。 |
` fmt.Printf(“ |
你也许也想控制字符串输出时的宽度,特别是要确保他们在 类表格输出时的对齐。这是基本的右对齐宽度表示。 | ` fmt.Printf(“ |
要左对齐,和数字一样,使用 - 标志。 |
` fmt.Printf(“ |
到目前为止,我们已经看过 Printf 了,它通过 os.Stdout 输出格式化的字符串。Sprintf 则格式化并返回一个字 符串而不带任何输出。 |
s := fmt.Sprintf("a %s", "string") fmt.Println(s) |
你可以使用 Fprintf 来格式化并输出到 io.Writers 而不是 os.Stdout 。 |
fmt.Fprintf(os.Stderr, "an %s\n", "error") |
输出如下:
{1 2}
{x:1 y:2}
main.point{x:1, y:2}
main.point
true
123
1110
!
1c8
78.900000
1.234000e+08
1.234000E+08
"string"
"\"string\""
6865782074686973
0x42135100
| 12| 345|
| 1.20| 3.45|
|1.20 |3.45 |
| foo| b|
|foo |b |
a string
an error
循环
for
是go的唯一循环
不带条件的循环
会一直循环下去,直到break
或return
伪while循环
先学的python,想在go中实现这种循环,但没有,可以在for循环中用if代替。
条件判断
逻辑运算符
&&
:逻辑AND
运算符||
:逻辑OR
运算符
多条件判断
case
中可以用”,”分隔多个条件;switch
中没有表达式的形式可作为if...else..
的备用;
如果switch
的表达式是一个类型,case
中将比较类型而不是值
switch express {
case express:
...
case express1, express2:
...
default:
...
}
在if中声明变量
可以用于该判断所有的分支
select
select语句是随机选择一个可运行的case运行,运行一个case后则停止
映射
创建映射
data := make(map[key-type]value-type)
给键赋值
data[key] = value
, 如果该键的值已经存在,则会被新值覆盖
打印
输出所有键值对:map[k1:7 k2:13]
移除
delete(m, "k2")
第一个参数是映射,第二个是要移除的键
取值
value, bool := m["k2"]
第一个返回值是“值”,第二个是bool类型的值,指示键是否存在
遍历
for k, v := range kvs
,for k := range kvs
则只遍历键
函数
参数和返回
多个同类型参数可仅声明一次plusPlus(a, b, c int)
声明返回的类型,如果没有返回可以不声明
多返回值
func vals() (int, int)
该特性经常被用来返回结果和错误信息
多参数
func sum(nums ...int)
调用:
sum(1, 2, 3)
- 如果参数是切片(nums):
sum(nums...)
闭包
func() int
是在func intSeq()
内定义的匿名函数,隐藏变量i
也可以说:func intSeq()
返回func() int
,同时包含了i
func intSeq() func() int {
i := 0
return func() int {
i++
return i
}
}
递归
也是调用自身
func fact(n int) int {
if n == 0 {
return 1
}
return n * fact(n-1)
}
goroutine
使用go func
创建gorotine
分配逻辑处理器
- 分配一个逻辑处理器给调度器使用
runtime.GOMAXPROCS(1)
,其中GOMAXPROCS
函数允许程序更改调度器可以使用的逻辑处理器的数量 - 给每个可用的核心分配一个逻辑处理器
runtime.GOMAXPROCS(runtime.NumCPU())
,其中NumCPU
函数返回可以使用的物理处理器数量
用wg来等待程序完成
var wg sync.WaitGroup
wg.Add(2)
goroutine完成时记录
defer wg.Done()
所有goroutin完成时退出
wg.Wait()
通道
通道(Channels)是连接多个 Go 协程的管道。可以从一个 Go 协程将值发送到通道,然后在别的 Go 协程中接收
创建
- 无缓冲
make(chan type)
- 有缓冲
make(chan string, 2)
发送和接收
默认发送和接收操作是阻塞的,直到发送方和接收方都准备完毕
- 发送:
channel <-
- 接收:
<- channel
通道同步
可以在函数中向缓冲通道传入一个值,然后在main
函数结尾传出;
也可以用sync.WaitGroup
方法
通道方向
使用通道作为函数的参数时,可以指定该通道是用来发送还是接收的,用来提升程序类型安全
如:func pong(pings <-chan string, pongs chan<- string)
通道选择器
goroutine + channel + select
goroutine中向无缓冲通道发送值,select的每个case等待每个通道传入,即同时等待多个通道。
如果要将这几个通道的值依次打印,对select使用for循环
超时处理
超时处理使用通道选择器。
select {
case res := <-c2:
fmt.Println(res)
case <- time.After(time.Second * 3):
fmt.Println("timeout 2")
}
time.After()
表示time.Duration
长的时候后返回一条time.Time
类型的通道消息。
select
语句阻塞等待最先返回数据的channel
,当先接收到time.After
的通道数据时,select
则会停止阻塞并执行该case
的代码。
非阻塞通道操作
我的理解为如果通道中没有可发送的值,select模块就会一直阻塞,通过default,通道中有值时则处理,没有时则跳过,不会一直阻塞。
关闭通道
close(chan)
关闭一个通道,意味着不能再向这个通道发送值了。
如果不关闭会继续阻塞执行,等待接收,导致报错:
fatal error: all goroutines are asleep - deadlock!
从通道接收的第一个为值,第二个为bool值:
go func() {
for {
j, more := <-jobs
if more {
fmt.Println("received job", j)
} else {
fmt.Println("received all jobs")
done <- true
return
}
}
}()
通过对more
使用if条件句判断是否完成,然后传递给done
通道
如果 jobs
已经关闭了,并且通道中所有的值都已经接收 完毕,那么 more
的值将是 false
。
通道遍历
通道中的值是可以迭代的,但可能需要将通道先关闭,不然会在循环中继续阻塞执行,等待接收。
标准库
flag
区分几个概念:
- 命令行参数(或参数):是指运行程序提供的参数
- 已定义命令行参数:是指程序中通过
flag.Xxx
等这种形式定义了的参数 - 非flag(non-flag)命令行参数(或保留的命令行参数):后文解释
定义flags:
var ip = flag.Int("flagname", 1234, "help message for flagname")
var flagvar int flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname")
flag.Var(&flagVal, "name", "help message for flagname")
net/http
http.ListenAndServe
建立监听
http.HandleFunc
进行处理,第二个参数为处理函数
request.FormValue
Get请求中的url参数,与之相似的还有request.URL.Query().Get