这是我参与「第五届青训营 」伴学笔记创作活动的第 1天、
1.1 Go语言创世纪
Go语言最初由Google公司的Robert Griesemer、Ken Thompson和Rob Pike三个大牛于2007年开始设计发明,设计新语言的最初的洪荒之力来自于对超级复杂的C++11特性的吹捧报告的鄙视,最终的目标是设计网络和多核时代的C语言。
1.2函数、方法、接口
函数有具名函数和匿名函数之分: 具名函数:包级函数一般是具名函数,具名函数是匿名函数的一种特例。 匿名函数:匿名函数引用了外部作用域中的变量时就成了闭包函数 方法是绑定到一个具体类型的特殊函数,Go语言中的方法是依托于类型的,必须在编译时静态绑定。 接口定义了方法的集合,这些方法依托于运行时的接口对象,因此接口对应的方法是在运行时动态绑定的. 包初始化流程,先执行main包下的main函数,但如果导入了其他包,先初始化其他包的常量、再初始化其他包的变量,再执行其他包的init函数,最后初始化main包中的常量、变量,再执行init函数。(注意:main.main函数执行之前,所有代码都在同一个goroutine中运行,所以如果使用go关键字创建新的goroutine,新的goroutine只有在进入main.main之后才会被执行) 传入参数和返回参数可以有多个,函数调用都是值传递。可变数量的参数必须是最后出现的参数,可变数量的参数其实是一个切片类型的参数。 当可变参数是一个空接口类型时,调用者是否解包可变参数会导致不同的结果:
示例程序
func main(){
var a = []interface{}{123, "abc"}
Print(a...) //123 abc
Print(a) //[123 abc]
}
func Print(a ...interface{}) {
fmt.Println(a...)
}
可以给函数的返回值命名,如果返回值命名了,可以通过名字来修改返回值,也可以通过 defer 语句在 return 语句之后修改返回值。闭包在捕获外界变量时不是值传递的方式访问,而是以引用的方式访问。
func Inc() (v int) {
defer func(){ v++ }()
return 42
}
最终返回43
每个goroutine刚启动时只会分配很小的栈(4或8KB,具体依赖实现),根据需要动态调整栈的大小,栈最大可以达到GB级(依赖具体实现,在目前的实现中,32位体系结构为250MB,64位体系结 构为1GB),程序员不需要关注内存分配在栈中还是分配在堆中,由程序自己决定。 在Go1.4以前,Go的动态栈采用的是分段式的动态栈,通俗地说就是采用一个链表来实现动态栈,每个链表的节点内存位置不会发生变化,但由于内存地址不连续对读取性能影响比较大。Go1.4之后改用连续的动态栈实现,也就是采用一个类似动态数组的结构来表示栈,就存在扩容到一定程度之后需要将整个栈迁移寻找合适的位置,所以内存地址是不固定的。 Go语言中,通过在结构体内置匿名的成员来实现继承,通过嵌入匿名的成员,我们不仅可以继承匿名成员的内部成员,而且可以继承匿名成员类型所对应的方法:
type Cache struct {
m map[string]string
sync.Mutex
}
func (p *Cache) Lookup(key string) string {
p.Lock()
defer p.Unlock()
return p.m[key]
}
Cache 结构体类型通过嵌入一个匿名的 sync.Mutex 来继承它的 Lock 和 Unlock 方法. 但是在调用 p.Lock()和 p.Unlock() 时,p并不是Lock 和Unlock 方法的真正接收者, 而是会将它们展开为 p.Mutex.Lock() 和 p.Mutex.Unlock() 调用. 这种展开是编译期完成的, 并没有运行时代价.
- fmt.Printf是完全基于接口的,真正执行是依赖于fmt.Fprintf函数,C语言只能打印基础类型,而go灵活的接口特性,可以打印任意对象。
- error是内置的错误接口,任何实现error的类型都可以作为error的返回值。
- 类型和该类型的方法,必须在同一个包中。
- 种通过嵌入匿名接口或嵌入匿名指针对象来实现继承的做法其实是一种纯虚继承,我们继承的只是接口指定的规范,真正的实现在运行的时候才被注入。