[关闭]
@SovietPower 2022-06-24T08:35:26.000000Z 字数 24990 阅读 1044

Go 笔记

学习笔记



作业部落链接:https://www.zybuluo.com/SovietPower/note/1828538
参考:
https://tour.go-zh.org/welcome/1
https://go-zh.org/pkg/
https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/preface.md
https://www.runoob.com/go/go-tutorial.html

目前:
https://tour.go-zh.org/concurrency/1

import:
import后的下划线的作用即:当导入一个包时,该包下的文件里所有init()函数都会被执行,
然而,有些时候我们并不需要把整个包都导入进来,仅仅是是希望它执行init()函数而已。这个时候
就可以使用 import _ " "引用该包。即使用【import _ 包路径】只是引用该包,仅仅是为了调用
init()函数,所以无法通过包名来调用包中的其他函数。

Internal packages (packages that are inside a folder that has an internal folder in their path) can only be imported from packages rooted at the parent of the internal folder.
E.g. a package pkg/foo/internal/bar can be imported by the package pkg/foo/internal/baz and also from pkg/foo/baz, but cannot be imported by the package pkg nor can it be imported by pkg/bar. This is by design. This is so big, complex packages can be broken into smaller packages without having to expose internals.
You have to treat internal packages as "private" or non-existent from the "outside".
Internal packages are a compiler restriction. If you want to expose them (if you want to use an internal package) in your own project, you have to remove the internal folder, and then of course you have to change the imports (import paths) too.
如果目录合法但仍然不能引用,使用go build .\main.go,而不是Coder Runner的go build "...\main.go"


Go 简介

Go 语言被设计成一门应用于搭载 Web 服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。
对于高性能分布式系统领域而言,Go 语言无疑比大多数其它语言有着更高的开发效率。它提供了海量并行的支持。

Go 安装与使用

https://golang.org/dl/https://golang.google.cn/dl/
解压或安装后,将Go\bin目录添加到环境变量即可。

命令行使用命令go即可测试。

使用go run name.go运行go程序,用go build name.go生成go的二进制文件。(忽略目标文件,则默认为当前包)

注意设置一下包路径GOPATHGOMODCACHEGOCACHE(用go env查看)。
如:go env -w GOCACHE="F:\Programs\go-build cache"(应该是build后的临时文件)

go mod

https://blog.csdn.net/lengyue1084/article/details/108230517
https://studygolang.com/articles/31112?fr=sidebar
https://www.cnblogs.com/ydymz/p/9788804.html
https://zhuanlan.zhihu.com/p/103534192

go mod init module_name:初始化。
go mod download ...:下载一个包。
go mod tidy:删除错误或者不使用的模块,下载未下载的包。

使用问题

vscode go插件的插件安装失败
https://www.zybuluo.com/SovietPower/note/1829392 的安装部分。
进行完这些设置后,再按提示安装插件即可(会装在$GOPATH\bin\下)。

no required module provides ...: go.mod file not found in current directory or any parent directory
可能问题:
1. 工作目录不在项目跟目录,所以找不到go.mod。将工作目录转到项目根目录即可。
2. 未下载包。可根据提示安装(会安装在缓存目录$GOMODCACHE下)。
3. 下载了包,但未更新go.mod。按提示进行更新(go mod tidy或者go get ...)。或者直接将所需包拷贝到当前根目录。

vscode提示 Error loading workspace: You are outside of a module and outside of $GOPATH/src...
同上,需要将当前工作目录切换到当前包所在目录。因为工作目录默认就是项目根目录。
不切换目录也可,只是run code不能用,在终端手动输入命令也可。

could not import github.com/gin-contrib/sessions/cookie (cannot find package "..." in any of ... (from $GOROOT) ... (from $GOPATH))
直接编译,看提示信息。可能要go get获取包并更新go.mod
输入命令后,再编译可成功,忽略该错误信息即可。因为对应包是在缓存目录$GOMODCACHE下,但依旧能找到。

gopls requires a module at the root of your workspace. You can work with multiple modules by opening each one as a workspace folder...
因为当前工作区中有多个go模块,而gopls(在未设置时)只支持一个模块,所以不行。
解决方法0:忽略。
解决方法1:将当前工作区改为某个包的根目录(而不是其父目录,包含其他模块)。
解决方法2:
设置gopls支持工作区中的所有模块:在setting.jsongopls中增加一句"experimentalWorkspaceModule": true

  1. "gopls": {
  2. "experimentalWorkspaceModule": true
  3. }

但gopls是个非常耗CPU和内存的东西,如此可能导致很大的资源占用。

no required module provides package. go.mod file not found in current directory or any parent directory
go env -w GO111MODULE=auto

引用相同package的定义 依然未定义
https://blog.csdn.net/coolboyzero/article/details/77946653
使用go run main.go时,go不会自动编译其它相关文件。
需全部run:go run A.go B.go main.go
用goland可以比较方便地一键编译。

package main is not in GOROOT
编译或run时用go run main.go,而不是go run main(会被当做GOROOT下的main包?)

build ...: cannot find module for path ...
https://niuzheng.net/archives/1298/
可能可供参考:https://github.com/golang/go/issues/27527
设置go env -w GO111MODULE=off(或auto?一般使用on

Get "https://sum.golang.org/lookup/gorm.io/driver/mysql@v1.3.3": dial tcp 172.217.160.113:443: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
Go 1.13设置了默认的GOSUMDB=sum.golang.org,这个网站是被墙了的,用于验证包的有效性,可通过如下命令关闭:go env -w GOSUMDB=off,即私有仓库自动忽略验证。
也可设置go env -w GOSUMDB="sum.golang.google.cn",这个是专门为国内提供的sum 验证服务。
-w 标记,要求一个或多个形式为 NAME=VALUE 的参数, 并且覆盖默认的设置

local import "../labrpc" in non-local package
使用go mod导入包,格式应是module/path,module即go mod init [module]中的。
如果没有go mod初始化,则初始化。

包package

https://www.jb51.net/article/199471.htm
一个文件夹下面直接包含的文件只能归属一个package,同样一个package的文件不能在多个文件夹下。
包名可以不和文件夹的名字一样,包名不能包含 - 符号。
包名为main的包为应用程序的入口包,这种包编译后会得到一个可执行文件,而编译不包含main包的源代码则不会得到可执行文件。
如果想在一个包中引用另外一个包里的标识符(如变量、常量、类型、函数等)时,该标识符必须是对外可见的(public)。在Go语言中只需要将标识符的首字母大写就可以让标识符对外可见了。
结构体中的字段名和接口中的方法名如果首字母都是大写,外部包可以访问这些字段和方法。

Go 语法

Go 语言的基础组成有以下几个部分:包声明、引入包、函数、变量、语句/表达式、注释。

示例:

  1. package main
  2. // import "fmt"
  3. import (
  4. "fmt"
  5. "math/rand"
  6. )
  7. func main() {
  8. // comments
  9. /* block comments */
  10. fmt.Println("Hello, World!")
  11. }

若标识符(常量、变量、类型、函数名、结构字段等)以大写字母开头,那么该标识符对象可以被外部包的代码所使用(需先导入这个包),这被称为导出(类似面向对象语言中的public);若标识符以小写字母开头,则对外部包不可见,但在整个包的内部是可见并且可用的(类似面向对象语言中的protected)。
即引入包中,只有导出的标识符可以使用。

注意:大括号不能换行,必须在同一行,否则会出错。

变量

使用如下方式声明变量

  1. var x = 5 // 自动识别为int类
  2. var y = 5.0 // 自动识别为float64类
  3. var x = y // 与y类型相同
  4. var x int
  5. var x int = 5
  6. x := 5 // 自动识别为int类。只能在函数内使用该简单命名方式
  7. x := y // 与y类型相同
  8. a, b := 1, "qwq" // 可像Python一样一次赋多值

同导入语句一样,变量声明也可以“分组”成一个语法块

  1. var (
  2. x bool = false
  3. MaxInt uint64 = 1<<64 - 1 //不会计算出错
  4. z complex128 = cmplx.Sqrt(-5 + 12i)
  5. )

使用const声明常量

  1. const x = 1
  2. const y = "qwq"
  3. const (
  4. a = 1<<20
  5. b = a>>10
  6. )

使用iota便于声明枚举类常量:https://www.jianshu.com/p/ed3c7893afd4

Go的基本变量类型

  1. bool
  2. string
  3. int int8 int16 int32 int64
  4. uint uint8 uint16 uint32 uint64 uintptr
  5. byte // uint8 的别名
  6. rune // int32 的别名
  7. // 表示一个 Unicode 码点
  8. float32 float64
  9. complex64 complex128

int, uint 和 uintptr 在 32 位系统上通常为 32 位宽,在 64 位系统上则为 64 位宽,是不同的类型。
一般都使用int和uint。
string的字符不可直接修改,需重新赋值(使用切片获取未修改部分比较方便)。
string的单个字符类型为byte(所以byte类似char)。

未被赋值的变量默认为00, false, "", nil)(无论该变量在哪声明)。
go的bool判断是严格的,不同类型间的零值不能比较。

"A"string'A'int32(实际值为65)。

运算符

没有前置++,有后置++
不能在复杂语句中使用++(比如return)。

类型转换

使用Type()将一个变量转为任意类型。
不同于C, Go的变量没有隐式类型转换,所有类型转换都需自己显式完成。
比如:赋值时需转换类型使类型相同;函数传参时需转换类型使形参与实参类型相同。
如下,会编译出错cannot use f (type int) as type uint in assignment,需手动转换。

  1. var f int
  2. var z uint = f
  3. // var z uint = uint(f)

数字转字符
intstring(x)x要可转为字符,否则显示不出来。
[]bytestring(x)
[]int:不能转。需写函数一位位转。

注意,数字用string转换后为该数字ASCII码对应的字符(的UTF-8表现形式),而非数字的字符串表示。
如:

  1. func main() {
  2. a := []byte{65,66,67}
  3. fmt.Println(a)
  4. fmt.Println(string(a)) //ABC
  5. }

数字转字符串: strconv.Itoa(x)(只适用于int,byte需先转int)
字符串转数字: strconv.Atoi(x)

字符串

避免用+append拼接字符串,使用 strings.BuilderWriteString()

字符串可使用 `str` 包裹来跨行。此时其中的所有转义字符均无效,文本将会原样输出。换行会按\n输出。

跨行字符串中就不能再出现 `。如果也要有,只能用拼接。

循环

Go 的 for 语句中的三个分句外没有小括号,大括号{}必须有(且第一个不换行)。

  1. sum := 0
  2. // 没有小括号,大括号`{}`必须有
  3. for i := 0; i < 10; i++ {
  4. sum += i
  5. }
  6. // 每条语句同样都是可选的
  7. for ; ; {
  8. sum += sum
  9. if sum > 1000 {
  10. break
  11. }
  12. }
  13. // 当只有第二条语句时,即等同于`while`时,可以省略分号
  14. // 即Go中`while`也用`for`表示
  15. for ; sum < 1000; {
  16. sum += sum
  17. }
  18. for sum < 1000 {
  19. sum += sum
  20. }
  21. // 当不需要条件时,可以直接省略判断语句
  22. for {
  23. sum += sum
  24. if sum > 1000 {
  25. break
  26. }
  27. }
  28. // 简单的死循环方式
  29. for {
  30. ...
  31. }

判断

类似循环,if后的表达式不需加小括号,大括号{}必须有(且第一个不换行)。
也可以加小括号,会被看做运算时的括号(类似Python)。

  1. if 1<2 {
  2. ...
  3. }

同循环,if在判断前也可以执行一个简单语句,用;分割判断条件。
可以在此定义变量,且该语句声明的变量作用域仅在if之内(对应的else的作用域也属于对应的if,即else也可使用该变量)。if...else...之外无法使用if定义的变量。
注意,else必须在前一个if的右大括号所在的行,其大括号不换行。

  1. if v:=math.Pow(x, n); v<lim {
  2. fmt.Println(v)
  3. } else {
  4. fmt.Println(v)
  5. }
  6. // `if`语句声明的变量作用域仅在`if`之内
  7. fmt.Println(v) // undefined: v
  8. if c--; c == 0 {
  9. ...
  10. }

switch

同判断,switch后的表达式不需加小括号,大括号{}必须有(且第一个不换行);Go的switch在判断前可以执行一个简单语句,用;分割要判断的值。
同样拥有判断所有case失败后的default

与其它语言不同的是:
1. Go的switch没有break,在判断成功运行完一个case块后,会自动终止。如需像其它语言一样继续向下判断,需使用fallthrough结束该case块。
2. case后的值不必须是常量,还可以是任意类型变量、表达式。在未判断当前case前,该处的表达式不会被执行。

switch可以不加条件,则等价于switch true,会执行后面(第一个)表达式为真的case块。
例:

  1. switch {
  2. case 2<1:
  3. fmt.Println("2<1!")
  4. case 1<2:
  5. fmt.Println("1<2!") // 输出1<2!
  6. default:
  7. fmt.Println("1==2!")
  8. }

defer

defer语句会将其后的函数推迟到外层函数返回之后再执行。
推迟调用的函数的参数会立即求值,但直到外层函数返回前,该函数都不会被调用。
例:

  1. func main() {
  2. s := "world"
  3. defer fmt.Println(s)
  4. s = "hello"
  5. fmt.Println(s)
  6. }
  7. // 输出 hello\nworld

推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。
例:

  1. func main() {
  2. fmt.Println("Start")
  3. for i := 0; i < 10; i++ {
  4. defer fmt.Print(i)
  5. }
  6. fmt.Println("End")
  7. }
  8. /*
  9. 输出:
  10. Start
  11. End
  12. 9876543210
  13. */

注意,即使出现了崩溃如panicdefer也会在退出时被执行。但是defer需要在发生panic的语句之前被调用。(见recover

panic recover todo

https://blog.go-zh.org/defer-panic-and-recover

指针

Go有指针,用法基本与C相同:*T为指向T类型的指针,&x创建一个指针,*p获取或设置一个指针的值。但是不支持指针运算(如p++, p+8)。

  1. var p *int

默认值/零值为nil
nil是预定义的标识符,代表指针、通道、函数、接口、映射或切片的零值,可以(但不应)被修改。

结构体

Go的结构体定义类似C。要通过type(C中的typedef),才能有显式的名字。

  1. type Vertex struct {
  2. X int
  3. Y int
  4. }
  5. var x = Vertex{1, 2}
  6. var y = struct {
  7. X int
  8. Y int
  9. }{1, 2}

使用.访问结构体中的元素,结构体指针也可直接使用.访问结构体中的元素:p.x(*p).x均可。
结构体的名字的首字母大写,才可被外部包调用,这和全局变量一样。此外,结构体中的元素只有首字母大写,才可被外部包访问到。

  1. type Vertex struct {
  2. X, Y int
  3. }
  4. func main() {
  5. // v := Vertex{1, 2}
  6. p := &Vertex{1, 2}
  7. fmt.Println(p.X, (*p).X, p)
  8. }
  9. // 输出:1 1 &{1 2}

创建结构体时,使用Name: val可给指定结构体元素赋值(未赋值的默认为0)。

  1. type Vertex struct {
  2. X, Y int
  3. }
  4. var x = Vertex{X: 5} // Y=0

数组

[n]T定义类型为T、长为n的数组。
数组是定长的。

  1. var a [10]int
  2. b := [4]int{1, 2, 3} // 未赋值的部分为0

遍历
类似Python,使用for...range可遍历数组、切片或映射。但range后无括号。
遍历时,每次迭代返回两个值,分别为当前元素的下标、当前元素的值。使用两个元素获取返回值,可得到下标和值;只使用一个元素获取返回值,只会得到下标。
如果只想得到值,可使用变量_赋值下标(否则赋值了i不用,会报错i declared but not used)。

_用来表示赋值了但不用的变量,该变量不能被引用。
可以同时为多个不用值赋_,如:_, _ = conn.Do("set", identifier+SplitChar+"LimitType", limitType)

  1. func main() {
  2. A := [4]int {0, 1, 2, 3}
  3. for i := range A {
  4. fmt.Print(i, " ")
  5. }
  6. fmt.Print("\n")
  7. for i, v := range A {
  8. fmt.Print(i, ",", v, " ")
  9. }
  10. fmt.Print("\n")
  11. for _, v := range A {
  12. fmt.Printf("%d ", v)
  13. }
  14. }
  15. /*
  16. 输出:
  17. 0 1 2 3
  18. 0,0 1,1 2,2 3,3
  19. 0 1 2 3
  20. */

切片

[]T定义一个类型为T的数组的切片。
使用array[l:r]为切片赋值,切片包含array[l], array[l+1], ..., array[r-1]
切片是对数组的一段的引用,对切片的修改与对数组的修改共享。切片指向的数组称为它引用的底层数组。
切片间赋值也是赋值引用。若想拷贝,应该可用var x[]intx = append(x, y...)
注意:当对一个数组的切片使用append时,若append后切片的cap没变化(即没有超出原数组大小上限),则会修改原数组;否则切片cap增长,切片会和数组切断关系,拷贝新数组修改,不再共享引用。见:https://blog.csdn.net/zhaoliang831214/article/details/103740809
即:append在追加元素时,当前cap足够容纳元素,则直接存入数据,否则需要扩容后重新创建新的底层数组,拷贝原数组元素后,再存入追加元素。
cap的扩容意味着内存的重新分配,数据的拷贝等操作,为了提高append的效率,若是能预估cap的大小的话,尽量提前声明cap,避免后期的扩容操作。

slice扩容规则:
设至少需要扩到C,预估容量为newCap = cap
newCap*2 < C,则令newCap = C
否则,若newCap < 1024,则令newCap = newCap * 2;否则令newCap *= 1.25,不断重复直到newCap >= C
这样得到增长后的预估容量newCap
newCap * 单个元素占用空间即为新的底层数组需要的内存B。但由于内存一般是分成固定规格的,不会分恰好这么多的内存,而是分稍大的一部分B',则B' / 元素占用空间即为实际扩容后的容量。

注意:注意range遍历切片时,会先拷贝一个切片,每遍历一个数据拷贝一个数据,修改遍历的数据不会影响切片。

注意var x []int在没有元素时会返回一个null,而不是空数组!执行append后才会自动初始化。
所以应使用x := make([]int, 0)

切片的默认值/零值为nil,且长度、容量均为0。

  1. func main() {
  2. A := [6]int{1, 2, 3}
  3. var x []int = A[1:4]
  4. fmt.Println(x)
  5. }
  6. // 输出:2 3 0

与Python相同,当array[l:r]省略l时,l为0;省略r时,rlen(array)

注意: 返回切片时,返回的不是副本,修改返回值会使原切片同步修改!
这与指针相同。但直接拷贝是指拷贝!与很多语言不同。

  1. var A [4]int = [4]int{1, 2, 3, 4}
  2. var x = &A
  3. var y = A[:]
  4. var z = A
  5. 修改 x, y 均会影响 A,但 z 不会

注意 []int创建的是切片,[n]int创建的是数组
直接拷贝切片[]int是浅拷贝,底层数组不变!直接拷贝数组[n]int是值拷贝!
sort.Ints()的参数只能是切片[]int,不能是数组,且会修改原切片!
使用copy(des, src)深拷贝切片。拷贝元素数取决于长度较小的切片。

快速创建切片
[4]int{1, 2, 3, 4}表示一个数组,[]int{1, 2, 3, 4}则表示一个切片。
使用[]int{1, 2, 3, 4}会创建一个数组[4]int{1, 2, 3, 4},然后返回一个该数组的切片。

  1. r := []bool{true, false, true}
  2. fmt.Println(r)
  3. s := []struct {
  4. i int
  5. b bool
  6. }{
  7. {2, true},
  8. {3, false},
  9. {5, true},
  10. }
  11. fmt.Println(s)
  12. // 输出:
  13. // [true false true]
  14. // [{2 true} {3 false} {5 true}]

切片的切片
切片可包含任何类型,包括其它的切片,即可定义多维切片(类似多维数组)。
[n][m]int为二维数组,[][]int为二维切片。

  1. func main() {
  2. A := [][]int{
  3. []int{1, 2, 3},
  4. []int{4, 5, 6},
  5. []int{7, 8, 9},
  6. }
  7. for i := 0; i < len(A); i++ {
  8. fmt.Println(A[i])
  9. }
  10. }
  11. /*
  12. 输出:
  13. [1 2 3]
  14. [4 5 6]
  15. [7 8 9]
  16. */

用切片实现动态数组

切片的长度和容量
使用len(s)cap(s)获取切片的长度、容量。
切片在被赋值后,其长度和容量就确定(且相等)。
若再使用它自身对其进行赋值,则不会改变该切片,但会获取到该切片对应的段(指向的底层数组不会变,长度改变,容量不会变)。

  1. func main() {
  2. t := [6]int{2, 3, 5, 7, 11, 13}
  3. var s = t[:]
  4. printSlice(s)
  5. s = s[:0]
  6. printSlice(s)
  7. s = s[:4]
  8. printSlice(s)
  9. s = t[1:]
  10. printSlice(s)
  11. s = s[:0]
  12. printSlice(s)
  13. s = s[:4]
  14. printSlice(s)
  15. }
  16. func printSlice(s []int) {
  17. fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
  18. }
  19. /*
  20. 输出:
  21. len=6 cap=6 [2 3 5 7 11 13]
  22. len=0 cap=6 []
  23. len=4 cap=6 [2 3 5 7]
  24. len=5 cap=5 [3 5 7 11 13]
  25. len=0 cap=5 []
  26. len=4 cap=5 [3 5 7 11]
  27. */

make
make(Type, len, cap)可创建切片,需指定切片类型、初始长度、总容量。可忽略cap,会与len相同。(不能忽略len)
创建的元素值为0。

  1. func main() {
  2. a := make([]int, 5)
  3. printSlice("a", a)
  4. b := make([]int, 0, 5)
  5. printSlice("b", b)
  6. c := b[:2]
  7. printSlice("c", c)
  8. d := c[2:5]
  9. printSlice("d", d)
  10. }
  11. func printSlice(s string, x []int) {
  12. fmt.Printf("%s len=%d cap=%d %v\n",
  13. s, len(x), cap(x), x)
  14. }
  15. /*
  16. 输出:
  17. a len=5 cap=5 [0 0 0 0 0]
  18. b len=0 cap=5 []
  19. c len=2 cap=5 [0 0]
  20. d len=3 cap=3 [0 0 0]
  21. */

append
append(s []T, elements... T)可返回在类型为T的切片s后追加elements元素后的新切片。
scap太小,不足以容纳所有元素时,cap会相应增加。
当对一个数组的切片使用append时,若append后切片的cap没变化(即没有超出原数组大小上限),则会修改原数组;否则切片cap增长,切片会和数组切断关系,拷贝新数组修改,不再共享引用。

  1. func main() {
  2. var s = []int{1, 2, 3, 4}
  3. printSlice(s)
  4. s = s[:2]
  5. printSlice(s)
  6. s = append(s, 1)
  7. printSlice(s)
  8. s = append(s, 2, 3, 4)
  9. printSlice(s)
  10. }
  11. func printSlice(s []int) {
  12. fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
  13. }
  14. /*
  15. 输出:
  16. len=4 cap=4 [1 2 3 4]
  17. len=2 cap=4 [1 2]
  18. len=3 cap=4 [1 2 1]
  19. len=6 cap=8 [1 2 1 2 3 4]
  20. */

映射map

类型写为:map[key_type]val_type
映射的零值为nilnil映射既没有键,也不能添加键。
访问不存在的键会返回val类型的零值(所以对不存在的key,使用m[key]++也能正确运行,因为m[key]=m[key]+1,而m[key]为0)。

make(T)会返回指定T类型的映射,并将其初始化(分配空间等?)。
注意使用map前必须初始化

  1. func main() {
  2. m = make(map[string]Vertex)
  3. // m = make(map[string]interface{}) // 映射到任意类型
  4. m["qwq"] = Vertex{
  5. 1, 2,
  6. }
  7. fmt.Println(m["qwq"])
  8. fmt.Println(m["123"])
  9. }
  10. /*
  11. 输出:
  12. {1 2}
  13. {0 0}
  14. */

定义映射初始值与定义结构体类似,只需在每个value前增加键值,即使用key: val

  1. var m = map[string]Vertex{
  2. "qwq": Vertex{
  3. 1, 2,
  4. },
  5. "123": Vertex{
  6. 1, 23,
  7. },
  8. }
  9. func main() {
  10. fmt.Println(m)
  11. }
  12. /*
  13. 输出:map[123:{1 23} qwq:{1 2}]
  14. */

val的值为对应的类型转换,可以省略这个类型名:

  1. type Vertex struct {
  2. x, y int
  3. }
  4. var m = map[string]Vertex{
  5. "qwq": {1, 2},
  6. "123": {1, 23},
  7. }
  8. func main() {
  9. fmt.Println(m)
  10. }
  11. /*
  12. 输出:map[123:{1 23} qwq:{1 2}]
  13. */

修改映射
获取元素:m[key]
插入或修改元素:m[key] = value
删除元素:delete(m, key)
检测某个键是否存在:val, ok := m[key]。当key存在于m中时,ok为true,否则ok为false,val为对应类型的零值。

没有清空整个map的操作。但是可以直接赋值一个新的map,旧的map被回收也非常快。

函数

go 是值传递。所以要想修改对象,需传递指针。
但 channel,slice,map 比较特别,它们本身就是一个指针,所以不需要再取址。

闭包

Go的函数可以是闭包。
闭包为一个内层函数,它可使用它外层函数的变量。
如:

  1. func adder() func(int) int {
  2. sum := 0
  3. return func(x int) int {
  4. sum += x
  5. return sum
  6. }
  7. }
  8. func main() {
  9. add, sub := adder(), adder()
  10. for i := 0; i < 10; i++ {
  11. fmt.Println(
  12. add(i),
  13. sub(-2*i),
  14. )
  15. }
  16. }

上述代码使用adder()定义一个闭包。共定义了两个,分别用add, sub保存。
使用adder()定义后,闭包会拥有一个互相独立的变量sum,当再次调用闭包(如add)时,闭包可修改与访问该变量sum
即外层函数为内层函数创建了静态变量,且该静态变量在闭包之间并不共享。

内层函数

在一个函数体内部声明:func(args...) { .. }(args...)
如果内层函数体内的变量不在参数列表,且在外层函数中被声明过,且没有隐藏,内部函数会使用和外层函数相同的变量。
但注意,这种使用外层函数变量的变量的值,是内部函数使用那个变量时,那个变量当前的值,也就是可能会在外层中被改变,而不是在内层函数被调用时就已确定。
这种问题一般出在用goroutinue时?如:

  1. // mp 是一个 map,其指向不会被改变
  2. var done sync.WaitGroup
  3. for _, x := range urls {
  4. done.Add(1)
  5. go func(x string) {
  6. defer done.Done()
  7. Do(x, mp)
  8. }(x)
  9. }
  10. done.Wait()

若不将x作为参数,则若循环在协程执行Do之前被执行,第一个协程使用的x可能是第二、第三或更后面的x,即x也能被循环修改。但使用参数,x在调用内部函数时就已确定。
因为mp在循环中没有被改变,所以可以直接用。
(ps:字符串本身是不能更改的,但string类型的变量只是指向一个字符串,当被重新赋值时,其变量值会变为另一个字符串,并不是说原字符串被改变了。string是一个结构体,封装了addr, len

内部引用的外部变量,在外部结束时不会被释放,会发生内存逃逸(见面试-go)。

min/max

go只有math中实现了浮点数的min max。但整数不应使用这个,要自己实现:

  1. const MAXINT = math.MAXINT
  2. func (rf *Raft) min(args ...int) int {
  3. res := MAXINT
  4. for _, v := range args {
  5. if res > v {
  6. res = v
  7. }
  8. }
  9. return res
  10. }

方法

Go没有类,但可以为结构体定义方法。
方法是带有接收者参数的函数。在关键字func和方法名之间,定义该方法的接收者(可拥有该方法的类型)。
如下例中,Len方法拥有一个类型为Vertex的接收者,在该函数中记作x

  1. type Vertex struct {
  2. X, Y float64
  3. }
  4. func (x Vertex) Len() float64 {
  5. return math.Sqrt(x.X*x.X + x.Y*x.Y)
  6. }
  7. func main() {
  8. v := Vertex{3, 4}
  9. fmt.Println(v.Len()) // 5
  10. }

上面的代码与下面效果相同:

  1. type Vertex struct {
  2. X, Y float64
  3. }
  4. func Len(x Vertex) float64 {
  5. return math.Sqrt(x.X*x.X + x.Y*x.Y)
  6. }
  7. func main() {
  8. v := Vertex{3, 4}
  9. fmt.Println(Len(v)) // 5
  10. }

非结构体也可绑定方法(注意type不是单纯的字符串替换,是确实定义一个相应类型,两个类型的变量赋值仍然要类型转换):

  1. type MyFloat float64
  2. func (f MyFloat) Abs() float64 {
  3. if f < 0 {
  4. return float64(-f)
  5. }
  6. return float64(f)
  7. }
  8. func main() {
  9. f := MyFloat(-math.Sqrt2)
  10. fmt.Println(f.Abs()) // 1.4142135623730951
  11. }

注意,方法的接收者类型必须是在同一包内定义的类型,其它包的类型不能在当前包作为某个方法的接收者类型(如int, float64任意类型切片等内建类型)。
但可像上例,通过type在当前包“新”定义一个类型实现。

指针接收者
类似C,在上例中,对应方法只是对对应接收者的副本(或值)进行的操作。若需对接收者进行修改,需使用指针接收者,即使用接收者类型的指针类型作为类型。
在一般函数里也一样,参数使用指针才可修改,不使用指针则参数为副本。
使用指针作为参数或接收者,不仅可用于修改,还可避免复制大型结构体导致的效率降低。

  1. type Vertex struct {
  2. X, Y float64
  3. }
  4. func (v Vertex) Scale(f float64) {
  5. v.X = v.X * f
  6. v.Y = v.Y * f
  7. }
  8. func (v *Vertex) Scale2(f float64) { // 使用指针!
  9. v.X = v.X * f
  10. v.Y = v.Y * f
  11. }
  12. func main() {
  13. v := Vertex{3, 4}
  14. v.Scale(10)
  15. fmt.Println(v)
  16. v.Scale2(10) // 等价于(&v).Scale2(10)
  17. fmt.Println(v)
  18. }
  19. /*
  20. 输出:
  21. {3 4}
  22. {30 40}
  23. */

上面的代码与下面效果相同:

  1. type Vertex struct {
  2. X, Y float64
  3. }
  4. func Scale(v Vertex, f float64) {
  5. v.X = v.X * f
  6. v.Y = v.Y * f
  7. }
  8. func Scale2(v *Vertex, f float64) { // 使用指针!
  9. v.X = v.X * f
  10. v.Y = v.Y * f
  11. }
  12. func main() {
  13. v := Vertex{3, 4}
  14. Scale(v, 10)
  15. fmt.Println(v)
  16. Scale2(&v, 10)
  17. fmt.Println(v)
  18. }
  19. /*
  20. 输出:
  21. {3 4}
  22. {30 40}
  23. */

但是,函数的指针参数只能接收指针类型参数,而指针接收者方法不仅指针对象能使用,非指针对象也能使用。
注意,只是能使用,不代表就是该类型的方法。
下面的例子中,v.Scale()等价于(&v).Scale()

  1. func (v *Vertex) Scale(f float64) { // 不仅指针对象可用,非指针对象也可用
  2. v.X = v.X * f
  3. v.Y = v.Y * f
  4. }
  5. func main() {
  6. v := Vertex{3, 4}
  7. v.Scale(10) // ok
  8. (&v).Scale(10) // ok
  9. }
  10. func Scale(v *Vertex, f float64) { // 只能使用指针!
  11. v.X = v.X * f
  12. v.Y = v.Y * f
  13. }
  14. func main() {
  15. v := Vertex{3, 4}
  16. Scale(v, 10) // Error
  17. Scale(&v, 10) // ok
  18. }

相反地,函数的非指针参数只能接收非指针类型参数,而非指针接收者方法不仅非指针对象能使用,指针对象也能使用。
同样注意,只是能使用,不代表就是该类型的方法。
下面的例子中,p.Scale()等价于(*p).Scale()

  1. func (v Vertex) Scale(f float64) { // 不仅非指针对象可用,指针对象也可用
  2. v.X = v.X * f
  3. v.Y = v.Y * f
  4. }
  5. func main() {
  6. v := Vertex{3, 4}
  7. p := &v
  8. (*p).Scale(10) // ok
  9. p.Scale(10) // ok
  10. }
  11. func Scale(v Vertex, f float64) { // 只能使用非指针!
  12. v.X = v.X * f
  13. v.Y = v.Y * f
  14. }
  15. func main() {
  16. v := Vertex{3, 4}
  17. p := &v
  18. Scale(*p, 10) // ok
  19. Scale(p, 10) // Error
  20. }

接口

接口类型

接口类型是由一组方法定义的集合。
接口类型变量可以赋值为一个任何 实现了集合中所有方法的类型 的值(称作其具体值),其类型会被同样设为对应类型(称作底层类型)。
接口值调用方法,会执行其底层类型的同名方法。

即接口类型为一组方法定义了一种类型,该类型中的元素,所包含信息、结构体不一定相同,但都拥有相同名称的这些方法。
或说,定义了某几个方法的类型,都可以算作一种类型。
如下,Handler就是实现了ServeHTTP()的类型,是一个借口:

  1. type Handler interface {
  2. ServeHTTP(ResponseWriter, *Request) // 路由实现器
  3. }

在Java中,实现 接口时通过implements 接口名来显示实现接口,并在实现类中实现接口的所有方法。
在go中,接口都是隐式实现的,实现类只需要实现接口中的全部方法隐式实现了这个接口。

接口一般命名为:以方法名+(e)r结尾;以able结尾;以I开头。

接口类型是一个指针,所以不要使用一个指针指向一个接口类型。

  1. type Abser interface {
  2. Abs() float64
  3. }
  4. func main() {
  5. var x Abser
  6. f := MyFloat(-math.Sqrt2)
  7. v := Vertex{3, 4}
  8. x = f // MyFloat 实现了 Abser,所以x现在是f
  9. x = &v // *Vertex 实现了 Abser,所以x现在是&v
  10. // x = v // 错误,不能赋值,因为Vertex没实现Abser
  11. // 错误信息:Vertex does not implement Abser (Abs method has pointer receiver)
  12. fmt.Println(x, x.Abs()) // &{3 4} 5
  13. fmt.Printf("(%v, %T)\n", x, x) // (&{3 4}, *main.Vertex)
  14. }
  15. // 实现MyFloat, *Vertex(注意不是Vertex)的Abs方法,则MyFloat, *Vertex实现了接口Abser
  16. type MyFloat float64
  17. func (f MyFloat) Abs() float64 {
  18. if f < 0 {
  19. return float64(-f)
  20. }
  21. return float64(f)
  22. }
  23. type Vertex struct {
  24. X, Y float64
  25. }
  26. func (v *Vertex) Abs() float64 {
  27. return math.Sqrt(v.X*v.X + v.Y*v.Y)
  28. }

接口类型的零值nil(因为是一个指针),既不包含值也不包含具体类型。此时不能调用任何方法,否则会RE。
但如果被赋值某类型的值后,其具体值为nil,则它自身并不为nil,会正确地指向那个(值为nil的)对象。此时可以正确调用该类型拥有的方法。

  1. type I interface {
  2. M()
  3. }
  4. type T struct {
  5. S string
  6. }
  7. func (t *T) M() {
  8. if t == nil {
  9. fmt.Println("<nil>")
  10. return
  11. }
  12. fmt.Println(t.S)
  13. }
  14. func main() {
  15. var i I
  16. describe(i) // (<nil>, <nil>) 自身为nil
  17. // i.M() // runtime error
  18. var t *T
  19. i = t
  20. describe(i) // (<nil>, *main.T) 仅具体值为nil,但拥有T类型
  21. i.M() // <nil>
  22. i = &T{"hello"}
  23. describe(i) // (&{hello}, *main.T)
  24. i.M() // hello
  25. }
  26. func describe(i I) {
  27. fmt.Printf("(%v, %T)\n", i, i)
  28. }

空接口

空接口是指定了0个方法的接口值,类型表示为interface{}
空接口可赋值为任何类型的值(因为都实现了0个方法)。
空接口可用来保存未知类型的值,如:一个拥有interface{}类型参数的函数,可以接收任意类型的参数。

  1. func main() {
  2. var i interface{}
  3. describe(i) // (<nil>, <nil>)
  4. i = 42
  5. describe(i) // (42, int)
  6. i = "hello"
  7. describe(i) // (hello, string)
  8. }
  9. func describe(i interface{}) {
  10. fmt.Printf("(%v, %T)\n", i, i)
  11. }

接口嵌套

类似Java中的接口继承。
一个接口可以包含/内嵌一个或者多个其他类型的接口,此时该接口也定义了其所有内嵌接口的方法。

  1. type ReadWrite interface {
  2. Read(b Buffer) bool
  3. Write(b Buffer) bool
  4. }
  5. type Lock interface {
  6. Lock()
  7. Unlock()
  8. }
  9. type File interface {
  10. ReadWrite
  11. Lock
  12. Close()
  13. }

类型断言

类型断言用于获取接口值的具体值(并检验它的底层类型为指定类型)。
i为接口值,x := i.(T)会判断i的底层类型,若为T,则会将i的具体值赋给x;否则i的底层类型不为T,会触发panic

为了判断i的底层类型但不触发panic,也可使用x, ok := i.(T),若i的底层类型为T,则会将i的具体值赋给xoktrue;否则i的底层类型不为Tx会被赋为类型T的零值,okfalse,但不会触发panic
类似判断映射的值。

  1. func main() {
  2. var i interface{} = "hello"
  3. s := i.(string)
  4. fmt.Println(s) // hello
  5. s, ok := i.(string)
  6. fmt.Println(s, ok) // hello true
  7. f, ok := i.(float64)
  8. fmt.Println(f, ok) // 0 false
  9. f = i.(float64) // panic: interface conversion: interface {} is string, not float64
  10. fmt.Println(f)
  11. }

类型选择可在多条类型断言中选择正确的类型,并执行对应语句。
类似switch,但case后的值为类型,不是值。

语法:

  1. // x为待赋值变量,i为一个接口值
  2. switch x := i.(type) {
  3. case T:
  4. // x 的类型为 T,且被赋为 i 的具体值
  5. case S:
  6. // x 的类型为 S,且被赋为 i 的具体值
  7. ...
  8. default:
  9. // 没有匹配,x 与 i 的类型相同,且被赋为 i 的具体值
  10. }

例:

  1. func do(i interface{}) {
  2. switch v := i.(type) {
  3. case int:
  4. fmt.Printf("Twice %v is %v\n", v, v*2)
  5. case string:
  6. fmt.Printf("%q is %v bytes long\n", v, len(v))
  7. default:
  8. fmt.Printf("Unknown type %T (%v)!\n", v, v)
  9. }
  10. }
  11. func main() {
  12. do(21) // Twice 21 is 42
  13. do("hello") // "hello" is 5 bytes long
  14. do(true) // Unknown type bool (true)!
  15. }

Stringer

fmt包中定义了接口Stringer

  1. type Stringer interface {
  2. String() string
  3. }

fmt.Stringer用于定制fmt的输出
任何实现了方法String() string的类型,在用fmtPrint, Println, Printf等方法输出时,会输出其String()的返回值。
例:

  1. import "fmt"
  2. type IPAddr [4]byte
  3. func (s IPAddr) String() string{
  4. return fmt.Sprintf("%v.%v.%v.%v", s[0], s[1], s[2], s[3])
  5. }
  6. func main() {
  7. hosts := map[string]IPAddr{
  8. "loopback": {127, 0, 0, 1},
  9. "googleDNS": {8, 8, 8, 8},
  10. }
  11. for name, ip := range hosts {
  12. fmt.Printf("%v: %v\n", name, ip)
  13. }
  14. }
  15. /*
  16. 输出:
  17. loopback: 127.0.0.1
  18. googleDNS: 8.8.8.8
  19. */

error

error类型是一个内建接口类型:

  1. type error interface {
  2. Error() string
  3. }

go使用error值表示错误状态。
通常每个函数的最后一个返回值都是一个error值。调用函数的代码可判断该返回值是否为nil,来判断是否发生错误、错误信息(若无错误,则等于nil,否则等于错误信息)。

  1. i, err := strconv.Atoi("42")
  2. if err != nil {
  3. fmt.Printf("couldn't convert number: %v\n", err)
  4. return
  5. }
  6. fmt.Println("Converted integer:", i)

error也可用于定制error的输出
任何实现了Error() string方法的类型(一般为某种自定义错误类,且被转为了error类型,存储在error类型变量中),在输出其error值时,会输出Error() string的返回值。

例:
注意在Error()中,直接使用e会导致死循环,需先使用float64转换e(不太理解?)。

  1. const eps = 1e-8
  2. type ErrNegative float64
  3. func (e ErrNegative) Error() string {
  4. return fmt.Sprintf("Cannot Sqrt negative number : %v\n", float64(e))
  5. }
  6. func Sqrt(x float64) (float64, error) {
  7. if x<0 {
  8. return 0, ErrNegative(x)
  9. }
  10. z, last := 1.0, 0.0
  11. for math.Abs(z-last)>eps {
  12. last = z
  13. z -= (z*z-x)/(2*z)
  14. }
  15. return z, nil
  16. }
  17. func main() {
  18. tests := []float64 {2, 9, -9, 15}
  19. for _, v := range tests {
  20. res, err := Sqrt(v)
  21. if err==nil {
  22. fmt.Printf("Sqrt(%v)=%v\n", v, res)
  23. } else {
  24. fmt.Print(err)
  25. }
  26. }
  27. }

Reader

io包定义了io.Reader接口。

  1. type Reader struct {
  2. s string // Reader读取的字符串
  3. i int64 // 当前读取到的位置
  4. prevRune int // 记录读取中文时候的下标,除在ReadRune时会被赋值,其他时候为-1
  5. }

使用strings.NewReader(s)创建一个从字符串s读数据的Reader。

Read()
io.Reader接口有Read()方法。

  1. func (T) read(b []byte) (n int, err error)

Read从当前Reader中读取数据(从上次未读取的位置开始),保存在切片b中。
返回值为(n, err),分别表示成功读取的字符数、错误信息。
当Reader有未读数据时,errnil。当b的大小足够读完Reader剩余数据时,返回值n为读取位数;否则只会读|b|个位,返回值nb的大小。
当Reader不能读取到任何数据时,返回值n为0,errio.EOF

  1. import (
  2. "fmt"
  3. "io"
  4. "strings"
  5. )
  6. func main() {
  7. r := strings.NewReader("Hello, Reader!")
  8. b := make([]byte, 8)
  9. for {
  10. n, err := r.Read(b)
  11. fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
  12. fmt.Printf("b[:n] = %q\n", b[:n])
  13. if err == io.EOF {
  14. break
  15. }
  16. }
  17. }
  18. /*
  19. 输出:
  20. n = 8 err = <nil> b = [72 101 108 108 111 44 32 82]
  21. b[:n] = "Hello, R"
  22. n = 6 err = <nil> b = [101 97 100 101 114 33 32 82]
  23. b[:n] = "eader!"
  24. n = 0 err = EOF b = [101 97 100 101 114 33 32 82]
  25. b[:n] = ""
  26. */

Image

https://tour.go-zh.org/methods/24

常见功能

格式化输出

使用%v
%v:只输出所有的值
%+v:输出字段类型: 字段的值
%#v:先输出结构体名字值,再输出字段类型: 字段的值

go test

http://c.biancheng.net/view/124.html
http://shouce.jb51.net/gopl-zh/ch11/ch11-01.html

生成随机数

go自带两种随机方式:math/randcrypto/rand
前者更高效,但是是伪随机,生成随机序列;后者较慢,但接近真随机、更安全(Windows上使用CryptGenRandom,其它系统使用/dev/urandom)。

/dev/urandomhttps://zhuanlan.zhihu.com/p/64680713

math/rand
类似C++的rand(),根据种子生成随机序列。
种子可以用rand.Seed(value)设置。相同的种子会得到相同的随机数序列。

一般使用当前时间作为种子:rand.Seed(time.Now().UnixNano())

函数:
rand.Int():返回随机int值。
rand.Int31():返回随机int32值。
rand.Int63():返回随机int64值。
rand.Intn(x):返回值域在的随机int值。x需大于0。
rand.Int31n(x):返回值域在的随机int32值。x需大于0。
rand.Int63n(x):返回值域在的随机int64值。x需大于0。

例:

  1. func RandArray(len, R int) []int {
  2. a := make([]int, len)
  3. for i := 0; i < len; i++ {
  4. a[i] = rand.Intn(R) // [0,R)
  5. }
  6. return a
  7. }
  8. func main() {
  9. rand.Seed(time.Now().UnixNano())
  10. fmt.Println(RandArray(10, 3))
  11. }

crypto/rand
用于与安全相关的随机数生成。

函数:
func Read(b []byte) (n int, err error):以随机byte值填充切片b。返回值n为成功填充的数量。当err==niln==len(b)
func Int(rand io.Reader, max *big.Int) (n *big.Int, err error):返回中的随机数。max需大于0。随机是均匀的。
func Prime(rand io.Reader, bits int) (p *big.Int, err error):返回拥有指定位数的数(大概率是质数)。bits需大于1。

type Reader interface {Read(p []byte) (n int, err error)}
io.Reader is the interface that wraps the basic Read method.

例:

  1. //生成随机byte序列
  2. func RandomBytes(n int) ([]byte, error) {
  3. b := make([]byte, n)
  4. _, err := rand.Read(b)
  5. if err != nil {
  6. return nil, err
  7. }
  8. return b, nil
  9. }
  10. //生成指定字符集的随机字符串
  11. var defaultLetters = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
  12. var defaultMod = byte(len(defaultLetters))
  13. func RandomString(n int) (string, error) {
  14. b, err := RandomBytes(n)
  15. for i := range b {
  16. b[i] = defaultLetters[b[i]%defaultMod]
  17. }
  18. return string(b), err
  19. }
  20. func RandomString2(n int, allowedChars ...[]byte) (string, error) {
  21. var letters []byte
  22. var mod byte
  23. if len(allowedChars) == 0 {
  24. letters = defaultLetters
  25. mod = defaultMod
  26. } else {
  27. letters = allowedChars[0]
  28. mod = byte(len(allowedChars[0]))
  29. }
  30. fmt.Println(letters, mod)
  31. b, err := RandomBytes(n)
  32. for i := range b {
  33. b[i] = letters[b[i]%mod]
  34. }
  35. return string(b), err
  36. }
  37. //生成随机字符串
  38. func RandomString3(n int) (string, error) {
  39. b, err := RandomBytes(n)
  40. fmt.Println(b)
  41. return base64.URLEncoding.EncodeToString(b), err
  42. }
  43. func main() {
  44. fmt.Println(RandomString(10))
  45. fmt.Println(RandomString2(10))
  46. fmt.Println(RandomString2(10, []byte("0123456")))
  47. }

常见语法错误

cannot convert 1 >= 0 (type untyped bool) to type int
原语句:x := int(1>=0)
bool不能跟其它类型做转换。必须使用if判断bool值来为其它int赋值(可以写个bool2int函数)。
根据提示,虽然1>=0类似bool,但它是untyped,和bool也不算同一类型?

Web应用

部分见https://www.zybuluo.com/SovietPower/note/1829667

高并发

http的HandleFuncListenAndServe原理:
https://blog.csdn.net/feifeixiang2835/article/details/88261685

简易的Worker Pool(类似线程池):
https://gobyexample.com/worker-pools
比较麻烦但还能了解点东西的Worker Pool:
https://blog.csdn.net/jeanphorn/article/details/79018205
线程池:
线程的创建销毁、调度都会带来额外的开销,线程太多也会导致系统整体性能下降。所以提前创建若干个线程,通过线程池来进行管理。

通道chan

http://c.biancheng.net/view/97.html
https://www.runoob.com/w3cnote/go-channel-intro.html
无缓冲的通道保证进行发送和接收的 goroutine 会在同一时间进行数据交换;有缓冲的通道没有这种保证。
限制通道的长度有利于约束数据生产者的供给速度,供给数据量必须在消费者处理量+通道长度的范围内,才能正常地处理数据。

主函数结束后,所有 goroutine 就会退出(无论是否执行完)。使用一个chan阻塞,可以实现 goroutine 通知main当它执行完毕后再退出。

如果channel已经被关闭,继续往它发送数据会导致panic: send on closed channel
继续读取数据,仍可读取到已发送的数据,且在读取完成后,可不断读取到零值。
如果通过for range读取chan,即使chan中没有元素,for也会一直等待,并处理进入的数据。只有在channel关闭或break后,for循环才会跳出。
通过i, ok := <-c可以查看Channel的状态,判断值是零值还是正常读取的值。

超时

  1. ch := make(chan int)
  2. quit := make(chan bool)
  3. // 新建一个协程
  4. go func() {
  5. for {
  6. select {
  7. case num := <-ch:
  8. fmt.Println("num = ", num)
  9. case <-time.After(3 * time.Second):
  10. fmt.Println("超时")
  11. quit <- true
  12. // return
  13. // 若3s内没有重新读到数据,写到quit(主函数使用读quit <-quit 阻塞,写到quit时退出,终止协程)(或是在最后return)
  14. }
  15. }
  16. }() //别忘了()

服务器

golang的net/http包实现了apache/nginx的基本功能了(如解析http协议),但是不支持什么url重写,gzip压缩,concat拼接之类复杂的,需要自己实现。也不支持多站点,虚拟目录等。所以一般还是nginx处理静态资源和代理动态请求,go在后面接受代理请求。
go是内置了服务器软件的编程语言。

命名规范

可用包

运行时检测死锁

https://github.com/sasha-s/go-deadlock
https://blog.csdn.net/DisMisPres/article/details/123402901
https://xiaorui.cc/archives/5951

操作excel

https://github.com/qax-os/excelize

创建表后,默认有Sheet1

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注