Go 的优点

Go like C++

  • 内存消耗少
  • 执行速度快
  • 启动快 Go not like C++
  • 程序编译时间短
  • 像动态语言一样灵活(runtime, interface, 闭包,反射)
  • 内存并发正常

defer

defer 执行顺序是先进后出,栈的方式 FIFO ,参数的值在 defer 语句执行就已经确定了

for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}

执行结果:

4 3 2 1 0

append 不是线程安全的

slice 中,如果 a[x] 和 b[y] 指向同一个内存区域,那么存在竞态关系

package main

import (
"fmt"
)

func main() {
a := []int{1, 2}
b := a[1:]
go func() {
a[1] = 0
}()

fmt.Println(b[0])

}

slice.go

package main

import (
"fmt"
)

func main() {
a := []int{1, 2}
b := a[1:]
go func() {
a[1] = 0
}()
fmt.Println(b[0])
}

运行命令

go run -race slice.go

执行结果

2
==================
WARNING: DATA RACE
Write at 0x00c0000bc018 by goroutine 7:
main.main.func1()
/Users/bytedance/go/src/code.byted.org/wangmingming.hit/GoProject/main/slice.go:11 +0x47

Previous read at 0x00c0000bc018 by main goroutine:
main.main()
/Users/bytedance/go/src/code.byted.org/wangmingming.hit/GoProject/main/slice.go:14 +0xb9

Goroutine 7 (running) created at:
main.main()
/Users/bytedance/go/src/code.byted.org/wangmingming.hit/GoProject/main/slice.go:10 +0xab
==================
Found 1 data race(s)
exit status 66

零值

零值和未初始后的值并不相同, 不同类型的零值是什么

  1. 布尔类型是 false, 整型是0, 字符串是 “”
  2. 指针,函数,interface 、slice 、channel 和 map 的零值都是 nil
  3. 结构体的零值是递归生成的,每个成员都是对应的零值

使用要注意如下几点:

  • 一个为nil的slice,除了不能索引外,其他的操作都是可以的
  • nil的map,我们可以简单把它看成是一个只读的map
// 一个为nil的slice,除了不能索引外,其他的操作都是可以的
// Note: 如果这个slice是个指针,不适用这里的规则
var a []int
fmt.Printf("len(a):%d, cap(a):%d, a==nil:%v\n", len(a),cap(a), a == nil) //0 0 true
for _, v := range a{// 不会panic
fmt.Println(v)
}
aa := a[0:0] // 也不会panic,只要索引都是0

// nil的map,我们可以简单把它看成是一个只读的map
var b map[string]string
if val, ok := b["notexist"];ok{// 不会panic
fmt.Println(val)
}
for k, v := range b{// 不会panic
fmt.Println(k,v)
}
delete(b, "foo") // 也不会panic
fmt.Printf("len(b):%d, b==nil:%v\n", len(b), b == nil) // 0 true

值传递

Go 语言中所有的传参都是值传递,或者说一个拷贝,传入的数据能不能在函数内被修改,取决于是指针或者含有指针的类型(指针被值传递复制后依然指向同一块地址),什么时候传入的参数会修改会生效,什么时候不会生效。slice类型在 值传递的时候len和cap不会变,所以函数内append没有用:

type slice struct {
array unsafe.Pointer
len int
cap int
}
// badcase
func appendMe(s []int){
s = append(s, -1)
}

map 和 chan 是引用类型,是个指针, 在函数内修改会生效

// map实际上是一个 *hmap
func makemap(t *maptype, hint int64, h *hmap, bucket unsafe.Pointer) *hmap {
//省略无关代码
}

// chan实际上是个 *hchan
func makechan(t *chantype, size int64) *hchan {
//省略无关代码
}

结构体传参

// 这是一个典型的指针包裹类型
type Person struct {
name string
age *int
}
func modify(x Person){
x.name = "modified"
*x.age = 66
}

这个结构体中 age 是个指针类型,在函数内会被修改

复制数据时,使用 copy 比 append 性能更好

import (
"crypto/rand"
"testing"
)
var (
src = make([]byte, 512)
dst = make([]byte, 512)
)
func genSource() {
rand.Read(src)
}
func BenchmarkCopy(b *testing.B) {
for n := 0; n < b.N; n++ {
b.StopTimer()
genSource()
b.StartTimer()
copy(dst, src)
}
}
func BenchmarkAppend(b *testing.B) {
for n := 0; n < b.N; n++ {
b.StopTimer()
genSource()
b.StartTimer()
dst = append(dst, src...)
}
}

dst 作为全局变量防止编译器优化 for-loop

uptime;go version;go test -bench=. ./
11:56:10 up 294 days, 14:58, 3 users, load average: 0.58, 0.52, 0.63
go version go1.14.1 linux/amd64
goos: linux
goarch: amd64
pkg: copyvsappend
BenchmarkCopy-40 9808320 116 ns/op
BenchmarkAppend-40 479055 8740 ns/op
PASS

Go 语言中为啥没有继承

go 没子类型的概念,只能把类型嵌入另外一个类型中,所以没有类型系统。

  • 使用伸缩性良好的组合,而不是继承
  • 数据和方法不绑定在一起,数据的集合使用 struct, 方法的集合使用 interface ,保持正交

接收器是用指针还是值

go 接收器可以用指针,也可以传值,传值的时候接收器不会改变。如果以下两种情况,请使用指针:

  • mystruct 很大时,需要拷贝的成本太高
  • 方法需要修改 myStruct

Note:如果对象有可能并发执行方法,指针接收器中可能产生数据竞争,记得加锁

func(s * MyStruct)pointerMethod(){    // 指针方法
s.Age = -1 // useful
}
func(s MyStruct)valueMethod(){ // 值方法
s.Age = -1 // no use
}

for 循环里的是副本

for key, element = range aContainer {...}
  1. 实际遍历的 aContainer 是原始值的一个副本
  2. element 是遍历到的元素原始值的一个副本
  3. key 和 Value 整个循环都是同一个变量,每次迭代都生成新变量

aContainer和element的拷贝成本。aContainer 数组的时候的拷贝成本比较大,而切片和map的拷贝成本比较小。如果想要缩小拷贝成本,我们有几个建议:

  1. 遍历大数组时,可以先创建大数组的切片再放在range后面
  2. element结构比较大的时候,直接用下标key遍历,舍弃element

map 的值不可取址

map 是哈希表的实现,所以值的地址在哈希表动态调整的时候会可能产生变化,因此,存在着 map 值的地址是没意义的,go 禁止了 map 取址操作,以下类型都不可取址

  • map 元素
  • string 的字节元素
  • 常量(有名变量和字面量都不可以)
  • 中间结果值(函数调用,显示值转换,各种操作)
// 下面这几行编译不通过。
_ = &[3]int{2, 3, 5}[0] //字面量
_ = &map[int]bool{1: true}[1] //字面量
const pi = 3.14
_ = &pi //有名常量
m := map[int]bool{1: true}
_ = &m[1] //map的value
lt := [3]int{2, 3, 5}
_ = &lt[1:1] //切片操作

常用的仓库

strings

有 strings 库,不要重复造轮子,很多人试图再写一遍,没必要

字符串前后处理

var s = "abaay森z众xbbab"
o := fmt.Println
o(strings.TrimPrefix(s, "ab")) // aay森z众xbbab
o(strings.TrimSuffix(s, "ab")) // abaay森z众xbb
o(strings.TrimLeft(s, "ab")) // y森z众xbbab
o(strings.TrimRight(s, "ab")) // abaay森z众x
o(strings.Trim(s, "ab")) // y森z众x
o(strings.TrimFunc(s, func(r rune) bool {
return r < 128 // trim all ascii chars
})) // 森z众

字符串分割与合并

// "1 2 3" -> ["1","2","3"]
func Fields(s string) []string // 用空白字符分割字符串
// "1|2|3" -> ["1","2","3"]
func Split(s, sep string) []string // 用sep分割字符串,sep会被去掉
// ["1","2","3"] -> "1,2,3"
func Join(a []string, sep string) string // 将一系列字符串连接为一个字符串,之间用sep来分隔

// Note:
// "1||3" -> ["1","","3"]

错误处理

  1. 可以把异常传递下去,并不丢失自己的类型
  2. 可以保存堆栈信息

for range

如果每个元素比较大,循环时,使用range 取值的方式遍历,性能比较差

package bench

import "testing"

var X [1 << 15]struct {
val int
_ [4096]byte
}
var Result int

func BenchmarkRangeIndex(b *testing.B) {
var r int
for n := 0; n < b.N; n++ {
for i := range X {
x := &X[i]
r += x.val
}
}
Result = r
}
func BenchmarkRangeValue(b *testing.B) {
var r int
for n := 0; n < b.N; n++ {
for _, x := range X {
r += x.val
}
}
Result = r
}
func BenchmarkFor(b *testing.B) {
var r int
for n := 0; n < b.N; n++ {
for i := 0; i < len(X); i++ {
x := &X[i]
r += x.val
}
}
Result = r
}

执行命令

go test -bench=. bench_test.go

Go 中的坑_数据在这里插入图片描述


欢迎关注公众号:程序员开发者社区

Go 中的坑_golang_02

关注我们,了解更多


学习资料

  • 《Go Tour》(一个小时学会Go)https://tour.go-zh.org/welcome/1
  • 《The Go Programming Language Specification》(语法细节)https://golang.org/ref/spec#Introduction(中文版《Go语言编码规范》)
  • 《Go语言圣经》(语法细节)https://docs.hacknode.org/gopl-zh/
  • 《Effective Go》(适合刚学完Go的基础语法时候读)https://www.kancloud.cn/kancloud/effective/72199
  • 《Go语言设计和实现》(适合想了解Go某个特性实现原理的时候参考)https://draveness.me/golang/docs/part1-prerequisite/ch02-compile/golang-compile-intro/
  • 《Go Q&A 101》(可以和官方QA结合看)https://gfw.go101.org/article/unofficial-faq.html#time-sleep-after
  • 《Go 语言高级编程》https://chai2010.cn/advanced-go-programming-book/
  • 《Go语言原本》https://golang.design/under-the-hood/
  • 《Google Go代码规范》https://github.com/golang/go/wiki/CodeReviewComments
  • 《Uber Go规范》https://github.com/xxjwxc/uber_go_guide_cn
  • ​https://learnku.com/articles/56078​
  • ​https://learnku.com/go/wikis/49781​
  • ​https://colobu.com/2015/09/07/gotchas-and-common-mistakes-in-go-golang/#​