函数
匿名函数
匿名函数就是没有定义名字符号的函数。
将匿名函数赋值给变量和为普通函数提供名字标示符有着本质的区别。编译器会给匿名函数生成一个符号名。
不曾使用的匿名函数会被编译器当成编译错误。
闭包
闭包是在其词法上下文中引用了自由变量的函数,或者说是函数和其引用的组合体。
func test(x int) func() {
return func() {
println(x)
}
}
func main() {
f := test(123)
f()
}
上段代码中,test返回的匿名函数里会引用上下文环境变量x。当该函数在main中执行时,它依然可以正确读取x的值。这种现象就称为闭包。
延迟求值
闭包通过指针引用环境变量,那么可能会导致其生命周期被延长,甚至还会被分配到堆内存。另外,还会有所谓的“延迟求值”的特性。
func test()[]func {
var s []func()
for i:=0; i< 2;i++ {
s = append(s,func(){
println(&i,i)
})
}
return s
}
func main() {
for _, f := test() {
f()
}
}
输出:
0xc42000a110 2
0xc42000a110 2
append操作仅仅将匿名函数加入列表,并未执行。当main函数执行这些函数时,读取的环境变量i是最后一次循环时的值。
解决方法就是每次用不同的环境变量或传参复制,让各自的闭包环境不同。
func test()[]func {
var s []func()
for i:=0; i< 2;i++ {
x:=i //x每次循环都重新定义
s = append(s,func(){
println(&x,x)
})
}
return s
}
func main() {
for _, f := test() {
f()
}
}
输出:
0xc420054000 0
0xc420054008 1
延迟调用defer
FILO执行,一般用于资源释放,接触锁定以及错误处理等。
panic和recover
panic会终止当前函数流程,执行defer,在defer中我们使用recover捕获错误,并返回panic提交错误对象。
方法
方法是与对象实例绑定的特殊函数。
方法和函数的语法区别在于方法有前置的实例接收参数(receiver)。
方法同样不支持重载。
如何选择方法receiver的类型?
- 要修改实例状态,用*T
- 无需修改状态的小对象或固定值,用T
- 大对象建议用*T,减少复制成本
- 引用类型,字符串,函数等指针包装类型,直接用T
- 若包含Mutex等同步字段,用*T,避免因复制造成锁操作无效。
- 其他无法确定的情况,都用*T
接口interface
接口代表一种调用契约,是多个方法的集合。
超集接口变量可隐式的转化为子集。反过来不行。
type stringer interface {
string() string
}
type tester interface {
stringer
test()
}
type data strcut{}
func (*data) test() {}
func (data) string() string {return ""}
func pp(a stringer) {...}
func main() {
var d data
var t tester = &d
pp(t) //隐式转换为子集接口
var s stringer = t //超集转化为子集
}
类型推断
1)ok-idiom模式
//value为interface的值,Type为推断的类型
value, ok := x.(Type)
eg:
value,ok := x.(int)
2)switch模式
v是类型转换后的结果
switch v := x.(type) {
case int:
...
case nil :
...
}
Goroutine
垃圾回收
Go1.3 并发清理
Go1.5 三色标记
此处所说的并发是指垃圾回收和用户逻辑并发执行。
三次标记和写屏障
- 起初所有对象为白色。
- 扫描找出所有可达对象,标记为灰色,放入待处理队列。
- 从队列中提取出灰色对象,将其引用对象标记为灰色放入队列,自身标记为黑色。
- 写屏障监视对象内存修改,重新标色或放回队列。
当完成全部扫描和标记工作后,剩余的不是白色就是黑色,分别代表待回收和活跃对象,清理操作只需将白色内存回收即可。
make和new的区别
- new返回指针*T,make返回值Y
- new只分配内存,make用于slice,map或者chan初始化。
struct内存对齐
在分配内存时,字段必须做对齐处理,通常以所有字段中最长的基础类型宽度为标准。
比较特殊的是空结构类型字段(struct{})。如果他是最后一个字段,那么编译器将其当做长度为1的类型做对齐处理。以便其地址不会越界,避免引发垃圾回收错误。
如果仅有一个空结构字段,那么同样按1对齐,只不过长度为0,且指向runtime.zerobase变量。
有疑问加站长微信联系(非本文作者)