Introduction
Go users: https://github.com/golang/go/wiki/GoUsers web services, devops: docker + k8s
Visual Studio Code go-plugin, vim go-plugin
Install and set up a runnable demo project, a brief guidance can see here: Official Setup Go project
If forget, read through the tutorials one by one: https://go.dev/doc/
Other resources:
-
The Go programming Language
-
Effective Go 这个更深入了,特别是用法方面
What are go package and module is explained here:
https://go.dev/doc/code
A package
is a collection of source files in the same directory that are
compiled together. Functions, types, variables, and constants defined in one
source file are visible to all other source files within the same package.
A repository contains one or more modules. A module
is a collection of related
Go packages that are released together. A Go repository typically contains only
one module, located at the root of the repository. A file named go.mod there
declares the module path: the import path prefix for all packages within the
module. The module contains the packages in the directory containing its go.mod
file as well as subdirectories of that directory, up to the next subdirectory
containing another go.mod file (if any).
An import path
is a string used to import a package. A package’s import path
is its module path joined with its subdirectory within the module. For example,
the module github.com/google/go-cmp contains a package in the directory cmp/.
That package’s import path is github.com/google/go-cmp/cmp. Packages in the
standard library do not have a module path prefix.
Example:
1 | // A module is a collection of related Go packages that are released together |
package
name in go file should be the same as folder name.
Important env variables:
1 | ## https://maelvls.dev/go111module-everywhere/ |
Understand run
, build
, install
, get
subcommands. pluralsight has Go CLI playbook
course.
1 | go version |
数据类型
基本数据类型: int32, float64, bool, string(没有char type) 复合数据类型: array, slice, map, function, pointer, struct, interface
&(取地址符,没有地址的算数运算), *t
变量赋值方式
1 | var xx = 23 |
常量使用
Use all upper cases
字符串类型
1 | str1 := "abc" |
强制类型转换
1 | int() |
算数运算符
关系运算符
<, >, <=, >=, ==, !=
逻辑运算符
&&, ||, !
位运算符
&, | , ^, &^(位清空), <<, >>
If-else
condition 没有括号, no ternary
1 | // if 还可以有初值,和for loop类似了 |
Switch
每个case 自带break。
关键字 fallthrough
只能放case最后一行, 连接执行2个case且一定执行。
case 后的数据类型必须和switch一致, case 可无序,多个condition可用逗号分开。
switch 省略条件相当于switch true
(tagless switch), 也可省略后,把condition 写在case 后面,就像if-else if了
也可以有初值switch t = 100 {..}
, t
only use in switch block
For loop
no while loop
语法和C一样: for init; conditionl; post {}
condition no bracket
1 | for {} |
Goto
前后跳都可以
Array
没赋值的默认值和C语言一样:
1 | var a [10]int // element default is 0 |
对于数组 len(a) 长度= cap(a) 容量
1 | // same address usage as C language |
二维数组
1 | var arr := [3][3]int{{},{},{}} |
Slice
动态数组,大小可变,背后有一个底层数组.
1 | // 不写长度, slice = nil, no underlying array |
Map
To understand map, see this question and the comment: https://stackoverflow.com/questions/40680981/are-maps-passed-by-value-or-by-reference-in-go this is a go playground for map to help understand its behavior:
If a map isn’t a reference variable, what is it?
1 | // nil map, 不能直接用 |
String
1 | // UTF-8 coding |
defer
用于延迟函数或方法的执行, 用defer
控制的调用会等到它的containing/surrounding function执行完了之后再执行,并且当所有延迟的部分执行结束之后,containing function才执行return 语句。
如果有多个defer
,则按照后进先出的顺序。
用法:
- 关闭连接,文件,比如defer close() 在函数的开头.
- panic, recover,defer函数执行完毕后,异常才会被抛出到上一层.
注意,延迟函数的arguments的值,在defer
的时候就固定了,值也可能是地址,则复合对象的内部值可能被改变了。
1 | func deferTest() { |
Function
函数也是一种复合数据类型,可以用%T
查看。
注意函数的参数类型和参数名是反过来写的,且返回值类型列表也写在最后。
函数名如果大写开头 表示公共函数,可被其他package调用,比如fmt.Println(),否则只能package内部访问。
参数传递没有python这么多形式。
参数传递也有值传递和引用(地址)传递. 值传递: int, float, string, bool, array, struct 引用传递: slice, map, chan
1 | // (int, int) 对应了 return val1, val2的类型 |
可变参数...
,只能放到参数列表的最后,且只能有一个可变参数:
1 | // ...type,0个到多个参数都可以,比如Println()就是 |
可以定义函数变量,然后赋值,其实赋值的是函数的地址:
1 | var c func(a ...interface{}) (n int, err error) |
Anonymous functions and closures 匿名函数,没有函数名,可以赋值给变量 或 直接调用. 所以说Go是支持函数式编程的,匿名函数可以作为其他函数的参数(这个作为参数的函数就叫回调函数),注意不是函数执行的返回值作为参数,是函数本身! 匿名函数也可以作为返回值(闭包)
1 | // no function name and call it |
回调函数:
1 | // callback function |
闭包closure, 这里外部函数返回后,匿名函数把外部函数内部的资源i
保留了下来。Python中local function一样的道理.
A closure is a function value that references variables from outside its body.
1 | // `func() int` is treated as return type as a whole |
Pointer
指针类型符号*
, 和C语言一样.
1 | // nil is pointer's default vaule |
数组指针,指向数组的指针
1 | arr := [3]int {1, 2, 3} |
指针数组,元素是指针的数组
1 | a := 1 |
For example:
1 | // 指向 指针数组的指针 |
函数指针,指向函数的指针, Go中函数名就是函数的地址,如同slice, map一样:
1 | var c func(a ...interface{}) (n int, err error) |
指针函数,返回指针值的函数:
1 | // 2个地址输出是一样的 |
结构体是值类型的,和array一样,结构体名不是它的地址。
1 | // 这是内容复制 |
make
只能创建map, slice, channel等
make
vs new
: https://www.godesignpatterns.com/2014/04/new-vs-make.html
new
returns pointer:
1 | // they are the same |
new
可以创建任意类型空间 和 返回任意类型的指针:
1 | pint := new(int) |
匿名结构体,和匿名函数类似的定义用法
1 | // 这里定义了一个匿名结构体 同时进行赋值 |
匿名字段,字段没有名字,且字段类型不能重复:
1 | type Worker struct { |
结构体嵌套:
1 | type A struct { |
如果对嵌套结构体使用了匿名字段,则相当于融合到了当前结构体,访问的时候不用中间层了。这在OOP 中使用到了:
1 | type A struct { |
Type
type
除了定义结构体,接口,还可以定义全新的类型 或者 别名:
function type and value
1 | // myint, mystr 就是全新的类型了 |
Go语言实现函数式编程的时候,如果函数复杂,可以用type
简化,这里的例子是作为返回值,也可以作为参数:
1 | // 把func(string, string) string 整体别名叫做myfun |
还需要注意的是,一个package中定义的别名,不能在其他包中为它添加method.
Error
Go中不要把错误 和 异常弄混了,这个还要单独看其他文章区分一下使用环境。
Go中错误也是一种数据类型error
, 惯例是error
在函数中返回值的最后一个位置, error其实是一个接口:
1 | type error interface { |
使用:
1 | import { |
自己如何创建错误呢, 通过errors.New
这个package提供的函数 或者fmt.Errorf
:
1 | import { |
以上只是简单的例子,复杂的error 是通过结构体实现的,里面包含了错误的具体信息,然后实现了error
这个接口.
Panic and Recover
和defer
一起使用较多,遇到panic
,函数后续会停止执行,然后逆序执行所有已经遇到的当前层的defer
函数,没遇到的不会执行,因为中断了,最后往上层抛出异常,
defer
函数中可能包含recover
来恢复panic
。
1 | func A() { |
哪些场景适合使用panic呢?
- 空指针
- 下标越界
- 除数为0
- 分支没有出现
- 错误输入