大部分编程语言使用固定大小的函数调用栈,常见的大小从64KB到2MB不等。
固定大小栈会限制 递归的深度,当你用递归处理大量数据时,需要避免栈溢出; 除此之外,还会导致安全性问题。
与 相反,Go语言使用可变栈,栈的大小按需增加(初始时很小)。
这使得我们使用递归时不必考虑溢出 和安全问题
// squares返回一个匿名函数。
// 该匿名函数每次被调用时都会返回下一个数的平方。
func squares() func() int {
var x int
return func() int {
x++
return x * x
}
}
func main() {
f := squares()
fmt.Println(f())
fmt.Println(f())
fmt.Println(f())
fmt.Println(f())
// "1"
// "4"
// "9"
// "16"
}
函数squares返回另一个类型为 func() int 的函数。对squares的一次调用会生成一个局部变量x并返 回一个匿名函数。
每次调用时匿名函数时,该函数都会先使x的值加1,再返回x的平方。第二次调 用squares时,会生成第二个x变量,
并返回一个新的匿名函数。新匿名函数操作的是第二个x变 量。
squares的例子证明,函数值不仅仅是一串代码,还记录了状态。在squares中定义的匿名内部函数
可以访问和更新squares中的局部变量,这意味着匿名函数和squares中,存在变量引用。
这就是函 数值属于引用类型和函数值不可比较的原因。Go使用闭包(closures)技术实现函数值,
Go程序 员也把函数值叫做闭包。
通过这个例子,我们看到变量的生命周期不由它的作用域决定:squares返回后,变量x仍然隐式的 存在于f中。
某些致命错误会导致Go在运行时终止程序,如内存不足 ,这种情况没法恢复。
在函数声明时,在其名字之前放上一个变量,即是一个方法。这个附加的参数会将该函数附加到这 种类型上,即相当于为这种类型定义了一个独占的方法。
接口值可以使用==和!=来进行比较。两个接口值相等仅当它们都是nil值或者它们的动态类型相 同并且动态值也根据这个动态类型的==操作相等。因为接口值是可比较的,所以它们可以用在 map的键或者作为switch语句的操作数
然而,如果两个接口值的动态类型相同,但是这个动态类型是不可比较的(比如切片),将它们进 行比较就会失败并且panic:
⚠️:什么时候可以比较,什么时候不可以比较呢?
可比较类型(如基本类型和指 针),完全不可比较的类型(如切片,映射类型,和函数),但是在比较接口值或者包含了接 口值的聚合类型时,我们必须要意识到潜在的panic。
#############################分割线###############################################
func CountWordsAndImages(url string) (words, images int, err error) {
resp, err := http.Get(url) if err != nil {
return
}
doc, err := html.Parse(resp.Body) resp.Body.Close()
if err != nil {
err = fmt.Errorf("parsing HTML: %s", err) return
}
words, images = countWordsAndImages(doc) return
}
func countWordsAndImages(n *html.Node) (words, images int) { /* ... */ }
Q:看上面的伪代码,会发现一个问题,明明是有返回值的函数,为什么直接return。这样表达是否有错误?
A:如果一个函数将所有的返回值都显示的变量名,那么该函数的return语句可以省略操作数。这称之 为bare return。 按照返回值列表的次序,返回所有的返回值,在上面的例子中,每一个return语句等价于:
return words, images, err
⚠️注意: 当一个函数有多处return语句以及许多返回值时,bare return 可以减少代码的重复,但是使得代码 难以被理解。举个例子,如果你没有仔细的审查代码,很难发现前2处return等价于 return 0,0,err(Go会将返回值 words和images在函数体的开始处,根据它们的类型,将其初始化为0), 最后一处return等价于 return words,image,nil。基于以上原因,不宜过度使用bare return。
假设这个错误的伪代码:
var rmdirs []func()
for _, dir := range tempDirs() {
os.MkdirAll(dir, 0755)
rmdirs = append(rmdirs, func() {
os.RemoveAll(dir) // NOTE: incorrect! })
}
//上面的代码哪里错了呢?能找出来么?与下面的代码有那些区别呢
var rmdirs []func()
for _, d := range tempDirs() {
dir := d // NOTE: necessary!
os.MkdirAll(dir, 0755) // creates parent directories too
rmdirs = append(rmdirs, func() {
os.RemoveAll(dir) })
}
Q:为什么要在循环体中用循环变量d赋值一个新的局部变量,而不是直接使用循环变量dir
A: 问题的原因在于循环变量的作用域。在上面的程序中,for循环语句引入了新的词法块,循环变量 dir在这个词法块中被声明。在该循环中生成的所有函数值都共享相同的循环变量。需要注意,函数 值中记录的是循环变量的内存地址,而不是循环变量某一时刻的值。以dir为例,后续的迭代会不断 更新dir的值,当删除操作执行时,for循环已完成,dir中存储的值等于最后一次迭代的值。这意味 着,每次对os.RemoveAll的调用删除的都是相同的目录。 通常,为了解决这个问题,我们会引入一个与循环变量同名的局部变量,作为循环变量的副本
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。