- Google开发,高性能,高并发,静态编译,垃圾回收,标准库丰富,跨平台
环境
编译运行
GOPATH
下包含目录bin
(存放编译后的二进制文件)、pkg
(存放编译后的库文件)、src
(存放源码文件)
1
2
3
4
5
6
|
go build xx.go # 编译型语言
go run xx.go # 运行
go get -u github.com/xx/xxx # 获取外部包
go env -w GO111MODULE=auto # 视情况而定
go mod init dir_name # 初始化
|
结构
1
2
3
4
5
|
package main
import xx
func main(){
xxx
}
|
库
os
1
2
3
|
file,err := os.Create("xxx") // 创建文件
os.Exit(0)//跳出程序
|
time
1
2
3
|
now = time.Now() // 当前时间
// 年月日时分秒
now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second()
|
时间持续:time.Duration()
1
|
time.Sleep(time.Duration(n) * time.second) // 停止n秒
|
时间运算
1
2
3
4
5
|
now.Add(time.Duration(2) * time.Hour) // 加2小时
now.Sub(xx) // 减
now.Equal(xx) // 是否相同
t.Before(u) // t是否在u前
t.After(u) // t是否在u后
|
定时器
1
2
|
ticker = time.Tick(time.Second) // 一秒间隔的定时器
<-ticker // 从定时器接收值
|
获取时间戳
net
http
1
2
|
http.Handle("/", http.FileServer(http.Dir("."))) // 根路径创建文件服务器
http.ListenAndServe(":8080", nil) // 启动HTTP服务器
|
tcp
1
2
3
4
|
server, err := net.Listen("tcp", "127.0.0.1:1080") // tcp监听
client, err := server.Accept()
port := binary.BigEndian.Uint16(buf) // 端口接受
dest, err := net.Dial("tcp", "x:x:x:x:aaa") // 建立tcp连接
|
math
1
2
3
|
math.Pi
math.Sin(xx)
a := math.MaxInt32 // 初始化最大值
|
基本结构
数组
- 值类型,可修改数组成员,长度不可变
[n]*T
:指针数组,*[n]T
:数组指针
创建
1
2
3
4
5
6
|
a := [count]int{1,2}
// count数量的int数组,{}为具体数据,若写[...]编译器自动计算长度
// 若不包含数字,会导致直接给其赋值失败,只能append作为切片操作
// 索引值
var langArray = [...]string{1:"Golang",3:"Python",7:"Java"}
|
遍历
1
|
for index,value := range Array
|
交换元素
二维数组
1
2
3
4
5
|
matrix2 := [][3]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
|
切片
- 可变长,引用类型,切片间不能比较,不能使用
==
,唯一比较方法是和nil比较
- nil切片没有底层数组,长度容量都为0
初始化
1
2
3
4
5
6
7
8
|
var var_name []T // T:元素类型,未申请内存
var c = []bool{false,true}
//基于数组得到切片
a := [5]int{55,56,57,58,59}//数组
b := a[1:4]
d := make([]int,5,10)// 元素个数5,容量10
|
返回
1
|
return []int{i, j} //创建一个包含两个整数元素的整数切片
|
增加
底层数组不能容纳新增元素时,切片容量按照1,2,4,8,16的规则自动进行扩容
1
2
|
slice = append(slice, element1, element2...) //返回一个新的切片,赋值给原始切片来更新,添加到切片末尾
slice = append(slice, slice...) //合并两个切片,修改函数内的局部变量而非外部传入的原始切片。
|
创建
1
2
|
slice := make([]dataType, length, capacity)
//长度为length、容量为capacity,容量表示切片底层数组的大小,当切片长度达到容量时,底层数组会扩展
|
取切片
1
2
|
q = q[:len(q)-1] //取第一个到倒数第二个
q = q[:len(q)] //取第一个到最后一个
|
删除元素
1
2
|
a := []string{"a","b","c","d"}
a = append(a[0:2],a[3:]...) // 删除"c"
|
切片容量
复制切片
将一个切片的数据复制到另一个切片空间中,区别于赋值,其中一个改变不影响另一个
1
2
3
|
copy(destSlice, srcSlice []T) // 返回的int为实际复制的元素个数
c := make([]string, len(s))
copy(c, s)
|
字符串
创建
1
|
str := "" // 字符串是不可变的,即一旦创建,就不能更改其内容
|
1
2
3
4
|
// 多行字符串
s := `first
second
`
|
连接
1
2
3
|
//连接要赋值给新的字符串
str1, str2 := "1", "2"
ans := str1 + str2
|
切片操作
1
2
|
tmp := s[i : i+len(p)]
//用于从字符串 s 中截取一个子串,起始索引为 i,长度为 len(p)
|
字符串切片拼接
1
2
|
s := []string{"1", "2"}
ans := strings.Join(s, "+") // 1+2
|
分割
1
2
|
s := "1x2x3"
p := strings.Split(s, "x") // ["1","2","3"]
|
查找
1
2
|
strings.Index(s, "a") // "a"第一次出现位置
strings.LastIndex(s, "z") // "z"最后一次出现位置
|
子串
1
2
3
|
t, x, s := "ABC", "A", "AB"
strings.ContainsRune(t, x) // 判断t中是否包含x
strings.Contains(t, s) // 判断t是否包含子串s
|
前后缀
1
2
3
|
// 返回bool类型
has := strings.HasPrefix(s, "pre")
has := strings.HasSuffix(s, "suf")
|
计数
重复
1
|
strings.Repeat(str, n) // 重复n次
|
替换
1
|
strings.Replace(src_str, old_sub_str, new_sub_str, n) // n替换次数, 负数表示替换所有匹配子串
|
大小写
1
2
|
strings.ToLower(a)
strings.ToUpper(a)
|
类型
自定义类型
1
|
type TypeName underlyingType // 将 TypeName 定义为底层underlyingType类型
|
结构体类型
- 实例化才分配内存,占用一块连续内存
- 字段大写表示公开,小写表示私有
1
2
3
4
5
6
7
8
9
|
type Person struct {
string // 匿名字段,每种数据类型只能有一种, x.string输出
Age int
x another_struct_type // 结构体嵌套
}
type hp struct{ sort.IntSlice }
//在结构体定义了一个匿名字段,类型是 sort.IntSlice,即hp可以使用sort.IntSlice的方法
//sort.IntSlice实现了sort.Interface 接口
|
初始化
1
2
3
4
|
p := person{
name: "jshiro",
age: 18,
}
|
结构体方法
1
2
3
4
5
|
func (p person) check(age int) bool {
return p.age == age
}
a.check(12) // a为实例对象
|
结构体指针
1
2
|
var a = new(person) // a的类型为*person结构体指针
(*a).name = "haha" //调用赋值 或 a.name = "haha"
|
取结构体地址进行实例化
1
2
|
x := &person{}
x.name = "hh"
|
自定义构造函数
1
2
3
4
5
6
7
|
func newPerson(name, city string,age int8) *person{
return &person{
name: name,
city: city,
age: age,
}
}
|
结构体标签Tag
- Tag携带元信息,可在运行时通过反射读取,用于序列化或数据库交互
- 在结构体字段后方定义,反引号包裹
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
type A struct{
Name string `json:"name" db:"name_db" xml:"name_xml"`
}
p := Person{Name: "h"}
// 获取反射值对象
v := reflect.ValueOf(p)
t := v.Type() // main.Person
// 遍历
for i := 0; i < v.NumField(); i++{
field := t.Field(i)
// field.Name : Name
// field.Tag.Get("json") : name
// field.Tag.Get("db") : name_db
}
|
数据结构
空:nil
链表
结构体:
1
2
3
4
|
type ListNode struct {
Val int
Next *ListNode
}
|
创建头结点指向链表head:
1
2
3
4
5
6
|
prev := &ListNode{Val: 0, Next: head}
// 创建一个新的链表头节点,并用一个指针指向头节点
dummy := &ListNode{}
current := dummy//最终返回d.next
|
创建空结点
1
|
var prev *ListNode //空结点
|
判断初始链表 空 或 一个结点 :
1
2
3
|
if head == nil || head.Next == nil{
return head
}
|
判断:
1
2
3
|
if q != nil && q.Next != nil{
xxx
}
|
哈希表
- map,映射,无序,引用类型,必须初始化才可使用
- 默认初始值nil,make函数分配内存
创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
hashTable := map[int]int{} //键值都为int型的空哈希表,初始值都为0
mp := map[string][]string{} //键为string字符串,值为[]string,字符串切片
// make(map[keyType]valueType) 键值对
//使用make时不能指定数量
mp := map[rune]bool{} // 使用 rune 作为键类型,表示 Unicode 字符
//取字符串中字符
mp
//创建键值对映射加入hash表h中
temp := map[string]int{ip.String(): port}
hp = append(hp, temp)
// 初始化
a = make(map[KeyType]ValueType,[cap]) //cap表示map的容量
b := map[int]bool{
1: true,
2: false,
}//声明同时初始化
|
删除
1
|
delete(map, key) //删除哈希表map中的键为key的键值对
|
添加
1
|
a["XXX"] = 100 // 添加键值对
|
遍历
1
|
for k, v := range Map{}
|
判断键是否存在
1
2
|
value, ok := scoreMap["xx"]
// ok返回true存在,值返回给value;false不存在,value为0
|
链表中用法
1
2
3
4
5
6
|
mp := map[*ListNode]struct{}{}
//此时主要留键,需要的是结点而不是结点中的值,所以*ListNode
//值为空结构体
//之后的赋值
mp[head] = struct{}{}
|
元素为map类型的切片
1
2
3
|
var mapSlice = make([]map[string]int,2,2) // 长度,容量 [nil nil]
mapSlice[0] = make(map[string]int, 4)//初始化
mapSlice[0]["XXX"] = 100 //[map[XXX:100] map[] map[] map[]]
|
值为切片类型的map
1
2
3
|
var sliceMap = make(map[string][]int,8)//只完成map初始化
sliceMap["xxx"] = make([]int,8)//完成对切片的初始化
sliceMap["xxx"][0] = 100 //xxx:[100 0 0 0 0 0 0 0]
|
堆
定义
1
|
type hp struct{ sort.IntSlice }//定义堆类型
|
less
1
2
3
4
|
func (h hp) Less(i, j int) bool { return a[h.IntSlice[i]] > a[h.IntSlice[j]] }
//(h hp)是一个方法的接收者声明,表明Less方法是hp类型的方法
//方法内部,h是当前调用该方法的hp类型的实例,h可访问当前实例的属性和方法
//比较 hp 类型中两个元素的大小,规则是根据切片 a 中元素的大小进行比较。
|
push
1
2
3
4
5
6
|
func (h *hp) Push(v interface{}) { h.IntSlice = append(h.IntSlice, v.(int)) }
//(h *hp)是指方法绑定到 hp 类型指针的实例上
//方法内部需要修改实例内容,所以用指针
//用于向hp类型的切片添加一个元素
//v interface{} :方法接收一个空接口类型的参数 v,即任意类型的值。
//v转化为int类型追加到h类型实例切片中,v.(int)为类型断言,只能用于接口类型
|
pop
1
2
3
4
5
6
7
|
func (h *hp) Pop() interface{} {
a := h.IntSlice; //获取 hp 类型中嵌套的 sort.IntSlice 实例
v := a[len(a)-1]; //获取切片中最后一个元素
h.IntSlice = a[:len(a)-1]; //删除切片中最后一个元素
return v //返回被弹出的元素
}
//hp类型弹出一个元素,最后一个元素取出,切片缩短一个元素,返回被取出的元素
|
创建
1
2
3
|
q := &hp{make([]int, k)}
//&hp{} 创建一个 hp 类型的实例,并返回实例的指针
//长度为 k 的 int 类型切片,并将其作为初始化值赋给 sort.IntSlice
|
初始化
1
2
3
|
heap.Init(q)
//将实现了 heap.Interface 接口的堆初始化
//sort.Interface 接口定义了一组用于排序的方法,其中包括 Len、Less 和 Swap。而 heap.Interface 接口在此基础上扩展,添加了 Push 和 Pop 方法,使得实现了 heap.Interface 接口的类型可以被用作堆数据结构。
|
基础操作
条件
if-else
1
2
3
4
5
6
7
|
if xxx {
xxx
} else if xxx {
xxx
} else {
xxx
}
|
switch
1
2
3
4
5
6
|
switch a { // 也可删除a, 直接在case中写if-else判断的条件
case 1:
xxx
default:
xxx
}
|
循环
for range
1
2
3
4
|
for i, x := range nums // range遍历nums数组, i为当前元素的索引, x为当前元素的值
for k, v := range maps // range遍历maps映射, k:键, v:值
for i, ch := range s[:sLen-pLen] //遍历字符串s的前sLen-pLen个字符
|
for
1
2
3
4
5
6
7
8
9
10
|
// for 初始语句; 条件表达式; 结束语句 任意一个都可省略
for i:=0; i < len(mp); {
i++
}
//遍历可能变化的数组
//m中的值为bool型,直到m[key+1]为false,循环停止
for m[key+1] {
key++
}
|
格式化输出
1
|
fmt.Println(xx,xx) // 输出后换行
|
查看数据类型
1
2
|
fmt.Printf("%T", str)
// %b:二进制, %o:八进制, %x:十六进制, %#v:结构体名+结构体, %.2f:2位浮点数, %p:地址值, %v:数据值, %s:字符串, %+v: 字段名+值
|
格式化
1
2
|
fmt.Fprintf(out, "there's '%s'\n", s)
fmt.Errorf("%w", err) // 错误输出
|
输入输出
从程序的标准输入中读取内容
1
2
3
4
5
6
|
scanner := bufio.NewScanner(os.Stdin)//声明创建 bufio.NewScanner 类型的变量 scanner
for scanner.Scan() { //循环读入下一行,并移除行末换行符,读到一行返回true,无内容返回false
fmt.Println(scanner.Text())//读取的内容
fmt.Print(scanner.Bytes())//扫描到的字节序列
}
|
文件中读取内容
1
2
|
f,err := os.Open("xx.txt")
fileread := bufio.NewScanner(f)
|
设置扫描器缓冲区大小
1
2
3
|
scanner.Buffer(nil, math.MaxInt32)
//第一个参数:表示用于存储扫描器缓冲区的字节切片, nil表示不使用自定义缓冲区
//第二个参数:缓冲区最大容量
|
1
2
|
xxx.Flush()
//将缓冲区中的数据刷新到底层的 io.Writer 接口对象中,确保数据被写入到底层的存储介质中(文件中)
|
创建带有缓冲区的写入器
1
|
func NewWriter(w io.Writer) *Writer
|
读取用户输入
1
2
3
4
5
6
7
8
|
// 循环读
reader := bufio.NewReader(os.Stdin)
for {
input, err := reader.ReadString('\n')
input = strings.TrimSpace(input) // 清理输入
}
ver, err := reader.ReadByte() // 一字节一字节读
|
输入
1
2
3
4
|
n,m,_ := fmt.Scan(&a, &b, &c)
var n int
fmt.Scanf("%d\n", &input) // 扫描用户输入
|
ACM方式输入,已知行数
1
2
3
4
5
6
|
var t,a,b int
fmt.Scanln(&t)
for i:=0;i<t;i++{
fmt.Scanln(&a,&b)
fmt.Println(a+b)
}
|
读取
1
2
|
buf := make([]byte, 4)
io.ReadFull(r, buf) // 从r读取数据直到buf填满
|
数字解析
1
2
|
f, _ := strconv.ParseInt("12", 10, 64) // 进制: 10, 0 表示自动推测, 精度: 64
n1, err := strconv.Atoi("123") // 123
|
文件
打开文件
1
2
3
4
|
file = os.Open("./xx.txt")
file, err := os.OpenFile("a.txt", os.O_CREATE|os.O_WRONLY, 0644)
// O_WRONLY只写, O_CREATE不存在则创建, O_RDONLY只读, O_TRUNC存在则清空, O_APPEND追加
defer file.Close()
|
文件写入
1
2
3
|
writer := bufio.NewWriter(file)
writer.WriteString("hello") // 将内容写入缓冲区
writer.Flush() // 将缓冲区内容写入磁盘
|
文件读取
1
2
|
var tmp = make([]byte, 128) // 字节切片存储
n, err := file.Read(tmp) // 读取到tmp中
|
处理读取结束
1
2
3
|
if err == io.EOF{ // 当把文件读完,End Of File
break
}
|
逐行读取
1
2
|
reader := bufio.NewReader(file)
line, err := reader.ReadString('\n') // 读取一行直到\n
|
数据复制
1
|
_, _ = io.Copy(dest, reader) // 将reader中数据复制到dest中, 阻塞直到完成或错误
|
错误处理
err
1
2
3
4
|
func xxx(xx) (xxx, err error) {
return xx, nil // 无错误
return nil, errors.New("error_information") // 错误
}
|
panic/recover模式
recover
只在defer
调用的函数中有效,panic将会异常退出
1
2
3
4
5
6
7
8
9
10
|
func b() {
// defer 确保 b函数结束前执行该匿名函数
defer func() { // defer需要在可能panic前出现, 捕获并处理panic
err := recover() // recover必须和defer一起使用, 捕获panic引发的错误
if err != nil { // 未panic:返回nil, panic:返回panic参数
fmt.Println("func b error")
}
}()
panic("panic in b") // 模拟错误情况
}
|
函数
a是func变量类型,可赋值b := a
1
2
3
4
|
// ... 表示可变参数,切片类型,参数可为函数类型: func(type) type
func fun_name(arg1 type, arg2 ...type) (ret1 type, ret2 type){ // 返回值可只写类型
return xxx, xxx
}
|
匿名函数
1
2
3
|
func(args)(ret_value){
xxx
}() // 括号表示立即执行
|
闭包
闭包 = 函数 + 引用环境 = 函数 + 外层变量的引用
1
2
3
4
5
6
7
8
9
|
func a(name string) func() { // 定义函数,返回值是函数
return func() {
fmt.Println("hello",name)
}
}
func main() {
r := a("Jshiro")
r() // hello Jshiro
}
|
接收者
- 方法是一种作用于特定类型变量(Receiver接受者)的函数,接受者类似于其他语言中的
this
与self
- 接收者类型是指针类型,则改变后会改变原值;值类型,则改变后不会改变原值,操作的是副本
1
2
3
4
5
|
// String 方法为Person类型定义了一个接收者方法
// p 是接收者变量,Person 是接收者类型,接收者变量一般为接收者类型的首字母小写
func (p Person) String(args) string {
return fmt.Sprintf("Name: %s, Age: %d", p.Name, p.Age)
}
|
指针
- 指针不能进行偏移和运算,安全指针,函数传参是值拷贝
- 修改变量值可创建指向该变量地址的指针变量,传递数据使用指针
定义
1
|
var ptr *int // 指向int类型变量的指针
|
赋值访问
1
2
3
|
a := 10
ptr = &a // 变量a的地址赋给指针ptr
*ptr // 访问变量a
|
函数
1
2
3
4
5
6
7
8
|
// 函数内
func addOne(x *int) {
*x = *x + 1 // 通过指针修改外部变量的值
}
// 函数外
a := 10
addOne(&a)
|
基础用法
“comma, ok” 惯用法
1
2
3
4
5
6
|
value, ok := myMap[key]// ok是bool值,value是key对应的值
if ok {
// 键 key 存在于 myMap 中,可以使用 value 进行操作
} else {
// 键 key 不存在于 myMap 中
}
|
1
2
3
|
if _, exists := mp[x]; !exists {
//若x不存在于mp中
}
|
获取进程信息
1
2
3
4
5
6
7
8
9
|
os.Args // 获取运行参数, 第一个为可执行程序路径
os.Getenv("PATH") // 获取环境变量
os.Setenv("A", "B") // 设置环境变量
// 命令执行
buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
if err != nil{
panic(err)
}
|
数据类型转化
1
2
3
|
s := []byte(str) //将字符串转化为字节切片
str := string(s) //字节转化为字符串
high := int(num[0]) - 48 //以索引方式取字符串单个字符,为byte型,转换为数字
|
多变量赋值
1
|
left, right, n := 0, 0, len(nums)
|
字母表数组
1
2
|
var pCount [26]int //统计字母数量
pCount[s[i]-'a']++ //字符串s
|
声明变量
1
2
3
4
5
6
7
8
9
10
11
12
|
var scount, pcount [26]int
//声明多个变量
var (
a = xxx
b = xxx
)
// 函数内部
a := 10
// 初始化
var a int = 10
// 匿名变量 不占用命名空间,不分配内存
_
|
声明常量
1
|
const con = 1 // 定义时必须赋值
|
位运算符
延迟执行
1
2
3
|
// 延迟处理语句按defer逆序执行: 先xx2后xx1
defer xx1
defer xx2
|
匿名函数或闭包
1
2
3
|
x := func() bool{
xxx
}
|
创建函数赋值给x,x为函数类型变量
1
2
|
len(nums) //nums为数组,则为数组长度
len(mp) //mp是哈希表,为键值对的数量
|
1
2
3
4
5
|
sort.Slice(s, func(i, j int) bool { return s[i] < s[j]}) //升序
sort.Ints(nums)//对整数切片进行升序排序,会修改传入的切片
sort.Ints(a[:])
sort.Strings() //用于对字符串切片进行排序
|
1
2
3
4
5
|
type Interface interface {
Len() int // 返回集合中的元素个数
Less(i, j int) bool // 报告索引 i 的元素是否应排在索引 j 的元素之前
Swap(i, j int) // 交换索引 i 和 j 处的元素
}
|
1
2
3
4
5
6
7
8
9
|
func max(arg ...int) int {
res := arg[0]
for _, v := range arg {
if v > res {
res = v
}
}
return res
}
|
1
|
rand.Intn(100) //随机0~99
|
1
2
3
4
|
// new
var a *int
a = new(int) // *a对应该类型的默认值:0
*a = 100 // 修改变量
|
1
2
3
4
|
// make 只用于slice、map、chan的内存创建, 返回引用类型本身
var b map[string]int
b = make(map[string]int,10)
b["xxx"] = 100
|
基础知识
拷贝
- copy复制 比 等号复制 慢
- copy复制为值复制,改变原切片的值不会影响新切片
- 等号复制为指针复制,改变原切片或新切片都会对另一个产生影响
- 值传递,切片本身是一个 struct 结构,参数传递时会发生值拷贝
- 使用append的切片的底层数组相同,所以建议append追加后的新切片赋值给原切片
- 或使用copy函数
深拷贝和浅拷贝
浅拷贝:
深拷贝:
- 任何对象被完完整整拷贝一份
- 拷贝对象与被拷贝对象不存在任何联系,不会互相影响
高级操作
序列化
JSON序列化(JavaScript Object Notation)
- 保存JS对象的方式,
"key1": value1, "key2": value2
- go语言数据 —> JSON格式字符串
1
2
3
4
|
import "encoding/json"
data,err := json.Marshal(c1) // 序列化后,前端可直接识别
// 打印时:data为16进制编码, string(data)可见字符串
|
反序列化
1
2
3
|
jsonStr:=`{"Title":"H","Students":[{"ID":0,"Name":"stu0"},{"ID":1,"Name":"stu1"}]}`
var c2 class
err = json.Unmarshal([]byte(jsonStr), &c2) // 字节数据, 传入指针才能更改
|
接口
定义接口
1
2
3
4
5
|
type interface_name interface {
func1(args) string
Serve(c context.Context, conn network.Conn) error
...
}
|
实现接口
1
2
3
4
5
6
7
8
9
10
|
type Person struct {
Name string
}
func (p Person) interface_name() string { // 值接收者
xxx
}
func (a *Animal) interface_name() string { // 指针接收者
xxx
}
|
空接口
没有定义任何方法的接口,空接口类型的变量可以存储任意类型的变量
1
2
3
4
|
var x interface{}
x ="hello"
x = 10
x = false // 皆可
|
类型断言
1
2
3
4
5
|
if str, ok := x.(string); ok {
fmt.Println("x is a string", str)
} else {
fmt.Println("x is not a string")
}
|
网络编程
1
2
3
4
5
6
7
|
client := &http.Client{}
var data = strings.NewReader(`{"key1":"value1"}`)
req, err := http.NewRequest("POST", "url", data) // 创建请求
req.Header.Set("Key", "value")
resp, err := client.Do(req) // 发起请求
defer resp.Body.Close()
bodyText, err := io.ReadAll(resp.Body) // 读取请求响应
|
并发
go
启动新goroutine线程,环境在多个goroutine间多路复用,
1
2
3
|
for { // 主函数循环可继续执行,不会阻塞
go func(xx)
}
|
可取消上下文
1
2
3
4
|
ctx, cancel := context.WithCancel(context.Background())
// 创建可取消的子上下文ctx及取消函数cancel, cancel()可放于goroutine中, 可取消ctx, 基于ctx的上下文也取消
<-ctx.Done() // 等待上下文取消,关闭通道
|
算法方法
子串
两层循环
1
2
3
4
5
|
for start := 0; start < len(a); start++{
for end := start; end > 0; end--{
//每一个子串的操作
}
}
|
滑动窗口
单调队列+双端队列,队列维护数组的索引,保证索引对应数组的元素单调
动态规划
找子问题,可以连接前面的问题的关系
轮转
- (i + 轮转数) % 数组长
- 先逆置,再对前k个逆置,对后m个逆置
反转链表
1
2
3
4
5
6
7
|
//pre 结点Next初始化为nil,即空结点
for cur != nil{
next = cur.Next // 将 next 指向当前节点的下一个节点
cur.Next, pre, cur = pre, cur, next // 反转当前节点的指针方向,同时更新 pre
}
//最终pre为头
|
开发
中间件
1
2
3
4
5
6
|
// 预处理 + 后处理, 路由上可注册多个Middleware
func Middleware (args..){
// pre-handle
Next()
// after-handle
}
|