📖 Go CheatSheet 是对于 Go 学习/实践过程中的语法与技巧进行盘点,其属于 Awesome CheatSheet 系列,致力于提升学习速度与研发效能,即可以将其当做速查手册,也可以作为轻量级的入门学习资料。本文参考了许多优秀的文章与代码示范,统一声明在了 Go Links;如果希望深入了解某方面的内容,可以继续阅读 Go 开发:语法基础与工程实践,或者前往 coding-snippets/go 查看使用 Go 解决常见的数据结构与算法、设计模式、业务功能方面的代码实现。
Go (a.k.a. Golang) is a statically-typed programming language first developed at Google. It is derived from C with additional features such as garbage collection, type safety, dynamic-typing capabilities, additional built-in types, and a large standard library.
可以前往这里下载 Go SDK 安装包,或者使用 brew 等包管理器安装。现在我们也推荐使用 gvm 这样的专用版本管理工具,来方便地更新与切换版本。
环境配置完毕后,可以使用 go get 获取依赖,go run 运行程序,go build 来编译项目生成与包名(文件夹名)一致的可执行文件。Golang 1.8 之后支持 dep 依赖管理工具,对于空的项目使用 dep init 初始化依赖配置,其会生成 Gopkg.toml Gopkg.lock vendor/
这三个文件(夹)。
在这里,我们首先对于 Go 的常用语法有所了解,请参考当前目录下的 go-snippets。
Golang 的包管理是一直是为人诟病之处,从 Golang 1.5 引入的 vendor 机制,到准官方工具 dep, 再到 Go 1.11 引入了 Go Modules,其包管理与模块机制一直也在不断地完善。
早期 Go 命令依赖于 GOPATH 环境变量进行代码组织,多项目情况下也可以使用 ln 进行目录映射以方便进行项目管;GOPATH 允许设置多个目录,每个目录都会包含三个子目录:src 用于存放源代码,pkg 用于存放编译后生成的文件,bin 用于存放编译后生成的可执行文件。
Go 并没有相对路径引入,而是以文件夹为单位定义模块;并且规定每个源文件的首部需要进行包声明,可执行文件默认放在 main 包中。如上文所述,GOPATH 环境变量为我们指明了本地工作空间的地址,而每个导入路径都会指明唯一的包。标准库中的包往往是 fmt
, net/http
这样的短路径;而对于自定义的包,则必须指明根路径以避免潜在的冲突。如果我们使用了 Github 这样的源码仓库,则需要使用 github.com/user
作为根路径。
// goworkdir/src/project1/utils/auth.gopackage utilsfunc Test1() string {return "Test1"}// goworkdir/src/project1/controllers/login.gopackage controllersimport "project1/utils"func Test2() string {return utils.Test1()}// goworkdir/src/project1/main.gopackage mainimport ("fmt""project1/controllers")func main() {fmt.Println(controllers.Test2())}
Go 中还允许针对不同的编译目标平台引入不同的文件:
mypkg_linux.go // only builds on linux systemsmypkg_windows_amd64.go // only builds on windows 64bit platforms
我们可以使用 dep ensure -add github.com/pkg/errors
添加依赖,运行之后,其会在 toml 文件中添加如下锁:
[[constraint]]name = "github.com/pkg/errors"version = "0.8.0"
简单的 Go 中 Hello World 代码如下:
package mainimport "fmt"func main() {fmt.Println("hello world")}
Go Modules 将包名与路径分离,可以存放于文件系统上的任何为止,而不用管 GOPATH 路径到底是什么,我们可以创建任意的项目目录:
$ mkdir -p /tmp/scratchpad/hello$ cd /tmp/scratchpad/hello
然后初始化所需要的模块:
$ go mod init github.com/you/hellogo: creating new go.mod: module github.com/you/hello
然后照常编写 Go 模块代码:
// hello.gopackage mainimport ("fmt""rsc.io/quote")func main() {fmt.Println(quote.Hello())}
在执行 go build
命令之后,即可以在 go.mod
文件中查看模块定义与显式的声明:
$ cat go.modmodule github.com/you/hellorequire rsc.io/quote v1.5.2
模块是包含了 Go 源文件的目录树,并在根目录中添加了名为 go.mod 的文件,go.mod 包含模块导入名称,声明了要求的依赖项,排除的依赖项和替换的依赖项。
module my/thingrequire (one/thing v1.3.2other/thing v2.5.0 // indirect...)exclude (bad/thing v0.7.3)replace (src/thing 1.0.2 => dst/thing v1.1.0)
需要注意的是,该文件中声明的依赖,并不会在模块的源代码中使用 import 自动导入,还是需要我们人工添加 import 语句来导入的。模块可以包含其他模块,在这种情况下,它们的内容将从父模块中排除。除了 go.mod 文件外,跟目录下还可以存在一个名为 go.sum 的文件,用于保存所有的依赖项的哈希摘要校验之,用于验证缓存的依赖项是否满足模块要求。
模块依赖项会被下载并存储到 GOPATH/src/mod
目录中,直接后果就是废除了模块的组织名称。假设我们正在开发的项目依赖于 github.com/me/lib 且版本号 1.0.0 的模块,对于这种情况,我们会发现在 GOPATH/src/mod 中文件结构如下
Go 的模块版本号必须以 v 开头,在发布版本时可以通过 Tag 方式来指定不同的版本。我们可以使用 go mod tidy
来移除未被使用的依赖,使用 go mod vendor
可以生成独立的 vendor 目录。
各个包中默认首字母大写的函数作为其他包可见的导出函数,而小写函数则默认外部不可见的私有函数。Go 允许在文件中包含初始化函数,默认使用 _
引入的包仅调用初始化函数:
import (mongo "mywebapp/libs/mongodb/db" // 对引入的模块重命名_ "mywebapp/libs/mysql/db" // 使用空白下划线表示仅调用其初始化函数)
初始化函数会于属性初始化之后被调用:
// sandbox.gopackage mainimport "fmt"var _ int64 = s()func init() {fmt.Println("init in sandbox.go")}func s() int64 {fmt.Println("calling s() in sandbox.go")return 1}func main() {fmt.Println("main")}// a.gopackage mainimport "fmt"var _ int64 = a()func init() {fmt.Println("init in a.go")}func a() int64 {fmt.Println("calling a() in a.go")return 2}/* 输出结果calling a() in a.gocalling s() in sandbox.goinit in a.goinit in sandbox.gomain*/
而单个文件中也可以定义多个初始化函数:
package mainimport "fmt"func init() {fmt.Println("init 1")}func init() {fmt.Println("init 2")}func main() {fmt.Println("main")}
作为强类型静态语言,Go 允许我们在变量之后标识数据类型,也为我们提供了自动类型推导的功能。不过需要注意的是,在函数体外 Go 仅允许使用声明式语句。
// 声明三个变量,皆为 bool 类型var c, python, java bool// 声明不同类型的变量,并且赋值var i bool, j int = true, 2// 复杂变量声明var (ToBe bool = falseMaxInt uint64 = 1<<64 - 1z complex128 = cmplx.Sqrt(-5 + 12i))// 短声明变量c, python, java := true, false, "no!"// 声明常量const constant = "This is a constant"
在 Go 中,如果我们需要比较两个复杂对象的相似性,可以使用 reflect.DeepEqual 方法:
m1 := map[string]int{"a":1,"b":2,}m2 := map[string]int{"a":1,"b":2,}fmt.Println(reflect.DeepEqual(m1, m2))
Go 提供了增强型的 if 语句进行条件判断:
// 基础形式if x > 0 {return x} else {return -x}// 条件判断之前添加自定义语句if a := b + c; a < 42 {return a} else {return a - 42}// 常用的类型判断var val interface{}val = "foo"if str, ok := val.(string); ok {fmt.Println(str)}
Go 也支持使用 Switch 语句:
// 基础格式switch operatingSystem {case "darwin":fmt.Println("Mac OS Hipster")// 默认 break,不需要显式声明case "linux":fmt.Println("Linux Geek")default:// Windows, BSD, ...fmt.Println("Other")}// 类似于 if,可以在条件之前添加自定义语句switch os := runtime.GOOS; os {case "darwin": ...}// 使用 switch 语句进行类型判断:switch v := anything.(type) {case string:fmt.Println(v)case int32, int64:...default:fmt.Println("unknown")}
Switch 中也支持进行比较:
number := 42switch {case number < 42:fmt.Println("Smaller")case number == 42:fmt.Println("Equal")case number > 42:fmt.Println("Greater")}
或者进行多条件匹配:
var char byte = '?'switch char {case ' ', '?', '&', '=', '#', '+', '%':fmt.Println("Should escape")}
Switch 中还支持对于类型的条件判断:
type T struct {name string}t := T{}switch (interface{})(t).(type) {case T:fmt.Print("1")}
Go 支持使用 for 语句进行循环,不存在 while 或者 until:
for i := 1; i < 10; i++ {}// while - loopfor ; i < 10; {}// 单条件情况下可以忽略分号for i < 10 {}// ~ while (true)for {}
我们也可以使用 range 函数,对于 Arrays 与 Slices 进行遍历:
// loop over an array/a slicefor i, e := range a {// i 表示下标,e 表示元素}// 仅需要元素for _, e := range a {// e is the element}// 或者仅需要下标for i := range a {}// 定时执行for range time.Tick(time.Second) {// do it once a sec}
因为 Go 没有逗号运算符,并且 ++ 与 -- 是语句而非表达式,因此当我们想要去同时处理多个变量时,需要使用并行赋值:
// Reverse afor i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {a[i], a[j] = a[j], a[i]}
// 简单函数定义func functionName() {}// 含参函数定义func functionName(param1 string, param2 int) {}// 多个相同类型参数的函数定义func functionName(param1, param2 int) {}// 函数表达式定义add := func(a, b int) int {return a + b}
Go 支持函数的最后一个参数使用 ... 设置为不定参数,即可以传入一个或多个参数值:
func adder(args ...int) int {total := 0for _, v := range args { // Iterates over the arguments whatever the number.total += v}return total}adder(1, 2, 3) // 6adder(9, 9) // 18nums := []int{10, 20, 30}adder(nums...) // 60// 传入任意类型的不定参数
我们也可以使用 Function Stub 作为函数参数传入,以实现回调函数的功能:
func Filter(s []int, fn func(int) bool) []int {var p []int // == nilfor _, v := range s {if fn(v) {p = append(p, v)}}return p}
Go 支持多个返回值:
// 返回单个值func functionName() int {return 42}// 返回多个值func returnMulti() (int, string) {return 42, "foobar"}var x, str = returnMulti()// 命名返回多个值func returnMulti2() (n int, s string) {n = 42s = "foobar"// n and s will be returnedreturn}var x, str = returnMulti2()
虽然 Go 不是函数式语言,但是也可以用其实现柯里函数(Currying Function):
func add(x, y int) int {return x+ y}func adder(x int) (func(int) int) {return func(y int) int {return add(x, y)}}func main() {add3 := adder(3)fmt.Println(add3(4)) // 7}
Go 同样支持词法作用域与变量保留,因此我们可以使用来访问函数定义处外层的变量:
func scope() func() int{outer_var := 2foo := func() int { return outer_var}return foo}
闭包中并不能够直接修改外层变量,而是会自动重定义新的变量值:
func outer() (func() int, int) {outer_var := 2inner := func() int {outer_var += 99return outer_var // => 101 (but outer_var is a newly redefined}return inner, outer_var // => 101, 2 (outer_var is still 2, not mutated by inner!)}
Go 中提供了 defer 关键字,允许将某个语句的执行推迟到函数返回语句之前:
func read(...) (...) {f, err := os.Open(file)...defer f.Close()...return .. // f will be closed
多个 defer 出现的时候,多个 defer 之间按照 LIFO(后进先出)的顺序执行:
package mainimport "fmt"func main(){defer func(){fmt.Println("1")}()defer func(){fmt.Println("2")}()defer func(){fmt.Println("3")}()}
Go 语言中并不存在 try-catch 等异常处理的关键字,对于那些可能返回异常的函数,只需要在函数返回值中添加额外的 Error 类型的返回值:
type error interface {Error() string}
某个可能返回异常的函数调用方式如下:
import ("fmt""errors")func main() {result, err:= Divide(2,0)if err != nil {fmt.Println(err)}else {fmt.Println(result)}}func Divide(value1 int,value2 int)(int, error) {if(value2 == 0){return 0, errors.New("value2 mustn't be zero")}return value1/value2 , nil}
Go 还为我们提供了 panic 函数,所谓 panic,即是未获得预期结果,常用于抛出异常结果。譬如当我们获得了某个函数返回的异常,却不知道如何处理或者不需要处理时,可以直接通过 panic 函数中断当前运行,打印出错误信息、Goroutine 追踪信息,并且返回非零的状态码:
_, err := os.Create("/tmp/file")if err != nil {panic(err)}
当函数 F 调用 panic 时,其执行流程会被终止,而所有的 deferred 函数会被正常的依次执行,然后 F 会返回到调用者。F 此时的行为逻辑与直接调用 panic 函数并无差异,进程会在函数所在 Goroutine 的所有函数执行完毕之后,恢复异常的调用栈。Recover 函数则可以手动地恢复 Panic Goroutine 的执行,正常的执行中 recover 函数会返回 nil;而如果当前 Goroutine 被 panic,recover 函数会捕获传递给 panic 的值,并且恢复正常的执行流。
package mainimport "fmt"func main(){defer func(){if r := recover();r != nil{fmt.Println(r)}}()panic([]int{12312})}
Go 中可以使用 interface{}
来表示任意类型。
Go 中的 type 关键字能够对某个类型进行重命名:
// IntSlice 并不等价于 []int,但是可以利用类型转换进行转换type IntSlice []inta := IntSlice{1, 2}
可以使用 T(v) 或者 obj.(T) 进行类型转换,obj.(T) 仅针对 interface{} 类型起作用:
t := obj.(T) // if obj is not T, errort, ok := obj.(T) // if obj is not T, ok = false// 类型转换与判断str, ok := val.(string);
interface {} // ~ java Objectbool // true/falsestringint8 int16 int32 int64int // =int32 on 32-bit, =int64 if 64-bit OSuint8 uint16 uint32 uint64 uintptruintbyte // alias for uint8rune // alias for int32, represents a Unicode code pointfloat32 float64
Go 中支持以反引号定义多行字符串:
// 多行字符串声明hellomsg := `"Hello" in Chinese is 你好 ('Ni Hao')"Hello" in Hindi is नमस्ते ('Namaste')`
fmt 的输出模块为我们提供了方便的格式化字符串输出功能:
fmt.Println("Hello, 你好, नमस्ते, Привет, ᎣᏏᏲ") // basic print, plus newlinep := struct { X, Y int }{ 17, 2 }fmt.Println( "My point:", p, "x coord=", p.X ) // print structs, ints, etcs := fmt.Sprintln( "My point:", p, "x coord=", p.X ) // print to string variablefmt.Printf("%d hex:%x bin:%b fp:%f sci:%e",17,17,17,17.0,17.0) // c-ish format// Sprintf 能够仅进行字符串格式化,返回格式化后的字符串,而非输出到控制台s2 := fmt.Sprintf( "%d %f", 17, 17.0 )// 将其输出到错误流中fmt.Fprintf(os.Stderr, "an %s\n", "error")
Array 与 Slice 都可以用来表示序列数据,二者也有着一定的关联。
其中 Array 用于表示固定长度的,相同类型的序列对象,可以使用如下形式创建:
[N]Type[N]Type{value1, value2, ..., valueN}// 由编译器自动计算数目[...]Type{value1, value2, ..., valueN}
其具体使用方式为:
// 数组声明var a [10]int// 赋值a[3] = 42// 读取i := a[3]// 声明与初始化var a = [2]int{1, 2}a := [2]int{1, 2}// 加 ... 会限制数组长度a := [...]int{1, 2}// 声明二维数组array := [2][3]int{{1, 2, 3}, {4, 5, 6}}
Go 内置了 len 与 cap 函数,用于获取数组的尺寸与容量:
var arr = [3]int{1, 2, 3}arr := [...]int{1, 2, 3}len(arr) // 3cap(arr) // 3
不同于 C/C++ 中的指针(Pointer)或者 Java 中的对象引用(Object Reference),Go 中的 Array 只是值(Value)。这也就意味着,当进行数组拷贝,或者函数调用中的参数传值时,会复制所有的元素副本,而非仅仅传递指针或者引用。显而易见,这种复制的代价会较为昂贵。
Slice 为我们提供了更为灵活且轻量级地序列类型操作,可以使用如下方式创建 Slice:
// 使用内置函数创建make([]Type, length, capacity)make([]Type, length)// 声明为不定长度数组[]Type{}[]Type{value1, value2, ..., valueN}// 对现有数组进行切片转换array[:]array[:2]array[2:]array[2:3]// 不定类型切片声明a := []interface{}{2, 1, []interface{}{3, []interface{}{4, 5}, 6}, 7, []interface{}{8}}// 二维不定类型切片b := [][]interface{}{[]interface{}{1, 2},[]interface{}{3, 4},}
不同于 Array, Slice 可以看做更为灵活的引用类型(Reference Type),它并不真实地存放数组值,而是包含数组指针(ptr),len,cap 三个属性的结构体。换言之,Slice 可以看做对于数组中某个段的描述,包含了指向数组的指针,段长度,以及段的最大潜在长度,其结构如下图所示:
// 创建 len 为 5,cap 为 5 的 Slices := make([]byte, 5)// 对 Slice 进行二次切片,此时 len 为 2,cap 为 3s = s[2:4]// 恢复 Slice 的长度s = s[:cap(s)]
需要注意的是,切片操作并不会真实地复制 Slice 中值,只是会创建新的指向原数组的指针,这就保证了切片操作和操作数组下标有着相同的高效率。不过如果我们修改 Slice 中的值,那么其会真实修改底层数组中的值,也就会体现到原有的数组中:
d := []byte{'r', 'o', 'a', 'd'}e := d[2:]// e == []byte{'a', 'd'}e[1] = 'm'// e == []byte{'a', 'm'}// d == []byte{'r', 'o', 'a', 'm'}
Go 提供了内置的 append 函数,来动态为 Slice 添加数据,该函数会返回新的切片对象,包含了原始的 Slice 中值以及新增的值。如果原有的 Slice 的容量不足以存放新增的序列,那么会自动分配新的内存:
// len=0 cap=0 []var s []int// len=1 cap=2 [0]s = append(s, 0)// len=2 cap=2 [0 1]s = append(s, 1)// len=5 cap=8 [0 1 2 3 4]s = append(s, 2, 3, 4)// 使用 ... 来自动展开数组并进行合并a := []string{"John", "Paul"}b := []string{"George", "Ringo", "Pete"}a = append(a, b...) // equivalent to "append(a, b[0], b[1], b[2])"// a == []string{"John", "Paul", "George", "Ringo", "Pete"}
我们也可以使用内置的 copy 函数,进行 Slice 的复制,该函数支持对于不同长度的 Slice 进行复制,其会自动使用最小的元素数目。同时,copy 函数还能够自动处理使用了相同的底层数组之间的 Slice 复制,以避免额外的空间浪费。
func copy(dst, src []T) int// 申请较大的空间容量t := make([]byte, len(s), (cap(s)+1)*2)copy(t, s)s = t
var m map[string]intm = make(map[string]int)m["key"] = 42// 删除某个键delete(m, "key")// 测试该键对应的值是否存在elem, has_value := m["key"]// map literalvar m = map[string]Vertex{"Bell Labs": {40.68433, -74.39967},"Google": {37.42202, -122.08408},}
Go 语言中并不存在类的概念,只有结构体,结构体可以看做属性的集合,同时可以为其定义方法。
// 声明结构体type Vertex struct {// 结构体的属性,同样遵循大写导出,小写私有的原则X, Y intz bool}// 也可以声明匿名/隐式结构体point := struct {X, Y int}{1, 2}// 声明空指针var v *Vertex = new(Vertex)// 显式声明键var v = Vertex{X: 1, Y: 2}// 声明数组var v = []Vertex{{1,2},{5,2},{5,5}}// 创建结构体实例var v = Vertex{1, 2}// 读取或者设置属性v.X = 4;
方法的声明也非常简洁,只需要在 func 关键字与函数名之间声明结构体指针即可,该结构体会在不同的方法间进行复制:
func (v Vertex) Abs() float64 {return math.Sqrt(v.X*v.X + v.Y*v.Y)}// Call methodv.Abs()
对于那些需要修改当前结构体对象的方法,则需要传入指针:
func (v *Vertex) add(n float64) {v.X += nv.Y += n}
// p 是 Vertex 类型p := Vertex{1, 2}// q 是指向 Vertex 的指针q := &p// r 同样是指向 Vertex 对象的指针r := &Vertex{1, 2}// 指向 Vertex 结构体对象的指针类型为 *Vertexvar s *Vertex = new(Vertex)
当我们在定义结构体时,可以使用指针或者值作为接受者来定义方法用指针作为接收者,那么变量(或者可以称作对象)本身是按引用传递的,在方法内可以修改对象的数据。使用值接收者,以为这是按值传递的,那么对象在方法内是处于只读状态的。并且指针类型时调用方法会复制 receiver, 每调用一次 TestValue,item 就会被复制一次.实际相当于 TestValue(v),TestPointer(&v)。
type VideoItem struct {GroupId int64ItemId int64AggrType int32}func (item *VideoItem) TestPointer(GroupId int64) {fmt.Printf("TestPointer %p %v\n", item, item)item.GroupId = GroupId}func (item VideoItem) TestValue(GroupId int64) {fmt.Printf("TestPointer %p %v\n", &item, &item)item.GroupId = GroupId}func main() {v := VideoItem{}fmt.Printf("TestPointer %p %v\n", &v, &v)// 值不变v.TestValue(1)// v 的 GroupId 被修改为 2v.TestPointer(2)// 值不变(&v).TestValue(3)// v 的 GroupId 被修改为 4(&v).TestPointer(4)fmt.Println(v)}/*TestPointer 0xc420018300 &{0 0 0}TestPointer 0xc420018360 &{0 0 0}TestPointer 0xc420018300 &{0 0 0}TestPointer 0xc4200183c0 &{0 0 0}TestPointer 0xc420018300 &{0 0 0}*/
传递普通变量传递值拷贝,不能修改原始值,如果是大对象则内存效率不高。 传递变量的指针,指针为固定大小,效率更高,可以就地修改对象的原始值。 在方法集的使用上,无论接收者是变量还是指针,都能直接正确调用,无需特殊处理,能正确调用所有绑定在该值或指针上的方法,Go 会自动帮我们处理引用与解引用。
Go 允许我们通过定义接口的方式来实现多态性:
// 接口声明type Awesomizer interface {Awesomize() string}// 结构体并不需要显式实现接口type Foo struct {}// 而是通过实现所有接口规定的方法的方式,来实现接口func (foo Foo) Awesomize() string {return "Awesome!"}
type Shape interface {area() float64}func getArea(shape Shape) float64 {return shape.area()}type Circle struct {x,y,radius float64}type Rectangle struct {width, height float64}func(circle Circle) area() float64 {return math.Pi * circle.radius * circle.radius}func(rect Rectangle) area() float64 {return rect.width * rect.height}func main() {circle := Circle{x:0,y:0,radius:5}rectangle := Rectangle {width:10, height:5}fmt.Printf("Circle area: %f\n",getArea(circle))fmt.Printf("Rectangle area: %f\n",getArea(rectangle))}//Circle area: 78.539816//Rectangle area: 50.000000
惯用的思路是先定义接口,再定义实现,最后定义使用的方法:
package animalstype Animal interface {Speaks() string}// implementation of Animaltype Dog struct{}func (a Dog) Speaks() string { return "woof" }/** 在需要的地方直接引用 **/package circusimport "animals"func Perform(a animal.Animal) { return a.Speaks() }
Go 也为我们提供了另一种接口的实现方案,我们可以不在具体的实现处定义接口,而是在需要用到该接口的地方,该模式为:
func funcName(a INTERFACETYPE) CONCRETETYPE
定义接口:
package animalstype Dog struct{}func (a Dog) Speaks() string { return "woof" }/** 在需要使用实现的地方定义接口 **/package circustype Speaker interface {Speaks() string}func Perform(a Speaker) { return a.Speaks() }
Go 语言中并没有子类继承这样的概念,而是通过嵌入(Embedding)的方式来实现类或者接口的组合。
// ReadWriter 的实现需要同时满足 Reader 与 Writertype ReadWriter interface {ReaderWriter}// Server 暴露了所有 Logger 结构体的方法type Server struct {Host stringPort int*log.Logger}// 初始化方式并未受影响server := &Server{"localhost", 80, log.New(...)}// 却可以直接调用内嵌结构体的方法,等价于 server.Logger.Log(...)server.Log(...)// 内嵌结构体的名词即是类型名var logger *log.Logger = server.Logger
Go 中并没有泛型。
package mainimport ("fmt""net/http")// define a type for the responsetype Hello struct{}// let that type implement the ServeHTTP method (defined in interface http.Handler)func (h Hello) ServeHTTP(w http.ResponseWriter, r *http.Request) {fmt.Fprint(w, "Hello!")}func main() {var h Hellohttp.ListenAndServe("localhost:4000", h)}// Here's the method signature of http.ServeHTTP:// type Handler interface {// ServeHTTP(w http.ResponseWriter, r *http.Request)// }
利用 Beego 官方推荐的 bee 命令行工具,我们可以快速创建 Beego 项目,其目录组织方式如下:
quickstart├── conf│ └── app.conf├── controllers│ └── default.go├── main.go├── models├── routers│ └── router.go├── static│ ├── css│ ├── img│ └── js├── tests│ └── default_test.go└── views└── index.tpl
在 main.go 文件中,我们可以启动 Beego 实例,并且调用路由的初始化配置文件:
package mainimport (_ "quickstart/routers""github.com/astaxie/beego")func main() {beego.Run()}
而在路由的初始化函数中,我们会声明各个路由与控制器之间的映射关系:
package routersimport ("quickstart/controllers""github.com/astaxie/beego")func init() {beego.Router("/", &controllers.MainController{})}
也可以手动指定 Beego 项目中的静态资源映射:
beego.SetStaticPath("/down1", "download1")beego.SetStaticPath("/down2", "download2")
在具体的控制器中,可以设置返回数据,或者关联的模板名:
package controllersimport ("github.com/astaxie/beego")type MainController struct {beego.Controller}func (this *MainController) Get() {this.Data["Website"] = "beego.me"this.Data["Email"] = "[email protected]"this.TplNames = "index.tpl" // version 1.6 use this.TplName = "index.tpl"}
import ("io/ioutil")...datFile1, errFile1 := ioutil.ReadFile("file1")if errFile1 != nil {panic(errFile1)}...
VSCode 可以为函数自动生成基础测试用例,并且提供了方便的用例执行与调试的功能。
/** 交换函数 */func swap(x *int, y *int) {x, y = y, x}/** 自动生成的测试函数 */func Test_swap(t *testing.T) {type args struct {x *inty *int}tests := []struct {name stringargs args}{// TODO: Add test cases.}for _, tt := range tests {t.Run(tt.name, func(t *testing.T) {swap(tt.args.x, tt.args.y)})}}