Go CheatSheet 是对于 Go 学习 / 实践过程中的语法与技巧进行盘点, 其属于 Awesome CheatSheet 系列, 致力于提升学习速度与研发效能, 即可以将其当做速查手册, 也可以作为轻量级的入门学习资料. 本文参考了许多优秀的文章与代码示范, 统一声明在了 Go Links; 如果希望深入了解某方面的内容, 可以继续阅读 Go 开发: 语法基础与工程实践, 或者前往 coding-snippets/go 查看使用 Go 解决常见的数据结构与算法, 设计模式, 业务功能方面的代码实现.
环境配置与语法基础
可以前往这里下载 Go SDK 安装包, 或者使用 brew 等包管理器安装. go 命令依赖于 $GOPATH 环境变量进行代码组织, 多项目情况下也可以使用 ln 进行目录映射以方便进行项目管理. GOPATH 允许设置多个目录, 每个目录都会包含三个子目录: src 用于存放源代码, pkg 用于存放编译后生成的文件, bin 用于存放编译后生成的可执行文件.
环境配置完毕后, 可以使用 go get 获取依赖, go run 运行程序, go build 来编译项目生成与包名 (文件夹名) 一致的可执行文件. Golang 1.8 之后支持 dep 依赖管理工具, 对于空的项目使用 dep init 初始化依赖配置, 其会生成 Gopkg.toml Gopkg.lock vendor / 这三个文件(夹).
我们可以使用 dep ensure -add github.com/pkg/errors 添加依赖, 运行之后, 其会在 toml 文件中添加如下锁:
- [[constraint]]
- name = "github.com/pkg/errors"
- version = "0.8.0"
简单的 Go 中 Hello World 代码如下:
- package main
- import "fmt"
- func main() {
- fmt.Println("hello world")
- }
也可以使用 Beego 实现简单的 HTTP 服务器:
- package main
- import "github.com/astaxie/beego"
- func main() {
- beego.Run()
- }
Go 并没有相对路径引入, 而是以文件夹为单位定义模块, 譬如我们新建名为 math 的文件夹, 然后使用 package math 来声明该文件中函数所属的模块.
- import (
- mongo "mywebapp/libs/mongodb/db" // 对引入的模块重命名
- _ "mywebapp/libs/mysql/db" // 使用空白下划线表示仅调用其初始化函数
- )
外部引用该模块是需要使用工作区间或者 vendor 相对目录, 其目录索引情况如下:
- cannot find package "sub/math" in any of:
- ${PROJECTROOT}/vendor/sub/math (vendor tree)
- /usr/local/Cellar/go/1.10/libexec/src/sub/math (from $GOROOT)
- ${GOPATH}/src/sub/math (from $GOPATH)
Go 规定每个源文件的首部需要进行包声明, 可执行文件默认放在 main 包中; 而各个包中默认首字母大写的函数作为其他包可见的导出函数, 而小写函数则默认外部不可见的私有函数.
表达式与控制流
变量声明与赋值
作为强类型静态语言, Go 允许我们在变量之后标识数据类型, 也为我们提供了自动类型推导的功能.
- // 声明三个变量, 皆为 bool 类型
- var c, python, java bool
- // 声明不同类型的变量, 并且赋值
- var i bool, j int = true,
- 2
- // 复杂变量声明
- var ( ToBe bool = false MaxInt uint64 = 1 <<64 - 1 z 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 := 42
- switch {
- 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")
- }
循环
Go 支持使用 for 语句进行循环, 不存在 while 或者 until:
- for i := 1; i < 10; i++ {
- }
- // while - loop
- for ; i < 10; {
- }
- // 单条件情况下可以忽略分号
- for i < 10 {
- }
- // ~ while (true)
- for {
- }
我们也可以使用 range 函数, 对于 Arrays 与 Slices 进行遍历:
- // loop over an array/a slice
- for 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
- }
Function: 函数
定义, 参数与返回值
- // 简单函数定义
- 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 := 0
- for _, v := range args { // Iterates over the arguments whatever the number.
- total += v
- }
- return total
- }
- adder(1, 2, 3) // 6
- adder(9, 9) // 18
- nums := []int{10, 20, 30}
- adder(nums...) // 60
我们也可以使用 Function Stub 作为函数参数传入, 以实现回调函数的功能:
- func Filter(s []int, fn func(int) bool) []int {
- var p []int // == nil
- for _, v := range s {
- if fn(v) {
- p = append(p, v)
- }
- }
- return p
- }
虽然 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 functionName() int {
- return 42
- }
- // 返回多个值
- func returnMulti() (int, string) {
- return 42, "foobar"
- }
- var x, str = returnMulti()
- // 命名返回多个值
- func returnMulti2() (n int, s string) {
- n = 42
- s = "foobar"
- // n and s will be returned
- return
- }
- var x, str = returnMulti2()
闭包: Closure
Go 同样支持词法作用域与变量保留, 因此我们可以使用闭包来访问函数定义处外层的变量:
- func scope() func() int{
- outer_var := 2
- foo := func() int { return outer_var}
- return foo
- }
闭包中并不能够直接修改外层变量, 而是会自动重定义新的变量值:
- func outer() (func() int, int) {
- outer_var := 2
- inner := func() int {
- outer_var += 99
- return 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
异常处理
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)
- }
数据类型与结构
类型绑定与初始化
Go 中的 type 关键字能够对某个类型进行重命名:
- // IntSlice 并不等价于 []int, 但是可以利用类型转换进行转换
- type IntSlice []int
- a := IntSlice{1, 2}
可以使用 T(v) 或者 obj.(T) 进行类型转换, obj.(T) 仅针对 interface{} 类型起作用:
- t := obj.(T) // if obj is not T, error
- t, ok := obj.(T) // if obj is not T, ok = false
- // 类型转换与判断
- str, ok := val.(string);
基本数据类型
- interface {} // ~ java Object
- bool // true/false
- string
- int8 int16 int32 int64
- int // =int32 on 32-bit, =int64 if 64-bit OS
- uint8 uint16 uint32 uint64 uintptr
- uint
- byte // alias for uint8
- rune // alias for int32, represents a Unicode code point
- float32 float64
字符串
- // 多行字符串声明
- // 多行字符串声明
- hellomsg := `
"Hello" in Chinese is 你好 ('Ni Hao')
- "Hello" in Hindi is ('Namaste')
- `
格式化字符串:
- fmt.Println("Hello, 你好, , Привет, ") // basic print, plus newline
- p := struct { X, Y int }{ 17, 2 }
- fmt.Println( "My point:", p, "x coord=", p.X ) // print structs, ints, etc
- s := fmt.Sprintln( "My point:", p, "x coord=", p.X ) // print to string variable
- fmt.Printf("%d hex:%x bin:%b fp:%f sci:%e",17,17,17,17.0,17.0) // c-ish format
- s2 := fmt.Sprintf( "%d %f", 17, 17.0 ) // formatted print to string variable
序列类型
Array 与 Slice 都可以用来表示序列数据, 二者也有着一定的关联.
Array
其中 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}
Go 内置了 len 与 cap 函数, 用于获取数组的尺寸与容量:
- var arr = [3]int{1, 2, 3}
- arr := [...]int{1, 2, 3}
- len(arr) // 3
- cap(arr) // 3
不同于 C/C++ 中的指针 (Pointer) 或者 Java 中的对象引用(Object Reference),Go 中的 Array 只是值(Value). 这也就意味着, 当进行数组拷贝, 或者函数调用中的参数传值时, 会复制所有的元素副本, 而非仅仅传递指针或者引用. 显而易见, 这种复制的代价会较为昂贵.
Slice
Slice 为我们提供了更为灵活且轻量级地序列类型操作, 可以使用如下方式创建 Slice:
- // 使用内置函数创建
- make([]Type, length, capacity)
- make([]Type, length)
- // 声明为不定长度数组
- []Type{}
- []Type{value1, value2, ..., valueN}
- // 对现有数组进行切片转换
- array[:]
- array[:2]
- array[2:]
- array[2:3]
不同于 Array,Slice 可以看做更为灵活的引用类型(Reference Type), 它并不真实地存放数组值, 而是包含数组指针(ptr),len,cap 三个属性的结构体. 换言之, Slice 可以看做对于数组中某个段的描述, 包含了指向数组的指针, 段长度, 以及段的最大潜在长度, 其结构如下图所示:
- // 创建 len 为 5,cap 为 5 的 Slice
- s := make([]byte, 5)
- // 对 Slice 进行二次切片, 此时 len 为 2,cap 为 3
- s = 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]int
- m = make(map[string]int)
- m["key"] = 42
- // 删除某个键
- delete(m, "key")
- // 测试该键对应的值是否存在
- elem, has_value := m["key"]
- // map literal
- var m = map[string]Vertex{
- "Bell Labs": {40.68433, -74.39967},
- "Google": {37.42202, -122.08408},
- }
Struct & Interface: 结构体与接口
Struct: 结构体
Go 语言中并不存在类的概念, 只有结构体, 结构体可以看做属性的集合, 同时可以为其定义方法.
- // 声明结构体
- type Vertex struct {
- // 结构体的属性, 同样遵循大写导出, 小写私有的原则
- X, Y int
- z bool
- }
- // 也可以声明隐式结构体
- point := struct {
- X, Y int
- }{1, 2}
- // 创建结构体实例
- var v = Vertex{1, 2}
- // 读取或者设置属性
- v.X = 4;
- // 显示声明键
- var v = Vertex{X: 1, Y: 2}
- // 声明数组
- var v = []Vertex{{1,2},{5,2},{5,5}}
方法的声明也非常简洁, 只需要在 func 关键字与函数名之间声明结构体指针即可, 该结构体会在不同的方法间进行复制:
- func (v Vertex) Abs() float64 {
- return math.Sqrt(v.X*v.X + v.Y*v.Y)
- }
- // Call method
- v.Abs()
对于那些需要修改当前结构体对象的方法, 则需要传入指针:
- func (v *Vertex) add(n float64) {
- v.X += n
- v.Y += n
- }
- var p *Person = new(Person) // pointer of type Person
Pointer: 指针
- // p 是 Vertex 类型
- p := Vertex{1, 2}
- // q 是指向 Vertex 的指针
- q := &p
- // r 同样是指向 Vertex 对象的指针
- r := &Vertex{1, 2}
- // 指向 Vertex 结构体对象的指针类型为 *Vertex
- var s *Vertex = new(Vertex)
Interface: 接口
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 animals
- type Animal interface {
- Speaks() string
- }
- // implementation of Animal
- type Dog struct{}
- func (a Dog) Speaks() string { return "woof" }
- /** 在需要的地方直接引用 **/
- package circus
- import "animals"
- func Perform(a animal.Animal) { return a.Speaks() }
Go 也为我们提供了另一种接口的实现方案, 我们可以不在具体的实现处定义接口, 而是在需要用到该接口的地方, 该模式为:
func funcName(a INTERFACETYPE) CONCRETETYPE
定义接口:
- package animals type Dog struct {}
- func (a Dog) Speaks() string {
- return "woof"
- }
- /** 在需要使用实现的地方定义接口 **/
- package circus type Speaker interface { Speaks() string
- }
- func Perform(a Speaker) {
- return a.Speaks()
- }
- Embedding
Go 语言中并没有子类继承这样的概念, 而是通过嵌入 (Embedding) 的方式来实现类或者接口的组合.
- // ReadWriter 的实现需要同时满足 Reader 与 Writer
- type ReadWriter interface {
- Reader
- Writer
- }
- // Server 暴露了所有 Logger 结构体的方法
- type Server struct {
- Host string
- Port int
- *log.Logger
- }
- // 初始化方式并未受影响
- server := &Server{"localhost", 80, log.New(...)}
- // 却可以直接调用内嵌结构体的方法, 等价于 server.Logger.Log(...)
- server.Log(...)
- // 内嵌结构体的名词即是类型名
- var logger *log.Logger = server.Logger
并发编程
Goroutines
Goroutines 是轻量级的线程, 可以参考并发编程导论一文中的进程, 线程与协程的讨论; Go 为我们提供了非常便捷的 Goroutines 语法:
- // 普通函数
- func doStuff(s string) {
- }
- func main() {
- // 使用命名函数创建 Goroutine
- go doStuff("foobar")
- // 使用匿名内部函数创建 Goroutine
- go func (x int) {
- // function body goes here
- }(42)
- }
- Channels
信道 (Channel) 是带有类型的管道, 可以用于在不同的 Goroutine 之间传递消息, 其基础操作如下:
- // 创建类型为 int 的信道
- ch := make(chan int)
- // 向信道中发送值
- ch <- 42
- // 从信道中获取值
- v := <-ch
- // 读取, 并且判断其是否关闭
- v, ok := <-ch
- // 读取信道, 直至其关闭
- for i := range ch {
- fmt.Println(i)
- }
譬如我们可以在主线程中等待来自 Goroutine 的消息, 并且输出:
- // 创建信道
- messages : = make(chan string)
- // 执行 Goroutine
- go func() { messages < - "ping"
- } ()
- // 阻塞, 并且等待消息
- msg : = < -messages
- // 使用信道进行并发地计算, 并且阻塞等待结果
- c : = make(chan int) go sum(s[: len(s) / 2], c) go sum(s[len(s) / 2 : ], c) x,
- y : = < -c,
- < -c // 从 c 中接收
如上创建的是无缓冲型信道(Non-buffered Channels), 其是阻塞型信道; 当没有值时读取方会持续阻塞, 而写入方则是在无读取时阻塞. 我们可以创建缓冲型信道(Buffered Channel), 其读取方在信道被写满前都不会被阻塞:
- ch := make(chan int, 100)
- // 发送方也可以主动关闭信道
- close(ch)
Channel 同样可以作为函数参数, 并且我们可以显式声明其是用于发送信息还是接收信息, 从而增加程序的类型安全度:
- // ping 函数用于发送信息
- func ping(pings chan<- string, msg string) {
- pings <- msg
- }
- // pong 函数用于从某个信道中接收信息, 然后发送到另一个信道中
- func pong(pings <-chan string, pongs chan<- string) {
- msg := <-pings
- pongs <- msg
- }
- func main() {
- pings := make(chan string, 1)
- pongs := make(chan string, 1)
- ping(pings, "passed message")
- pong(pings, pongs)
- fmt.Println(<-pongs)
- }
同步
同步, 是并发编程中的常见需求, 这里我们可以使用 Channel 的阻塞特性来实现 Goroutine 之间的同步:
- func worker(done chan bool) {
- time.Sleep(time.Second)
- done <- true
- }
- func main() {
- done := make(chan bool, 1)
- go worker(done)
- // 阻塞直到接收到消息
- <-done
- }
Go 还为我们提供了 select 关键字, 用于等待多个信道的执行结果:
- // 创建两个信道
- c1 := make(chan string)
- c2 := make(chan string)
- // 每个信道会以不同时延输出不同值
- go func() {
- time.Sleep(1 * time.Second)
- c1 <- "one"
- }()
- go func() {
- time.Sleep(2 * time.Second)
- c2 <- "two"
- }()
- // 使用 select 来同时等待两个信道的执行结果
- for i := 0; i < 2; i++ {
- select {
- case msg1 := <-c1:
- fmt.Println("received", msg1)
- case msg2 := <-c2:
- fmt.Println("received", msg2)
- }
- }
Web 编程
- HTTP Server
- package main
- import (
- "fmt"
- "net/http"
- )
- // define a type for the response
- type 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 Hello
- http.ListenAndServe("localhost:4000", h)
- }
- // Here's the method signature of http.ServeHTTP:
- // type Handler interface {
- // ServeHTTP(w http.ResponseWriter, r *http.Request)
- // }
- Beego
利用 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 main
- import (
- _ "quickstart/routers"
- "github.com/astaxie/beego"
- )
- func main() {
- beego.Run()
- }
而在路由的初始化函数中, 我们会声明各个路由与控制器之间的映射关系:
- package routers
- import (
- "quickstart/controllers"
- "github.com/astaxie/beego"
- )
- func init() {
- beego.Router("/", &controllers.MainController{})
- }
也可以手动指定 Beego 项目中的静态资源映射:
- beego.SetStaticPath("/down1", "download1")
- beego.SetStaticPath("/down2", "download2")
在具体的控制器中, 可以设置返回数据, 或者关联的模板名:
- package controllers
- import (
- "github.com/astaxie/beego"
- )
- type MainController struct {
- beego.Controller
- }
- func (this *MainController) Get() {
- this.Data["Website"] = "beego.me"
- this.Data["Email"] = "astaxie@gmail.com"
- this.TplNames = "index.tpl" // version 1.6 use this.TplName = "index.tpl"
- }
DevPractics: 开发实践
文件读写
- 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 *int
- y *int
- }
- tests := []struct {
- name string
- args args
- }{
- // TODO: Add test cases.
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- swap(tt.args.x, tt.args.y)
- })
- }
- }
来源: http://zhuanlan.51cto.com/art/201804/570080.htm