GO踩坑集锦
项目环境
首先我们得把自己的项目目录加入环境变量里,不然它找不到包啊。
GOPATH=$HOME/go:your/project/path
目录结构
- 每个包都得放到一个独立文件夹里!
- main包得放到项目root目录下,不然编译过不了。
开发工具栈
工具库
strconv
-
strconv.Itoa(n)
将int
型数字n
转为十进制字符串
反向转换strconv.Atoi(s)
-
strconv.FormatInt(n, 10)
将int64
数字n
转为十进制字符串
反向转换strconv.ParseInt(s, 10, 64)
strings
string
转Reader
r := strings.NewReader("abcde")
io/ioutil
Reader
转[]byte
r := strings.NewReader("abc")
b, err := ioutil.ReadAll(r)
encoding/base64
[]byte
转base64
fileByte, _ := ioutil.ReadAll(file) fileBase64 := base64.StdEncoding.EncodeToString(fileByte)
GO Tools
godoc
安装
go get -v golang.org/x/tools/cmd/godoc
go-swagger
当前项目下生成swagger.json
$ swagger generate spec -o ./swagger.json
将swagger.json
以web页面形式展示
swagger serve -p=10100 -F=swagger ./swagger.json
GO 类型及转换
整型
整型分为以下两个大类: 按长度分为:int8、int16、int32、int64 对应的无符号整型:uint8、uint16、uint32、uint64
浮点型
Go语言支持两种浮点型数:float32和float64。
GO 基础
make 和 new 的区别
new
的函数声明:func(Type) *Type
The value returned is a pointer to a newly allocated zero value of that type.
new
对该类型分配一个内存空间并进行了零值初始化,并返回了其指针。
var a *int // (*int)(nil)
a = new(int) // (*int)(0xc0000a41b0)
对于引用类型,只有在值的引用指针不为nil时才能对其进行赋值操作。
make
的函数声明:func(t Type, size ...IntegerType) Type
和new
不一样的地方在于,make
只能初始化slice
,map
,channel
类型的值,make
不仅初始化了内存地址,还为其分配了内存空间,使其初始值不为nil。这样,就可以对其进行赋值操作。
var ap = new([]int) // &[]int(nil)
var ap1 = new([1]int) // &[1]int{0}
var a = make([]int, 0, 0) // []int{}
var mp = new(map[string]string) // map[string]string(nil)
var m = make(map[string]string) // map[string]string{}
这一点对于map
来说十分重要,因为没有指定大小,new
操作并没有为其分配内存空间,尝试为其赋值会出错。但是make
会为其分配一个很小的空间。
var mp = *new(map[string]string)
mp["a"] = "1"
// panic: assignment to entry in nil map
string的实现
string的底层是[]byte
它的底层结构为
type StringHeader struct {
Data uintptr
Len int
}
字符串的内容就是StringHeader.Data
所指向的[]byte
fmt格式
Printf
用于格式化字符串,其中拥有多种占位符,每个占位符都有自己的格式化结果。
fmt.Printf("%s", "text")
占位符 | 描述 | 例子 |
---|---|---|
%v | 默认格式化 | fmt.Printf("%v%v\n", 1, "xxx") -> “1xxx” |
%s | 转换字符串 | fmt.Printf("%s", "text") -> “text” |
%t | 转换布尔值 | fmt.Printf("%t", true) -> “true” |
%b,%d,%o,%x | 转换进制 | fmt.Printf("%b", 888) -> “1101111000” |
%p | 转换指针 | fmt.Printf("%p", new(int)) -> “0xc00011c198” |
%f | 转换浮点数 | fmt.Printf("%.4f", math.Pi) -> “3.1415” |
字符串字符长度
由于golang默认使用utf-8编码格式,所以如果需要计算字符数而不是字节数,要这么用
import "unicode/utf8"
len("你好") // 6,中文占3个字节
utf8.RuneCountInString("你好") // 2
遍历字符串
for _, v := range "你好" {}
// 相当于
for _, v := range []rune("你好") {}
for _, v := range "你" {
fmt.Println(v) // 20320
fmt.Printf("%c", v) // 你
fmt.Println(string(v)) // 你
}
参数解构
func foo(args ...int) {
bar(args...)
}
通过...type
和slice...
,可以将多个参数变为切片,以及将切片变为多个参数传递。
GO 最佳实践
使用buffer拼接字符串
由于字符串是不变的,使用a+=b
形式拼接字符串会消耗过多内存,所以建议使用byte buffer
的形式
var b bytes.Buffer
...
for condition {
b.WriteString(str) // 将字符串str写入缓存buffer
}
return b.String()
指针和接口类型
永远不要使用一个指针指向一个接口类型,因为它已经是一个指针。
,ok模式
在Golang中,基于多返回值的特性,在返回正确结果时,我们也可以定义返回的错误异常。所以就有了,ok的模式。
if val, ok := dosomething(); ok {}
这种模式常被用于:
1. 检查函数错误
if val, err := func(); err != nil {
// 处理错误
}
// 处理val
2. 检查键是否存在
if val, exist := somemap["key"]; exist {}
3. 类型断言
if val, ok := value.(T); ok {}
4. 检查通道关闭
for {
if val, open := <-ch; !open {
break
}
}
复制类型切片到空接口切片
如果你想这样操作:
var a []sometype
var b []interface{} = a
这样是不行的,因为他们的内存布局不一样,需要进行循环转换。反过来也一样。
GO 测试
GO 自带了单元测试和性能测试功能
go test ./...
以上命令会将目录下的所有测试文件(以_test.go
结尾)都跑一遍。
-v 选项
显示打印日志
-run regexp
只执行特定的测试函数,通过一个正则表达式匹配需要执行的测试
比如:
go test -run TestFoo ./...
只会执行函数名以TestFoo
开头的测试函数
取消缓存
使用-count=1
来取消测试缓存
跳过测试
func TestFoo(t *testing.T) {
t.Skip("reason")
// ...
}