Golang Notes
在 Ubuntu 安装:
>cd ~
>wget https://dl.google.com/go/go1.11.2.linux-amd64.tar.gz
>tar -C /usr/local -xzf go1.11.2.linux-amd64.tar.gz // 解压到得到的 go 目录,放到 /usr/local 目录下
>vim .bashrc // 写入以下内容
export GOPATH=~/hejtao/go_projects
export GOROOT=/usr/local/go
export GOBIN=$GOROOT/bin
export PATH=$PATH:$GOBIN
>source .bashrc
更新依赖
go mod edit -replace=google.golang.org/grpc=github.com/grpc/grpc-go@latest
go mod tidy
go get -u github.com/grpc/grpc-go@latest
GROM
db.Where(models.Balance{
WalletAddress: "xxx",
ChainId: 666,
}).Attrs(map[string]any{
"balance": 888,
}).Assign(map[string]any{
"balance": gorm.Expr("Balance - ?", 5),
}).FirstOrCreate(&models.Balance{})
// 存在就 balance - 5
// 不存在就创建 {xxx, 666, 888}
// struct 可以换成 map
数据类型:
基本类型:
- 数值
- 整型:
int;int8;int16;int32(rune,Unicode);int64;uint;uint8(byte);uint16;uint32;uint64;uintptrint,uint,uintptr在 32 位系统上是 32 位,在 64 位 系统上是 64 位 - 浮点型:
float32;float64 - 复数型:
complex64;complex128
- 整型:
- 字符串:使用双引号
"a" - 布尔:
true;false - 常量:三种基本类型
在
if语句中,若检验条件为i >= 0,则i的类型不宜为uint型(uint数据始终 >= 0)。尽管内置函数len()返回值是非负整数,但它际返回int型,
基本类型与操作符构成表达式
- 取余
%只用于整型;余数与被除数符号相同,5%3=2;-5%3=-2 5.0/4=1.25;5/4.0=1.25;5/4=1&&若左边的表达式结果为false,不检验右边的表达式;&始终检验两边的表达式- 与或
^; 一元前缀^
聚集类型 (aggregate types):
数组
var a [3]int
r := [...]int{99: 1} // 索引为99的元素,r[99], 等于 1,其他默认 0
p := new([10]int) // 将生成的数组的指针赋给 p, 为 *[10]int 类型
p[0]=1 // 给 p 指向的数组的索引为0的元素赋值 1
数组的长度也是数组类型的一部分,因此 [3]int 和 [4]int 是不同类型的数组,不能进行比较或赋值。
结构
(1)结构的字段(field)
type person struct{ // 定义 person 类型
gender string
age int
}
func main(){
student := person{}
student.age = 16
student.gender = "male"
// or
student := person{
gender : "male",
age : 16, //逗号不能省
}
// or
student := person{"male", 16}
teacher := &person{ //取指针
gender : "female",
age : 30,
}
teacher.age = 36 //指针 teacher 仍然可以进行点操作
}
指针也可以进行点操作。
匿名结构,字段匿名
student := struct{ // 匿名结构
gender : string
age : int
}{
gender : "male",
age : "17",
}
type person struct{
string
int
}
// 按照顺序初始化
student := person{"male", 10}
结构嵌套,
type person struct{
gender string
age int
parents struct{ // 嵌套一个匿名结构
dad, mom : string
}
}
type address struct{
state, city string
}
type person2 struct{
gender string
age int
address // 嵌套你一个结构address
}
func main(){
student := person{gender : "female", age : 10}
student.parents.dad = "Tom"
student.parents.mom = "Lily"
student2 := person2{gender:"female", age:10, address : address{county:"LA" state:"California"} }
student2.address.state = "Massachusetts" // or
student2.state = "Massachusetts"
}
(2)结构的方法(method) 函数与方法
package main
import (
"fmt"
"math"
)
type Point struct{ X, Y float64 }
func Distance(p, q Point) float64 { //函数
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
func (p Point) Distance(q Point) float64 { // 方法,在函数名前增加一个形参(receiver) 类似于Java的this 和python的 self,
return math.Hypot(q.X-p.X, q.Y-p.Y) // 接收者的名称通常取它的类型名称的第一个字母
}
func main() {
p := Point{1, 2}
q := Point{4, 6}
fmt.Println(Distance(p, q)) // 打印 5, 调用函数
fmt.Println(p.Distance(q)) // 调用方法
}
当需要使用方法对值(value of type T,相对于方法来讲就是实参,argument) 的字段进行修改时,使用接收者为指针的方法或者叫指针方法
func (p *Point) ScaleBy(factor float64) { // 接收者参数p的类型是指针类型
p.X *= factor // p在这里是指针,等价于 (*p).X
p.Y *= factor
}
同一个 struct 的方法和字段占据相同的命名空间(name space),因此两者的名称不能重复; 指针方法看作高权限方法。
对方法的调用, 值(实参) 和 接收者(形参)类型要相同
Point{1, 2}.Distance(q) // Point Point
pptr.ScaleBy(2) // *Point *Point
pptr.Distance(q) // 隐含 (*pptr)
p.ScaleBy(2) // 隐含 (&p)
Point{1, 2}.ScaleBy(2) //错误!!!
(&Point{1, 2}).ScaleBy(2)
不仅仅是 struct
package main
import (
"fmt"
)
type INT int
func main() {
var a INT
a = 1
a.Print() // 打印 2
}
func (a *INT) Print() {
*a = 2
fmt.Println(*a)
}
方法是与命名类型(named type)相关联的函数。
引用类型
指针
var p *int
i := 20
p = &i
*p = 10 // i 的值为 10
切片(slice)
var s []int // 声明切片 s
a := [5]int{1, 2, 3, 4, 5}
s = a[:2] // [1, 2], len(s)等于2,cap(s)等于5
s = a[0:1] // [1],len(s)等于1,cap(s)等于5
s = a[3:] // [4, 5],len(s)等于2,cap(s)等于2
s := make([]int, 2, 4) //make([]type, len, cap) ,切片长度为2,底层数组的长度为4
s = append(s, 1) // s 的地址不变
s = append(s, 2, 3) // 生成新的数组,地址改变,容量翻倍,也就是cap(s)等于4*2=8
s1 := []int{1, 2, 3}
s2 := []int{4, 5}
copy(s1, s2) //把 s2 复制到 s1,s1 为 [4, 5, 3]
s1 = []int{1, 2, 3}
copy(s2, s1) // s2 为 [1, 2]
切片的本质是对底层数组的引用;切片的容量(cap)是切片的始索引到底层数组的末索引的长度。
字典(map)
var m map[int]string // key int 型;value string 型
m = make(map[int]string)
m2 := make(map[int]string)
m[0] = "OK"
delete(m, 0) // 删除 m 中键为0的键值对
嵌套,
m := make(map[int]map[int]string) // value map型
m[1] = make(map[int]string)
m[1][2] = "YES"
函数
func main(int, []string) int
means, function main takes an int and a slice of strings and returns an int
函数作为类型,
var f func(func(int,int) int, int) func(int, int) int
不定长变参,闭包
package main
import (
"fmt"
)
func main(){
var_args(1)
var_args(1, 2, 3)
f := closure(10)
fmt.Println(f(1))
fmt.Println(f(2))
}
func var_args(args ...int){
fmt.Println(args)
}
func closure(x int) func(int) int{ // 返回匿名函数
return func(y int) int{
return x + y
}
}
输出:
[1]
[1 2 3]
11
12
通道(channel)
channel 是 goroutine 沟通的桥梁,通过 make 创建,close 关闭
package main
import (
"fmt"
)
func main() {
c := make(chan bool)
go func() {
fmt.Println("I from goroutine !")
c <- true
}()
<-c
}
channel 作为函数形参
package main
import (
"fmt"
)
func main() {
c := make(chan bool)
go Hello(c)
<-c
}
func Hello(c chan bool) {
fmt.Println("Hello, I from goroutine!")
c <- true
}
多个 goroutine,多个 channel
package main
import (
"fmt"
"runtime"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // 开启多核
c := make(chan bool)
for i := 0; i < 5; i++ { // 启动多个 goroutin
go Decomposition(c, i, 100000007)
}
for i := 0; i < 5; i++ { // 多个 channel 阻塞
<-c
}
}
func Decomposition(c chan bool, index int, n int) { // 质数分解
for i := 2; i <= n; i++ {
for n != i {
if n%i == 0 {
fmt.Printf("%d*", i)
n = n / i
} else {
break
}
}
}
fmt.Printf("%d: %d\n", index, n)
c <- true
}
输出:
0: 100000007
2: 100000007
1: 100000007
4: 100000007
3: 100000007
从输出结果的顺序可以看出 goroutine 并非先启动先执行
使用同步包来代替 channel
package main
import (
"fmt"
"runtime"
"sync"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
wg := sync.WaitGroup{}
wg.Add(5) // 添加 5 个 任务(goroutine)
for i := 0; i < 5; i++ {
go Decomposition(&wg, i, 100000007)
}
wg.Wait() // 等到任务数减到 0
}
func Decomposition(wg *sync.WaitGroup, m int, n int) {
for i := 2; i <= n; i++ {
for n != i {
if n%i == 0 {
fmt.Printf("%d*", i)
n = n / i
} else {
break
}
}
}
fmt.Printf("%d: %d\n", m, n)
wg.Done() // 任务数减 1
}
输出:
0: 100000007
2: 100000007
4: 100000007
1: 100000007
3: 100000007
selec{}语句,
如果有多个 case 读取数据,select 会随机选择一个 case 执行,其他不执行;
如果没有 case 读取数据,就执行 default;
如果没有 case 读取数据,且没有 default,select 将阻塞,直到某个 case 可以执行。
package main
import (
"fmt"
)
func main() {
c1, c2, block := make(chan int), make(chan string), make(chan bool)
go func() {
for {
select { // 按随机顺序处理多个 case
case message, open := <-c1:
if !open { // 如果通道 c1 关闭,则跳出无限循环
block <- true
break
}
fmt.Println("A message from main by c1:", message)
case message, open := <-c2:
if !open { // 如果通道 c2 关闭,则跳出无限循环
block <- true
break
}
fmt.Println("A message from main by c2:", message)
}
}
}()
c1 <- 10
c2 <- "hello"
c1 <- 20
c2 <- "world"
close(c1) // 关闭通道 c1
<-block
}
输出:
A message from main by c1: 10
A message from main by c2: hello
A message from main by c1: 20
A message from main by c2: world
package main
import (
"fmt"
"time"
)
func main() {
select {
case <-time.After(2000000 * time.Microsecond):
fmt.Println("2 seconds")
case <-time.After(1999999 * time.Microsecond):
fmt.Println("1.999999 seconds")
}
}
输出:
1.999999 seconds
接口类型(interface):
(1)接口代表某些方法的集合
package main
import (
"fmt"
)
type game interface {
Strike_of_Kings() int
Battle_Grounds() int
}
type contact interface {
Wechat()
QQ()
}
type smartphone interface{ // 接口嵌套,
game
contact
}
type iphone struct {
version string
price float32
user string
}
func (iph iphone) Wechat() {
fmt.Println("I installed wechat on my iphone", iph.version)
}
func (iph *iphone) QQ() {
fmt.Println("I installed wechat on my iphone", iph.version)
}
// iphone 不满足 contact 接口
func (iph iphone) Battle_Grounds() int {
fmt.Println("There are 4 teammates at most in the Battle Grounds.")
return 4
}
func (iph iphone) Strike_of_Kings() int {
fmt.Println("There are 5 teammates at most in the Strike of Kings.")
return 5
}
// iphone 满足 game 接口
func (iph *iphone) New_Version(version string) {
iph.version = version
}
func all_round_game(game) { // 接口作为形参
fmt.Println("Both Strike of_Kings and Battle Grounds have installed.")
}
func main() {
my_phone := iphone{"X", 8316, "Xiaohe"}
my_phone.Wechat()
fmt.Println(my_phone.Battle_Grounds())
all_round_game(my_phone) // my_phone 符合 game 接口,可作为该函数的实参
var _ game = iphone // 确保 iphone 实现了接口game
}
(2)任何类型都满足空接口;空接口 interface{}作为形参可以接受任何类型的实参
package main
import (
"fmt"
)
func main() {
m := make(map[int]interface{})
m[1] = "a"
m[2] = 2
m[3] = false
print_map(m)
}
func print_map(m map[int]interface{}) {
for k, v := range m {
fmt.Println(k, ":", v)
}
}
输出:
1 : a
2 : 2
3 : false
[]string和[]interface{}是不同的类型; 接口是一种抽象类型,可理解为是将所有具体类型按照方法集进行再分类; 指针方法集包含非指针方法集。
(3)接口值(interface value)包含 类型 (接口的动态类型)和 类型值 (接口的动态值) 两个部分,仅当两者均为nil 时,接口才为nil
(4)反射 (reflection)
package main
import (
"fmt"
"reflect"
)
type iphone struct {
version string
price int
user string
}
func (iph iphone) Wechat() {
fmt.Println("I installed wechat on my iphone", iph.version)
}
func main() {
my_phone := iphone{"X", 8316, "Xiaohe"}
Info(my_phone)
}
func Info(itf interface{}) {
t := reflect.TypeOf(itf)
fmt.Println(t)
for i := 0; i < t.NumField(); i++ {
fmt.Println(t.Field(i))
}
fmt.Println("___________________")
v := reflect.ValueOf(itf)
fmt.Println(v)
for i := 0; i < v.NumField(); i++ {
fmt.Println(v.Field(i))
}
}
输出:
main.iphone
{version main string 0 [0] false}
{price main int 16 [1] false}
{user main string 24 [2] false}
___________________
{X 8316 Xiaohe}
X
8316
Xiaohe
控制流 (for if switch defer goto):
(1)for:
for (inti statement);condition;(post statement) {
}
for i := 0; i < 10; i++ {
}
for ; i < 10; { // 去掉分号
}
for i < 10{ // while 语句
}
for{ // 无限循环
}
(2) if:
if (init statement);condition {
}
if (init statement);condition {
}else {
}
(3)switch:
switch (init statement);some value {
case 0:
case f():
...
default:
}
switch {
case 布尔表达式1:
case 布尔表达式2:
...
default:
}
一旦符合条件自动终止,若希望继续检验下面的 case,使用 fallthrough 语句。
(4)defer:
- defer 后必须跟函数引用
- defer 语句被检验后,延迟函数获得变量的拷贝
func a() {
i := 0
defer fmt.Println(i)
i++
return
} // defer 语句打印0
- defer 语句被检验后,延迟匿名函数获得变量的地址
func b() (i int) {
defer func() { i++ }()
return 1 // 将1 赋给 i
} // 返回 2。利用 defer 语句修改外围函数的命名返回值
func c() {
for i := 0; i < 3; i++ {
defer func() {
fmt.Print(i)
}()
}
return
} // 打印 333
- defer 语句被检验后,延迟函数的引用被推入堆栈,当外围函数返回后,按照后进先出的顺序被调用(即使外围函数发生错误,如 panic,延迟函数仍然会被调用)
func d() {
for i := 0; i < 4; i++ {
defer fmt.Print(i)
}
} // 打印 3210
更多细节如,panic, recover(只能用在延迟函数中) 参考 defer blog。
(5)goto:
LABEL:
for {
for i := 0; i < 10; i++ {
if i > 3 {
break LABEL // 跳出与LABEL同级的循环,即跳出无限循环
}
}
}
LABEL:
for i := 0; i < 10; i++ {
for {
continue LABEL
}
}
LABEL:
for {
for i := 0; i < 10; i++ {
if i > 3 {
goto LABEL // 将再次进入无限循环
}
}
}
通常
标签放到goto的后面。
创建工程目录:
Go 工程中共有三个部分:
- src:存放 go 源码文件
- pkg:存放编译后的包文件
- bin:存放编译后的可执行文件
src 目录需要自己创建,pkg 和 bin 编译时会自动创建
步骤:
- 新建工程目录,my_project,并在该目录下创建 src 目录;
- 把 my_project 添加到 GOPATH,GOPATH=/home/user/…;my_project(可以同时添加多个路径目录,Linux 下用冒号:隔开,window 下分号;隔开);
- 在 src 下创建 my_pkg 和 my_cmd;
- 包文件放入到 my_pkg 中,比如 test.go
package my_pkg
import "fmt"
func Test(){
fmt.Println("Hello,world!")
fmt.Println("You used a function defined in my_package!")
}
在命令行 src 目录,执行 go install my_pkg 将创建 pkg 目录并声成 my_pkg.a 文件。 5. my_cmd 中放入 package main,比如 hello_world.go
package main
import(
"my_pkg"
)
func main(){
my_pkg.Test()
}
在命令行 src 目录,执行 go install my_cmd 将创建 bin 目录并生成可执行文件成 hello_world.exe 文件。
目录结构: src/ $\quad$ my_pkg/
$\qquad$ test.go
$\quad$ my_cmd/ $\qquad$ hello_world.go
Tips:
import 包 A 的时候,会自动调用包 A 的 init()函数(i 字母小写)。如果该包 A 又 import 了别的包 B,会优先调用包 B 的 init()函数,最后才调用 main 包的 init()函数。 一个包的 init()函数可以定义多个,每个都会被调用,调用的顺序按文件名排序。同一个文件也可以定义多个 init 函数。
其他:
fmt.printfverbs:%x %b:16 进制,2 进制显示;%t:显示 bool 结果;%T:显示值的类型;%v:显示值;%p:显示地址;\n:换行- Sublime text 3 上一个编辑处: alt+- 下一个编辑处: alt+shift+-
- GoSublime: GoSublime 快捷键列表:ctrl+.+. (连击 .) 查看声明:ctrl+.+h 代码跳转:ctrl+shift+左键 package control:ctrl+shift+p
- Goland:退回上一次光标位置:ctrl+win+alt+左键