各个类型默认值
bool -> false
numbers -> 0
string -> ""
pointers -> nil
slices -> nil
maps -> nil
channels -> nil
functions -> nil
interfaces -> nil
type Person struct {
AgeYears int
Name string
Friends []Person
}
var p Person // Person{0, "", nil}
var p *int
p == nil // true
*p // panic: invalid memory address or nil pointer dereference
slice 与nil
// nil slices
var s []slice
len(s) // 0
cap(s) // 0
for range s // iterates zero times
s[i] // panic: index out of range
map与nil
// nil maps
var m map[t]u
len(m) // 0
for range m // iterates zero times
v, ok := m[i] // zero(u), false
m[i] = x // panic: assignment to entry in nil map
对于nil的map,我们可以简单把它看成是一个只读的map,不能进行写
func NewGet(url string, headers map[string]string) (*http.Request, error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
for k, v := range headers {
req.Header.Set(k, v)
}
return req, nil
}
NewGet("http://google.com", map[string]string{
"USER_AGENT": "golang/gopher",
},)
// 可以这样传入一个nil
NewGet("http://google.com", map[string]string{})
// 也可以这样
NewGet("http://google.com", nil)
channel与nil
// nil channels
var c chan t
<- c // blocks forever
c <- x // blocks forever
close(c) // panic: close of nil channel
// 假如现在有两个channel负责输入,一个channel负责汇总,简单的实现代码:
func merge(out chan<- int, a, b <-chan int) {
for {
select {
case v := <-a:
out <- v
case v := <- b:
out <- v
}
}
}
// 如果在外部调用中关闭了a或者b,那么就会不断地从a或者b中读出0,这和我们想要的不一样,我们想关闭a和b后就停止汇总了,
// 修改一下代码:
func merge(out chan<- int, a, b <-chan int) {
for a != nil || b != nil {
select {
case v, ok := <-a:
if !ok {
a = nil
fmt.Println("a is nil")
continue
}
out <- v
case v, ok := <-b:
if !ok {
b = nil
fmt.Println("b is nil")
continue
}
out <- v
}
}
fmt.Println("close out")
close(out)
}
在知道channel关闭后,将channel的值设为nil,这样子就相当于将这个select case子句停用了,因为nil的channel是永远阻塞的。
nil与interface
interface并不是一个指针,它的底层实现由两部分组成,一个是类型,一个值,也就是类似于:(Type, Value)。只有当类型和值都是nil的时候,才等于nil。看看下面的代码:
func do() error { // error(*doError, nil)
var err *doError
return err // nil of type *doError
}
func main() {
err := do()
// 输出的是false
fmt.Println(err == nil)
}
输出结果是false。do函数声明了一个*doErro的变量err,然后返回,返回值是error接口,但是这个时候的Type已经变成了 (*doError,nil),所以和nil肯定是不会相等的。所以我们在写函数的时候,不要声明具体的error变量,而是应该直接返回nil:
func do() error {
return nil
}
func do() *doError { // nil of type *doError
return nil
}
func wrapDo() error { // error (*doError, nil)
return do() // nil of type *doError
}
func main() {
err := wrapDo() // error (*doError, nil)
fmt.Println(err == nil) // false
}
这里最终的输出结果也是false。为什么呢?尽管wrapDo函数返回的是error类型,但是do返回的却是*doError类型,也就是变成了(*doError,nil),自然也就和nil不相等了。因此,不要返回具体的错误类型。遵从这两条建议,才可以放心地使用if x != nil。
What
A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking.
how
//demo1 defer使用实例
func main() {
fmt.Println("test1")
defer fmt.Println("defer")
fmt.Println("test2")
}
// 输出
// test1
// test2
// defer
使用规则
Each time a “defer” statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked. Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred. If a deferred function value evaluates to nil, execution panics when the function is invoked, not when the “defer” statement is executed.
- 一个函数中有多个defer时的运行顺序
- defer语句执行时的拷贝机制
- defer如何影响函数返回值
//demo3 多个defer执行顺序
func main() {
fmt.Println("test1")
defer fmt.Println("defer1")
fmt.Println("test2")
defer fmt.Println("defer2")
fmt.Println("test3")
}
// test1
// test2
// test3
// defer2
// defer1
//demo4 defer函数在defer语句执行那一刻就已经确定
// I am function test1
func main() {
test := func() {
fmt.Println("I am function test1")
}
defer test()
test = func() {
fmt.Println("I am function test2")
}
}
//demo5 defer函数参数的值在注册那一刻就已经确定
// 打印10
func f5() {
x := 10
defer func(a int) {
fmt.Println(a)
}(x)
x++
}
// demo6 defer 函数传递参数为指针传递
// 11
func main() {
x := 10
defer func(a *int) {
fmt.Println(*a)
}(&x)
x++
}
//demo7 defer 延迟函数为闭包
// 闭包里的变量本质上是对上层变量的引用
// 11
func main() {
x := 10
defer func() {
fmt.Println(x)
}()
x++
}
// demo10 defer函数与非命名返回值之间的关系
// 返回10
func f10() int {
x := 10
defer func() {
x++
}()
return x
}
//demo11 defer函数与非命名返回值之间的关系
// 返回11
func f11() *int {
a := 10
b := &a
defer func() {
*b++
}()
return b
}
func main() {
fmt.Println("f10", f10())
fmt.Println("f11", *f11())
}
//拆解 demo10_1 defer函数与非命名返回值之间的关系, return拆解
func f10_1() int {
x := 10
defer func() {
x++
}()
//return x => 拆解
_result := x
return _result //实际返回的是_result的值,因此defer中修改x的值对返回值没有影响
}
//demo11_1 defer函数与返回值之间的关系, return拆解
func f11_1() *int {
a := 10
b := &a
defer func() {
*b++
}()
//return b => 拆解
_result := b
return _result //执行defer函数调用*b++, 修改了b指向的内存空间的值,实际返回的是result指针
}
//demo13 defer函数与命名返回值之间的关系
// 11
func f13() (result int) {
defer func() {
result++
}()
return 10
}
// demo14 defer函数与命名返回值之间的关系
// 11
func f14() (result int) {
result = 10
defer func() {
result++
}()
return result
}
//demo15 defer函数与命名返回值之间的关系
// 12
func f15() (b *int) {
a := 10
b = &a
fmt.Println("b", b)
defer func() {
c := 12
b = &c
fmt.Println("defer", b)
}()
return
}
func main() {
fmt.Println("f13", f13())
fmt.Println("f14", f14())
t := f15()
fmt.Println("f15", *t, t)
}
defer 与recovery
//demo16 defer与recover
// catch error: TEST
// I am ok
func f16() {
defer func() {
// 捕获异常
if err := recover(); err != nil {
fmt.Println("catch err:", err)
}
}()
panic(errors.New("TEST"))
}
func main() {
f16()
fmt.Println("I am OK.")
}
why
清理资源,避免遗忘
f, err := os.Open(filename)
if err != nil {
panic(err)
}
defer f.Close() //释放资源
/*
读取和处理文件内容
*/
格式化
- 格式化,使用gofmt(go fmt命令):
- 缩进使用tab而非空格。
- 行长度不限,可以自己缩进换行。
- 括号用的比较少: if, for, switch。
注释文档(godoc/go doc)
- 包(package)注释在文档的最上方。
- 函数文档在函数头部(函数首字母大写才能被导出)。
Name
- 包(package)名:小写,简洁,单词,无下划线。io.Reader/ring.New
- Getters/Setters:obj.Owner()/obj.SetOwner(x)
- 函数/变量:首字母大写才可导出(export)被外部模块使用。
- 驼峰命名法:不使用下划线,如MixedCaps/mixedCaps。
defer
- 函数退出时调用对应的call。
- LIFO。
- 参数是在执行defer语句时确定的,而非对应函数执行时。
分配对象
- new:返回指针,所有内容为零值。
- make:返回对象,仅适用于slices, maps, channels。
Array
- 值对象:赋值是拷贝所有元素。
- 参数传递是值传递,即函数操作的是一份拷贝,不会影响原有对象。
- 长度是类型的一部分,[10]int和[20]int不是同一个类型。
Slices
- 引用对象:赋值其实指向的同一个array。
- 尽量使用slices替代array。
Maps
- 引用对象。
- v := map[key] 不存在的key会赋零值。
- value, ok := m[key] 不存在的key ok为false。 删除:delete(m, key)。key不存在时也是安全的。
Printing
- 带f的都有格式化参数,不带的按照类型默认的输出格式输出。
- 自定义类型%v格式化输出需要实现String()方法。
- 实现String()方法尽量防止%s格式的隐形转化,可能会死循环。 …
- 可变参数:func foo(args …T)。
- 切片打散:append(slice1, slice2…)。
常量(Constants):
- 编译时确定。
- 右值如果是表达式,那么因子都必须是常量。
- iota的使用。
init函数
- 每个文件都可以含有若干个init函数,会在第一次引用的时候执行,且只执行一次。
- 一个文件如果有多个init函数,则按上下顺序执行。
- 一个package里的多个文件会按文件名的字典顺序执行,
值(values)和指针(pointers)
- 值方法(method)可以被值类型或者指针类型调用。
- 指针方法只能被指针类型调用。
- 指针方法能改变结构的内容,值方法不行。
- 优先声明methods on *T unless you have a strong reason to do otherwise.
接口(interfaces)
- 含义:如果X可以做这件事(实现了这些方法),那么X就能在这用。
- 几乎所有类型都可以增加方法,也即可以实现接口,如:内置类型,通道,函数等。
- 原则:如果一个类型设计上仅需要实现一个接口,那么不应该暴露自己的类型给外部,而是暴露接口的类型(举例:hash算法很多,不同的hash算法类型构造器应返回接口类型,这样替换hash算法时只需要修改一处构造代码)。
类型转换
- 获得值的类型:typeName := value.(type),常用语switch语句。
- 类型转换:v, ok := value.(typeName)。没有ok参数失败会出现运行时错误。
嵌入
- struct/interface都可以使用。
- 不带有变量名,仅含有类型:匿名成员?。
- 初始化:和正常一样初始化。T{v1, v2, v3…}。
- 引用:直接操作类型(job.Logger.Printf,其中Logger是类型)。
- 好处:和继承类似,上层类型可以直接调用下层类型的方法。
- 坏处:名字冲突:上层的名字优先级高于下层。
并发
- 不要通过共享内存来交互,通过交互来共享内存。
- 尽量使用channel通信。
Goroutines
- 多个协程共享同一地址空间。
- 轻量级的,比栈消耗资源多点。
- 有限使用栈,栈内存会随需要增大,如果有需要则会使用堆。
- 协程+多线程(具体调度较复杂)。
Channels
- make关键字创建。
- 引用类型。
- 第二个整形参数代表bufferk大小,默认0。ci := make(chan int) cj := make(chan int, 3)。
- 用途:a) 消息队列。 b) 协程间数据的同步交互。 c) 并发控制。
并行(Parallelization)
- CPU核心数:runtime.NumCPU()
- go能使用的最大核心数:runtime.GOMAXPROCS,默认值为runtime.NumCPU。
Error & Panic
- Error:可恢复错误,实现Error接口。
- Panic:不可恢复错误。一般初始化失败的时候使用。
- Panic发生时会根据调用栈一层层退出,但会执行defer调用。
Recover
- 多协程时,一个协程出现panic异常退出希望不会影响到其他的协程。
- panic可以被捕获,防止整个进程退出。
- panic发生时会执行defer,所以只能在defer中去捕获panic。
- 捕获panic仅仅在defer代码中调用recover()语句即可。
- 在非defer语句中调用recover()会返回nil。
- defer可以修改命名返回值,所以可以在defer中重制命名返回值。
why interface
- writing generic algorithm
- hiding implementation detail
- providing interception points
// 实现泛型编程,第一个优点
func sayHello(ig IGreeting) {
ig.sayHello()
}
golang := Go{}
php := PHP{}
// golang与php实现了IGreeting接口,可以直接使用sayHello函数,即"泛型编程"
sayHello(golang)
sayHello(php)
// 通过返回接口,来隐藏实现细节
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
}
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) //返回 cancelCtx
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) //返回 timerCtx
func WithValue(parent Context, key, val interface{}) Context //返回 valueCtx
// 通过 interface,我们可以通过类似装饰器方式实现 dynamic dispatch
type header struct {
rt http.RoundTripper
v map[string]string
}
func (h header) RoundTrip(r *http.Request) *http.Response {
for k, v := range h.v {
r.Header.Set(k,v)
}
return h.rt.RoundTrip(r)
}
how interface
- 接口嵌套
- 接口类型间可以组合使用
- 接口类型间的嵌入不会涉及方法间的”屏蔽”,只要方法同名就会产生冲突,并无法通过编译
type Animal interface {
// ScientificName 用于获取动物的学名。
ScientificName() string
// Category 用于获取动物的基本分类。
Category() string
}
type Named interface {
// Name 用于获取名字。
Name() string
}
//包含Animal接口与Named接口
type Pet interface {
Animal
Named
}
- 空接口
- 空接口就是不包含任何方法的接口。因此,所有的类型都实现了空接口
- []T不能直接赋值给[]interface{}
// 所有类型都实现了空接口
var v1 interface{} = 1
var v2 interface{} = "abc"
var v3 interface{} = struct{ X int }{1}
// 如果函数打算接收任何数据类型,则可以将参数声明为interface{}。最典型的例子就是标准库fmt包中的Print和Fprint系列的函数:
func Fprint(w io.Writer, a ...interface{}) (n int, err error)
// 下述代码在编译时会报错:cannot use t (type []int) as type []interface {} in assignment
t := []int{1, 2, 3, 4}
var s []interface{} = t
- 区分接口存储类型的方法
- 将函数里interface{}类型的参数转换为具体的类型的方法有两种:type assert和type switch判断
type assert
type assert: <目标类型的值>,<布尔参数> := <表达式>.( 目标类型 )
Params := make([]interface{}, 3)
Params[0] = 88 // 整型
Params[1] = "go interface type" // 字符串
Params[2] = Go{Name: "go"} // 自定义结构体类型
for index, v := range Params {
if value, ok := v.(int); ok {
fmt.Printf("[comma_ok]Params[%d] 是int类型 ,值:%v\n", index, value)
}else if value, ok := v.(string); ok{
fmt.Printf("[comma_ok]Params[%d] 是string类型 ,值:%v\n", index, value)
}else if value, ok := v.(Go); ok{
fmt.Printf("[comma_ok]Params[%d] 是自定义数据类型 ,值:%v\n", index, value)
}
}
反射三原则:
- Reflection goes from interface value to reflection object.
- Reflection goes from reflection object to interface value.
- To modify a reflection object, the value must be settable.
关键概念:
- reflection object: 最关键的是reflection.Type与reflection.Value。也可以说是变量的元信息的类定义
- interface value, 因为空接口 interface{}是可以作为一切类型值的通用类型使用。这里的接口值interface value可以理解为空接口变量值interface{} value。
应用场景
- 判断未知对象是否实现了具体接口
通常判断未知对象是否实现具体接口很简单,直接通过 变量名.(接口名) 类型验证的方式就可以判断。但也有例外,框架代码实现中检查调用代码的情况。因为框架代码先实现,调用代码后实现,也就无法在框架代码中通过简单额类型验证的方式进行验证
grpcServer := grpc.NewServer()
// 服务端实现注册
pb.RegisterRouteGuideServer(grpcServer, &routeGuideServer{})
注册的实现没有实现所有的服务接口时,程序就会报错。它是如何做的,我们用简单的场景来看下原理。
//目标接口定义
type Foo interface {
Bar(int)
}
dst := (*Foo)(nil)
dstType := reflect.TypeOf(dst).Elem()
//验证未知变量 src 是否实现 Foo 目标接口
srcType := reflect.TypeOf(src)
if !srcType.Implements(dstType) {
log.Fatalf("type %v that does not satisfy %v", srcType, dstType)
}
这段代码通常会是在程序的启动阶段所以对于程序的性能而言没有任何影响。
- 结构体字段属性标签
通常定义一个待JSON解析的结构体时,会对结构体中具体的字段属性进行tag标签设置,通过tag的辅助信息对应具体JSON字符串对应的字段名,这里利用结构体字段属性标签做反射的例子
type App struct{
// attributes ...
// metrics ...
metrics struct{
// Size of the current server queue
QueueSize metrics.Gauge `metric:"thrift.udp.server.queue_size"`
// Size (in bytes) of packets received by server
PacketSize metrics.Gauge `metric:"thrift.udp.server.packet_size"`
// Number of packets dropped by server
PacketsDropped metrics.Counter `metric:"thrift.udp.server.packets.dropped"`
// Number of packets processed by server
PacketsProcessed metrics.Counter `metric:"thrift.udp.server.packets.processed"`
// Number of malformed packets the server received
ReadError metrics.Counter `metric:"thrift.udp.server.read.errors"`
}
}
在应用中首先直接定义匿名结构metrics, 将针对该应用的metric探测标量定义到具体的结构体字段中,并通过其字段标签tag的方式设置名称。这样在代码的可读性大大增强了。初始化代码
import "github.com/jaegertracing/jaeger-lib/metrics/prometheus"
//初始化
metrics.Init(&app.metrics, prometheus.New(), nil)
- 函数适配
func newRequest(method string, url *url.URL, body string) *http.Request {
req, err := http.NewRequest(method, url.String(), createBody(body))
if err != nil {
log.Fatalf("unable to create request: %v", err)
}
for _, h := range httpHeaders {
k, v := headerKeyValue(h)
if strings.EqualFold(k, "host") {
req.Host = v
continue
}
req.Header.Add(k, v)
}
return req
}
func main() {
server, err := net.Listen("tcp", ":6000")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
go func() { // Launch routine that will accept connections
for {
// 接受一个新的TCP连接
conn, err := server.Accept()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if len(users) < maxUsers {
newConnection <- conn // Send to handle new user
} else {
// 向网络连接直接写入字符串
io.WriteString(conn, "Server is full!")
}
}
}()
}
不指定A依赖的版本
指定A依赖的版本