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)
- })
- }
- }
【本文是51CTO专栏作者“张梓雄 ”的原创文章,如需转载请通过51CTO与作者联系】