strings.Map

  • 定义:func Map(mapping func(rune) rune, s string) string
  • 作用: 对字符串的每一个字符进行修改,替换或删除,生成一个新的字符串

主要作用取决于mapping这个函数的返回值,如果返回一个新的合法字符,那么就会替换,如果返回原来的字符就不变,如果返回-1就删除这个字符

  • 为什么要使用strings.Map?自己使用for循环不也能做到这些吗
    1. 可以处理UTF-8字符,go的字符串的底层是[]byte,如果直接使用循环遍历,处理中文会乱码。strings.Map会自动把字符串解码为Runes,适合处理中文等
    2. 更高效,strings.Map内部使用了strings.Builder,相比于循环拼接时更高效
    3. 使用函数值,我们可以将遍历结点的逻辑和操作结点的逻辑分离,使得我们可以复用遍历的逻辑,从而对结点进行不同的操作。

匿名函数

函数squares返回另一个类型为 func() int 的函数。对squares的一次调用会生成一个局部变量x并返回一个匿名函数。每次调用匿名函数时,该函数都会先使x的值加1,再返回x的平方。第二次调用squares时,会生成第二个x变量,并返回一个新的匿名函数。新匿名函数操作的是第二个x变量。

squares的例子证明,函数值不仅仅是一串代码,还记录了状态。在squares中定义的匿名内部函数可以访问和更新squares中的局部变量,这意味着匿名函数和squares中,存在变量引用。这就是函数值属于引用类型和函数值不可比较的原因。Go使用闭包(closures)技术实现函数值,Go程序员也把函数值叫做闭包。

通过这个例子,我们看到变量的生命周期不由它的作用域决定:squares返回后,变量x仍然隐式的存在于f中。

闭包对循环变量的捕获

这是go1.22前的行为,在1.22之后,每次循环迭代,循环变量都会重新创建了,这个特性可以忽略了

  • 当你在 for 循环里创建一个函数(闭包),并且这个函数里用到了循环变量(比如 dir),这个函数记下的不是当时那个 dir 的值,而是 dir 这个变量本身的地址。
1
2
3
4
5
6
7
8
9
10
11
12
13
var rmdirs []func()
for _, d := range tempDirs() {
//go的变量遮蔽,使用外部循环变量的值初始化,这样闭包捕获的就是这个内部的新变量,而不是一直在变得循环变量
dir := d // NOTE: necessary!
os.MkdirAll(dir, 0755) // creates parent directories too
rmdirs = append(rmdirs, func() {
os.RemoveAll(dir)
})
}
// ...do some work…
for _, rmdir := range rmdirs {
rmdir() // clean up
}

为什么使用d赋值一个新的变量,而不是使用下面这样

1
2
3
4
5
6
7
var rmdirs []func()
for _, dir := range tempDirs() {
os.MkdirAll(dir, 0755)
rmdirs = append(rmdirs, func() {
os.RemoveAll(dir) // NOTE: incorrect!
})
}

defer

  • defer 关键字只负责延迟“最外层”的那个函数调用,而为了拿到那个被延迟执行的函数,Go 必须先“立即”执行前面的代码
    defer trace("big")()实际等同于
1
2
returnedFunc := trace("big")
defer returnedFunc()