概述
一门后端语言可以分成数据处理+外部交互+类型抽象+并发编排四部分。
基础配置
选择一个空白文件夹,在终端中执行
其中 xxx 是项目名。若要推送到 github,尽量规范命名:github.com/cheyanne/project
项目根目录创建 main.go,其中开头必须是 package main ,用于标记程序入口。想要自己写包,在项目文件夹下创建包名文件夹,包名文件夹下创建同名的 go 文件,开头写 package [包名]。公开的函数以大写字母开头,内部函数以小写字母开头。
最终导入包时,按照
1 2 3 4 import ( “[项目名]/[包名]” ... )
的格式导入。
运行:go run .
编译:go build -o xxx.exe(Win 平台下)
语法
Go 命名规范:
函数、变量、结构体(包括这其中的变量、函数 / 方法)等
首字母大写时外部包可访问。小写时无法访问到。
接收器命名:
结构体方法的接收器用结构体首字母小写,简短统一。
struct
普通结构体:
1 2 3 4 5 type Addr struct { Province string City string District string }
嵌套结构体:
1 2 3 4 5 6 type UserInfo struct { Name string Age int Sex string Addr Addr }
Go 允许自定义变量类型和内部变量名同名。上面的也可以写成匿名形式,不过可读性比较差:
1 2 3 4 5 6 7 8 type UserInfo struct { Name string Age int Sex string Addr }
实例化对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var user = UserInfo{ Name: "Cheyanne" , Age: 18 , Sex: "female" , Addr: Addr{ Province: "Henan" , City: "Zhengzhou" , District: "Erqiqu" , }, } u := User{ Name: "Alice" , Age: 28 , Email: "" , Password: "secret_password" , Roles: []string {"admin" , "editor" }, }
结构体函数:
1 2 3 4 5 6 7 func (u *UserInfo) SetAge(newAge int ) { u.Age = newAge }func (u *UserInfo) SetProvince(newProvince string ) { u.Addr.Province = newProvince }
前面的括号指明这是一个结构体函数,传入指针可以直接修改信息。
序列化:
1 2 3 4 5 6 type User struct { Name string `json:"name"` Age int `json:"age"` Password string `json:"-"` Tags []string `json:"tags,omitempty"` }
结构体变量后面反引号包裹的内容相当于机器注释。在这里,json:"name" 表示导入后的键名为 name,而非 Name。json:"-" 表示忽略该项。
底层库在读取这段文本时,会按照 json:"[键名],[备注1],[备注2]" 的顺序读取。即识别到的第一个为键名。omitempty 是 omit it if empty 的意思,即若为空则忽略该项。
对于这个 User 结构体,序列化函数:
1 2 3 4 5 func (u *User) Serialize() string { jsonData, _ := json.Marshal(u) return string (jsonData) }
反序列化:
1 2 3 4 5 func UnSerialize (jsontxt string ) User { var return_user User json.Unmarshal([]byte (jsontxt), &return_user) return return_user }
map
创建一个空 map,指明键值对类型:
1 NewMap := make (map [any]any)
也可以初始化时赋值:
1 2 3 4 5 6 7 8 9 10 NewMap := map [string ]any{ "name" : "XiaoMing" , "age" : 18 , "sex" : "male" , "hobbies" : []string { "cooking" , "play games" , }, }
访问:
1 2 3 val, ok := NewMap["sex" ].(string ) hbs, ok := NewMap["hobbies" ].([]string )
修改 / 添加键值对:
1 NewMap["sex" ] = "female"
删除键值对:
打印值(一定使用 fmt.Print,不要使用内置的 print):
1 fmt.Println(NewMap["hobbies" ])
序列化:
对于需要在内存中操作的场景,使用json.Marshal
1 2 3 4 5 6 newMap := map [string ]string { "name" : "xiaoming" , "sex" : "male" , } raw_data, _ := json.Marshal(newMap) data := string (raw_data)
直接流式输出(控制台 / http):
1 2 3 4 5 6 7 8 9 10 11 12 13 newMap := map [string ]string { "name" : "xiaoming" , "sex" : "male" , } json.NewEncoder(os.Stdout).Encode(newMap) http.HandleFunc("/" , func (w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(newMap) })
slice
创建某类型的切片:
1 2 3 4 s := make ([]string , 0 )var s []string
带有数据的初始化:
1 list := []string {"apple" , "banana" }
增删改查:
1 2 3 4 5 6 7 8 list = append (list, "grape" ) list[0 ] = "orange" first := list[0 ] var delIndex = 1 sli = append (sli[:delIndex], sli[delIndex+1 :]...) fmt.Println(sli)
遍历:
1 2 3 4 5 6 for _, value := range sli { fmt.Println(value) }
func
函数格式:
1 2 3 4 func function (param1 any, param2 any) any { xxx return xxx }
结构体函数:
1 2 3 4 5 6 func (u *Userinfo) function(param1 any, param2 any) any { xxx return xxx }
Go 不允许嵌套函数,但是可以定义匿名函数赋值给变量。
匿名函数:
1 2 3 add := func (a int , b int ) int { return a + b }
闭包:
针对同一函数内,如果定义匿名函数并复制给变量,则匿名函数的作用域是整个函数周期,且能够有效操作内部变量。
1 2 3 4 x := 10 f := func () { x++ }
例如删除切片元素的函数:
1 2 3 4 5 6 func DelSliceElem (delIndex int , slice []any) []any { if delIndex < 0 || delIndex >= len (slice) { return slice } return append (slice[:delIndex], slice[delIndex+1 :]...) }
默认返回:
当在函数签名里额外声明了返回值的变量名(而不仅仅是类型),就可只写 return 语句,此时函数会自动把相应变量带出去。
当函数内部有 defer 语句时,无法直接在 defer 内让原函数显式返回。这时候就需要在签名时声明返回值的变量名,静静等待函数把相应的变量带出。
错误处理 + (defer + recover)
在函数返回值中可以显式返回一个 error 型变量。当能够预知到传参或运行途中可能会出现某些状况导致 panic,可以提前处理边界情况。
1 2 3 4 5 6 7 func safeDivide (a, b int ) (res float64 , err error ) { if b == 0 { err = fmt.Errorf("不能除以0!" ) return 0 , err } return float64 (a) / float64 (b), nil }
字符串可以通过 fmt.Errorf 被复制到 error,进而被回显。
主函数的错误处理:
1 2 3 4 5 6 7 8 9 func main () { res, err := safeDivide(1 , 0 ) if err != nil { fmt.Printf("error: %v\n" , err) } else { fmt.Printf("res: %f\n" , res) } fmt.Println("done" ) }
使用 defer+recover 结构可以让程序在出错时不至于直接退出,能够继续接下来的业务逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func safeDivide (a, b int ) (res int , err error ) { defer func () { if r := recover (); r != nil { err = fmt.Errorf("自定义错误类型(%v)" , r) } }() return a / b, nil }func main () { res, err := safeDivide(1 , 0 ) if err != nil { fmt.Printf("%v\n" , err) } else { fmt.Printf("%d\n" , res) } fmt.Println("done" ) }
在这段程序中,使用了命名返回值。如果 b 不为 0,则 a/b 没有问题,正常返回 res, nil。当 b 为 0 时,程序计算到 a/b 时会崩溃,进而抛出 panic。由于写了 defer 结构,这个 panic 会被 recover 捕获到(并消解 panic 的强制退出性质),并被赋值给 r。此时走 defer 函数的 if 分支:err 被格式化的字符串赋值,最终程序把没有赋值的 res(默认为 0)和自定义的 err 带出。
当 defer 结构是匿名函数时,需要在函数定义的末尾写一对小括号表示立即调用,否则只会定义这个函数,defer 实际为空。
Functional Options
FO 解决了函数传参时需要按固定顺序、形参没有默认值的难点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package makeburgertype burger struct { bun bool beef_patty bool cheese bool sauce bool pickles bool }type option func (*burger) func WithCheese (cheese bool ) option { return func (b *burger) { b.cheese = cheese } }func WithSauce (sauce bool ) option { return func (b *burger) { b.sauce = sauce } }func WithPickles (pickles bool ) option { return func (b *burger) { b.pickles = pickles } }func NewBurger (opts ...option) *burger { b := &burger{ bun: true , beef_patty: true , cheese: false , sauce: false , pickles: false , } for _, opt := range opts { opt(b) } return b }
type option func(*burger) 一句定义了一个匿名函数 option。传入结构体指针可以直接修改作用域外的结构体变量的值,也无需返回值。更重要的是创建 option 的意义。在通常情况下,只有实例化一个结构体之后,才能对这个对象进行修改。这里把函数模板提前定义出来,是为了满足后续 With 函数对此模板函数(option)的修改需求(初始化一个注射器)
后面的自定义函数 With... 接收布尔值,返回 option。布尔值是用户想要对实例化对象执行的操作。假如用户对某项的需求为 true,则直接扔进去,得到一个自定义的 option(这个 option 被调用后 能够使对象的对应值改变,相当于给注射器灌药)
最后的实例化函数接收多个 options(花式注射器),返回一个被修改过的结构体实例的指针。其中 (opts ...options) 把传入的所有 option 打包为切片,便于访问。在实例化函数中,先创建一个 默认参数的 结构体,并将其指针赋值给内部变量。接着遍历切片,依次把每个 option 作用于对象(因为原 option 模板规定传入指针所以可以修改,相当于把所有注射器扎到对象上),最终返回这个对象(所有更改已经正式生效)的指针。
os (file IO)
系统级的文件 IO 操作分为两大类:
对某一具体文件进行修改(按文件大小分为大文件和小文件,二者要用不同的方法处理);追加文件内容
管理目录结构:
创建目录(文件、单层文件夹、嵌套创建多层文件夹)
删除(文件、文件夹)
遍历目录(读取文件夹及其文件、递归读取、在多文件中查找内容)
改名
移动
获取元信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 package singlefileimport ( "io" "os" "path/filepath" )var MainPath = filepath.Join("." , "test.txt" )func TestPath (ph string ) error { _, err := os.Stat(ph) return err }func LoadContent (ph string ) (string , error ) { file, err := os.OpenFile(ph, os.O_RDONLY, 0644 ) if err != nil { return "" , err } defer file.Close() raw_data, err := io.ReadAll(file) if err != nil { return "" , err } return string (raw_data), nil }func LoadContent_simple (ph string ) (string , error ) { raw_data, err := os.ReadFile(ph) if err != nil { return "" , err } return string (raw_data), nil }func RewriteFile (ph, content string ) error { file, err := os.OpenFile(ph, os.O_WRONLY|os.O_TRUNC, 0644 ) if err != nil { return err } defer file.Close() _, err = file.WriteString(content) if err != nil { return err } return nil }func RewriteFile_simple (ph, content string ) error { return os.WriteFile(ph, []byte (content), 0644 ) }func ReadLine (ph string , targetLineNum int ) (string , error ) { file, err := os.OpenFile(ph, os.O_RDONLY, 0644 ) if err != nil { return "" , err } defer file.Close() scanner := bufio.NewScanner(file) lineNum := 1 for scanner.Scan() { if lineNum == targetLineNum { return scanner.Text(), nil } lineNum++ } if err := scanner.Err(); err != nil { return "" , err } return "" , fmt.Errorf("line %d not found" , targetLineNum) }func AppendContent (ph, content string , isNewline bool ) error { file, err := os.OpenFile(ph, os.O_WRONLY|os.O_APPEND, 0644 ) if err != nil { return err } defer file.Close() if isNewline { _, err := file.WriteString("\n" + content) return err } else { _, err := file.WriteString(content) return err } }
Gin框架