GOROOT
VS GOPATH
VS GOBIN
VS GOPROXY
GOROOT
: Go语言安装路径
GOPATH
: 若干工作区目录的路径。是我们自己定义的工作空间
GO1.8
版本之后,开发包安装完成后会自动设置一个GOPATH
目录,
GO1.14
版本之后,推荐使用Go Module
模式,不一定非要将代码写在GOPATH
目录下,也不需要自己配置GOPATH
GOBIN
: GO程序生成的可执行文件的路径
GOPROXY
: 默认为GOPROXY=https://proxy.golang.org,direct
,修改为GOPROXY=https://goproxy.cn,direct
跨平台编译
Windows 编译 LINUX或者OSX
1 2 3 4
| SET CGO_ENABLED=0 // 禁用CGO SET GOOS=linux // 目标平台, [windows, linux, darwin] SET GOARCH=amd64 // 目标处理器架构 go build
|
LINUX 或者 OSX 编译其他环境
1
| CGO_ENABLED=0 GOOS=linux|darwin|windows GOARCH=amd64 go build
|
- 只有Windows 需要在cmd窗口中运行
SET
命令
编写测试
- 有一个程序名为
xxx.go
,则其测试程序应该名为xxx_test.go
- 测试函数的命名以
Test
开始,例如Testxxx()
- 测试函数只能有一个参数
t *testing.T
,参数t
是测试的hook
,测试失败时可以执行t.Fail()
等操作
举例
1 2 3 4 5 6 7 8 9 10 11 12
| package main
import "fmt"
func Hello() string { return "Hello world" }
func main() { fmt.Println(Hello()) }
|
1 2 3 4 5 6 7 8 9 10 11 12
| package main
import "testing"
func TestHello(t *testing.T){ got := Hello() want := "Hello world" if got != want{ t.Errorf("got '%q' want '%q'", got, want) } }
|
- 在终端直接执行
go test
命令即可进行测试,如果文件名错误,会报错:? gogo [no test files]
重构 t.Helper()
现在有这两个文件hello.go, hellotest.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package main
import "fmt"
const helloPrefix = "Hello "
func Hello(name string) string { return helloPrefix + name }
func main() { fmt.Println(Hello("world")) }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package main
import "testing"
func TestHello(t *testing.T){ assertCorrectMessage := func(t *testing.T, got, want string){ t.Helper() if got != want{ t.Errorf("got '%q' want '%q'", got, want) } } t.Run("saying hello to people", func(t *testing.T){ got := Hello("chris") want := "Hello chris" assertCorrectMessage(t, got, want) }) t.Run("say hello world when an empty string is supplied", func(t *testing.T){ got := Hello("") want := "Hello world" assertCorrectMessage(t, got, want) }) }
|
- 将断言单独作为一个函数,由两个子测试进行调用。
- 第一个子测试能通过,第二个子测试失败
t.Helper()
告诉测试套件这个函数是一个辅助函数,这样测试失败时报告的行号将在函数调用中,而不是在辅助函数内部。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| # 不添加t.Helper()的输出 --- FAIL: TestHello (0.00s) --- FAIL: TestHello/say_hello_world_when_an_empty_string_is_supplied (0.00s) hello_test.go:15: got '"Hello "' want '"Hello world"' # 断言函数处的行号,但是具体不知道是哪个测试用例出错 FAIL exit status 1 FAIL gogo 0.001s
# 添加t.Helper()的输出 --- FAIL: TestHello (0.00s) --- FAIL: TestHello/say_hello_world_when_an_empty_string_is_supplied (0.00s) hello_test.go:28: got '"Hello "' want '"Hello world"' # 具体测试用例内部的行号 FAIL exit status 1 FAIL gogo 0.001s
|
命名返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| func Hello(name string, language string) string { if name == ""{ name = "world" } return greetingPrefix(language) + name }
func greetingPrefix(language string) (prefix string){ switch language { case french: prefix = frenchHelloPrefix case spanish: prefix = spanishHelloPrefix default: prefix = englishHelloPrefix } return }
|
- 在函数签名中,使用了命名返回值
prefix string
- 会自动创建一个名为
prefix
的变量,并且分配零值,即如果是int
,则prefix=0
,如果是string
,则prefix=""
- 会在
Go Doc
中显示,代码更加清晰
- 只需要直接写
return
即可,不需要return prefix
- 函数如果是小写字母开头,则是私有函数;如果是大写字母开头,则是公共函数
示例
1 2 3 4 5 6
|
func Add(a, b int) int { return a + b }
|
Add
函数有两个相同类型的参数,所以可以直接写成x, y int
- 添加的函数注释
Add takes two integers and returns the sum of them
会放在Go Doc中
- 添加示例函数,示例函数同样会更新在Go Doc中,可以反映出代码的实际功能
1 2 3 4 5 6
| func ExampleAdd(){ sum := Add(1, 2) fmt.Println(sum) }
|
// Output: 3
实际上是ExampleAdd()
这个函数的期望输出,如果写5,表明希望1 + 2 = 5,会返回测试失败
- 这个语法不能在别的测试中使用,只有
Examplexxx()
中可以使用
- 使用
go test -v
可以输出每个测试用例的通过情况,以及整体的通过情况,输出结果如下
1 2 3 4 5 6
| === RUN TestAdd --- PASS: TestAdd (0.00s) === RUN ExampleAdd --- PASS: ExampleAdd (0.00s) PASS ok integer 0.001s
|
基准测试
1 2 3 4 5 6 7 8
| func Repeat(a string) string{ var repeat string for i := 0; i < 5; i++{ repeat += a } return repeat }
|
1 2 3 4 5 6
| func BenchmarkRepeat(b *testing.B){ for i := 0; i < b.N; i++{ Repeat("a") } }
|
testing.B
可以访问隐性命名b.N
,表示这个代码的运行次数,并记录时间
- 测试框架会选择
b.N
- 使用
go test -bench=.
来运行基准测试,如果在Windows
中则使用go test -bench="."
- 如果直接使用
go test [-v]
不会运行基准测试
- 使用
go test -cover
查看覆盖率
数组 VS 切片
numbers := [5]int{1,2,3,4,5}
numbers := [...]int{1,2,3,4,5}
- 遍历数组的两个方式
1 2 3 4 5 6 7 8
| for i := 0; i < 5; i ++{ ... }
for _, number := range numbers{ ... }
|
- 数组传参,比如需要传入
numbers
,参数类型应该为numbers [5]int
- 此时如果传入一个
[4]int
传入函数,不能通过编译,因为会判定为不同的类型
- 所以导致数组没什么用,一般用切片
slice
,尺寸不固定
- 切片就是在声明的时候不指定长度,也就是
mySlice := []int{1,2,3}
,而不是mySlice := [3]int{1,2,3}
- 参数是可变数量的切片时,应该写
numbers ... []int
- 不能对切片使用等于号,简单的方法是使用
reflect.DeepEqual
,用于判断两个变量是否相等
- 但是
reflect.DeepEqual
不是类型安全的,甚至可以比较slice
和string
。
- 使用
make
创建切片可以指定容量和长度,创建的新切片中所有元素均为0