如何打造一个简易网站以吸引流量?
摘要:网站引流怎么做,自己怎么做简单的网站,低价网站建设推广优化,制作app连接网站关键词:函数式编程 闭包 匿名函数 匿名函数特别适合作为函数或方法的回调在Go中函数是一等公民,和string&am
网站引流怎么做,自己怎么做简单的网站,低价网站建设推广优化,制作app连接网站关键词: 函数式编程 闭包 匿名函数 匿名函数特别适合作为函数或方法的回调 在Go中函数是一等公民#xff0c;和string#xff0c;int等一样。 而在C、C 等不支持匿名函数的语言中#xff0c;函数不能在运行期创建 go 学习笔记之仅仅需要一个示例就能讲清楚什么闭包 闭包 与… 关键词: 函数式编程 闭包 匿名函数 匿名函数特别适合作为函数或方法的回调 在Go中函数是一等公民和stringint等一样。 而在C、C 等不支持匿名函数的语言中函数不能在运行期创建 go 学习笔记之仅仅需要一个示例就能讲清楚什么闭包 闭包 与 普通函数的区别 在(普通)函数里面定义一个内部函数(匿名函数)并且这个内部函数(匿名函数)用到了外面(普通)函数的变量那么将这个内部函数和用到的一些变量统称为闭包 在闭包中既有函数又有数据而且(其内部定义的)数据是闭包里面独有的数据与外界无影响 (普通)函数中需要使用的全局变量在一定程度上是受到限制的因为全局变量不仅仅是一个函数使用其他的函数也可能会使用到一旦修改会影响到其他函数使用全局变量所以全局变量不能随便修改从而在函数的使用中受到一定局限性 匿名函数和闭包的关系 简单来说匿名函数是指不需要定义函数名的一种函数实现方式。匿名函数是由一个不带函数名的函数声明和函数体组成。匿名函数的优越性在于可以直接使用函数内的变量不必声明一个子方法所以在某些场景下被广泛使用 关于闭包的定义存在以下广泛流传的公式闭包函数引用环境。函数指的是匿名函数引用环境指的是编译器发现闭包直接将闭包引用的外部变量在堆上分配空间当闭包引用了函数的内部变量即局部变量时每次调用的外部变量数据都会跟随闭包的变化而变化闭包函数和外部变量是共享的。显然闭包只能通过匿名函数实现可以把闭包看作是有状态的匿名函数反过来如果匿名函数引用了外部变量就形成了一个闭包 Go 函数式编程篇三匿名函数和闭包 一般来说一个函数返回另外一个函数这个被返回的函数可以引用外层函数的局部变量这形成了一个闭包。在Go中「闭包在实现上是一个结构体它存储了一个函数通常是其入口地址和一个关联的上下文环境相当于一个符号查找表」 type closure struct { F uintptr // 函数指针代表着内部匿名函数 x *int // 自由变量x代表着对外部环境的引用} Go函数闭包底层实现 在GoPHP中匿名函数可以认为就是闭包(Go 规范和 FAQ 都这么说了 )哪怕这个匿名函数没有入参没有引用外部的变量也没有任何返回值如 func(){ print(123) }() 严格来说这其实只是个匿名函数 不算闭包。 但Go里称其为闭包也ok即模糊了匿名函数和闭包的界限(有引用外部变量的匿名函数为闭包) 一道 Go 闭包题面试官说原来自己答错了面别人也涨知识 一些例子 无参数也无返回值的匿名函数 package mainimport ( fmt)func main() { f : func() { fmt.Println(不加括号就只是定义赋值给f可通过f()来调用) } f() fmt.Printf(变量f的类型为: %T\n, f) // func() // 下面这种方式定义只在此调用一次不如上面的方式可以随时复用 fmt.Println(--------------) func() { fmt.Println(而加上最后加上()就是直接调用(这种方式只能在此调用一次没法复用了)) }()} 输出: 不加括号就只是定义赋值给f可通过f()来调用变量f的类型为: func()--------------而加上最后加上()就是直接调用(这种方式只能在此调用一次没法复用了) 带参数的匿名函数 package mainimport ( fmt)func main() { i : 0 // 后面有()一次执行 func(i int) { fmt.Println(i 1) }(i) i -100000 // 赋值给add,可通过add()方式多次调用 add : func(k int) { fmt.Println(k 6) } add(200)} 输出: 1206 配合defer可以使问题非常复杂。也是高阶面试常问的~ 变形1 package mainimport ( fmt)func main() { i : 0 // 后面有()一次执行 defer func(i int) { fmt.Println(i 1) }(i) i -100000 // 赋值给add,可通过add()方式多次调用 add : func(k int) { fmt.Println(k 6) } add(200)} 输出: 2061 目前还好理解defer在return时执行(确切地说是在return和计算return值的中间执行) 变形2 package mainimport ( fmt)func main() { i : 0 // 后面有()一次执行 defer func(k int) { fmt.Println(i 1) }(i) i -100000 // 赋值给add,可通过add()方式多次调用 add : func(k int) { fmt.Println(k 6) } add(200)} 输出: 206-99999 如果有人说Go简单可以请其解释一下这个输出.. 有返回值的匿名函数 package mainimport fmtfunc main() { name : 张三 say : func(name string) string { return hello name } res : say(name) fmt.Println(res) //hello 张三} 当返回值是匿名函数 package mainimport fmtfunc main() { a : Fun() b : a(hello ) c : a(hello ) d : Fun() e : d(hello ) f : d(hello ) fmt.Println(b) //worldhello fmt.Println(c) //worldhello hello fmt.Println(e) //worldhello fmt.Println(f) //worldhello hello}func Fun() func(string) string { rs : world return func(args string) string { rs args return rs }} 等同于 package mainimport fmtfunc main() { cui : func() func(string) string { rs : world return func(args string) string { rs args return rs } } a : cui() b : a(hello ) c : a(hello ) d : cui() e : d(hello ) f : d(hello ) fmt.Println(b) //worldhello fmt.Println(c) //worldhello hello fmt.Println(e) //worldhello fmt.Println(f) //worldhello hello} 参考自 GO 匿名函数和闭包[1] 当参数是匿名函数 参考下方[回调函数闭包可以用作回调函数(例如在异步编程中可以捕获外部函数的上下文) 高阶函数闭包可以用作高阶函数的参数并在调用时返回新的函数将匿名函数作为函数参数可以让该函数执行多种不同逻辑]( 回调函数闭包可以用作回调函数(例如在异步编程中可以捕获外部函数的上下文) 高阶函数闭包可以用作高阶函数的参数并在调用时返回新的函数将匿名函数作为函数参数可以让该函数执行多种不同逻辑) 多个匿名函数 package mainimport fmtfunc main() { f1, f2 : F(1, 2) fmt.Println(f1(4)) //6 fmt.Println(f2()) //6}func F(x, y int) (func(int) int, func() int) { f1 : func(z int) int { return (x y) * z / 2 } f2 : func() int { return 2 * (x y) } return f1, f2} 常见使用场景 私有数据闭包可以捕获函数内部的数据并且对外部不可见。这是一种创建私有数据的方法保证局部变量的安全性 package mainimport fmtfunc main() { var j int 1 f : func() { var i int 1 // i 在闭包内部定义其值被隔离不能从外部修改 fmt.Printf(i, j: %d, %d\n, i, j) } f() j 2 f() // 对比下面的输出可见并不是调用时刻的值而只是记录变量的引用 defer f() j 10000} 输出: i, j: 1, 1i, j: 1, 3i, j: 1, 10003 package mainimport ( fmt)func main() { accumulator : SomeFunc() //使用accumulator变量接收一个闭包 // 累加计数并打印 fmt.Println(The first call CallNum is , accumulator()) //运行结果为The first call CallNum is 1 // 累加计数并打印 fmt.Println(The second call CallNum is , accumulator()) //运行结果为The second call CallNum is 2}func SomeFunc() func() int { // 创建一个函数返回一个闭包闭包每次调用函数会对函数内部变量进行累加 var CallNum 0 //函数调用次数系函数内部变量外部无法访问仅当函数被调用时进行累加 return func() int { // 返回一个闭包 CallNum //对value进行累加 //实现函数具体逻辑 return CallNum // 返回内部变量value的值 }} 输出: The first call CallNum is 1The second call CallNum is 2 通过闭包既没有暴露CallNum这个变量又实现了为函数计数的目的 回调函数闭包可以用作回调函数(例如在异步编程中可以捕获外部函数的上下文) 高阶函数闭包可以用作高阶函数的参数并在调用时返回新的函数将匿名函数作为函数参数可以让该函数执行多种不同逻辑 Go基础系列函数(2)——回调函数和闭包 [2] 参考自 【Go基础】搞懂函数回调和闭包[3] 回调函数就是一个通过函数指针调用的函数。如果你把函数的指针地址作为参数传递给另一个函数当这个指针被用来调用其所指向的函数时就说这是回调函数。回调函数不是由该函数的实现方直接调用而是在特定的事件或条件发生时由另外一方调用的用于对该事件或条件进行响应。日常开发中可以将函数B作为另一个函数A的参数可以使得函数A的通用性更强可随意定义函数B只要满足规则函数A都可以去处理这比较适合于回调函数。 下面看几个简单的例子来理解回调 package mainimport fmttype Callback func(x, y int) int// 提供一个接口让外部去实现func test1(x, y int, callback Callback) int { return callback(x, y)}// 回调函数的具体实现func calculationXOR(x, y int) int { return x ^ y}func calculationAND(x, y int) int { return x y}// 回调函数的具体实现func main() { fmt.Println(test1(2, 3, calculationXOR)) //这样调用test1就能实现异或 以及 与的运算 fmt.Println(test1(2, 3, calculationAND))} 12 再看个简单例子将字符串转为Int转换失败时执行回调函数输出错误信息 package mainimport ( fmt strconv)type Callback func(msg string)// 将字符串转换为int64如果转换失败调用Callbackfunc stringToInt(s string, callback Callback) int64 { if value, err : strconv.ParseInt(s, 0, 0); err ! nil { callback(err.Error()) return 0 } else { return value }}// 记录日志消息的具体实现func errLog(msg string) { fmt.Println(Convert error(转换发生了错误!): , msg)}func main() { fmt.Println(stringToInt(18, errLog)) fmt.Println(stringToInt(hh, errLog))} 输出: 18Convert error(转换发生了错误!): strconv.ParseInt: parsing hh: invalid syntax 下面这个例子和第一个类似: package mainimport fmtfunc main() { // 普通的加法操作 add1 : func(a, b int) int { return a b } // 定义另一种加法规则即 加数*10第二个加数 base : 10 add2 : func(a, b int) int { return a*base b } handleAdd(1, 2, add1) handleAdd(1, 2, add2)}// 将匿名函数作为参数func handleAdd(a, b int, call func(int, int) int) { fmt.Println(call(a, b))} 输出: 312 这样就可以通过一个函数执行多种不同加法实现算法提升代码的复用性 可以基于这个功能特性实现一些更复杂的业务逻辑如 Go 官方 net/http 包底层的路由处理器[4]也是这么实现的 // HandleFunc registers the handler function for the given pattern// in the DefaultServeMux.// The documentation for ServeMux explains how patterns are matched.func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler)} Go源码中还有非常多的将func作为参数的高阶函数参数的func即回调函数更多可参考 可通过关键字func(检索 延迟计算闭包可以延迟计算直到闭包被调用时才执行计算(将匿名函数作为函数返回值) package mainimport fmt// 将函数作为返回值func deferAdd(a, b int) func() int { return func() int { return a b }}func main() { // 此时返回的是匿名函数 addFunc : deferAdd(1, 2) // 这里才会真正执行加法操作 fmt.Println(addFunc()) // 3} Go函数闭包底层实现 易错问题 循环里打印出的都是最后一个值 case1 package mainimport fmtfunc main() { // 此时a是F()的返回值即一个[]func() //a : F() a : func() []func() { b : make([]func(), 3, 3) for i : 0; i 3; i { b[i] func() { fmt.Println(i, i) } } return b }() a[0]( 0) //0x140000200c8 3 a[1]( 1) //0x140000200c8 3 a[2]( 2) //0x140000200c8 3}//func F() []func() {// b : make([]func(), 3, 3)// for i : 0; i 3; i {// b[i] func() {// fmt.Println(i, i)// }// }// return b//} 解决办法: 每次复制变量 i 然后传到匿名函数中让闭包的环境变量不相同。 package mainimport fmtfunc main() { a : F() a[0]( 0) //0x14000128008 0 a[1]( 1) //0x14000128010 1 a[2]( 2) //0x14000128018 2}func F() []func() { b : make([]func(), 3, 3) for i : 0; i 3; i { b[i] (func(j int) func() { return func() { fmt.Println(j, j) } })(i) } return b}//或者//package main////import fmt////func main() {// a : F()// a[0]( 0) //0xc00004c080 0// a[1]( 1) //0xc00004c088 1// a[2]( 2) //0xc00004c090 2//}//func F() []func() {// b : make([]func(), 3, 3)// for i : 0; i 3; i {// j : i// b[i] func() {// fmt.Println(j, j)// }// }// return b//} 参考自 GO 匿名函数和闭包[5] package mainimport fmtfunc main() { // 保存函数闭包 var s []func() for _, v : range []string{a, b, c, d, e} { s append(s, func() { // 捕获v, 保存在闭包中 fmt.Printf(value: %v\n, v) }) } for _, f : range s { f() }} 输出: value: evalue: evalue: evalue: evalue: e 闭包中捕获的v不是值, 而是有地址的变量如GoLang闭包注意这里有蹊跷 中图1所示且创建闭包时循环变量的值已经被确定并与闭包关联。当闭包被调用时它使用捕获的值而不是当前值解决的关键就在于重新声明变量这样每个闭包都有自己的变量能够正确地访问其所需的值 case2for rangeGoroutine 使用闭包不当 package mainimport ( fmt time)func main() { tests1ice : []int{1, 2, 3, 4, 5} for _, v : range tests1ice { go func() { fmt.Println(v) }() } time.Sleep(2 * time.Second)} 55555 由于没有在Goroutine中对切片执行写操作所以首先排除了内存屏障的问题最终还是通过反编译查看汇编代码发现Goroutine打印的变量v其实是地址引用Goroutine执行的时候变量v所在地址所对应的值已经发生了变化汇编代码如下 for _, v : range tests1ice { 499224: 48 8d 05 f5 af 00 00 lea 0xaff5(%rip),%rax # 4a4220 type.*0xa220 49922b: 48 89 04 24 mov %rax,(%rsp) 49922f: e8 8c 3a f7 ff callq 40ccc0 runtime.newobject 499234: 48 8b 44 24 08 mov 0x8(%rsp),%rax 499239: 48 89 44 24 48 mov %rax,0x48(%rsp) 49923e: 31 c9 xor %ecx,%ecx 499240: eb 3e jmp 499280 main.main0xc0 499242: 48 89 4c 24 18 mov %rcx,0x18(%rsp) 499247: 48 8b 54 cc 20 mov 0x20(%rsp,%rcx,8),%rdx 49924c: 48 89 10 mov %rdx,(%rax) go func() { 49924f: c7 04 24 08 00 00 00 movl $0x8,(%rsp) 499256: 48 8d 15 f3 b7 02 00 lea 0x2b7f3(%rip),%rdx # 4c4a50 go.func.*0x6c 49925d: 48 89 54 24 08 mov %rdx,0x8(%rsp) 499262: 48 89 44 24 10 mov %rax,0x10(%rsp) 499267: e8 54 3a fa ff callq 43ccc0 runtime.newproc 解决方案一在参数方式向匿名函数传递值引用 package mainimport ( fmt time)func main() { tests1ice : []int{1, 2, 3, 4, 5} for _, v : range tests1ice { w : v go func(w int) { fmt.Println(w) }(w) } time.Sleep(time.Second)} 24513 解决方案二在调用gorouinte前将变量进行值拷贝 package mainimport ( fmt time)func main() { tests1ice : []int{1, 2, 3, 4, 5} for _, v : range tests1ice { w : v go func() { fmt.Println(w) }() } time.Sleep(time.Second)} 13254 Go的闭包看你犯错Rust却默默帮你排坑 另外的例子: package mainimport ( fmt time)func main() { s : []int{1, 2, 3} for _, v : range s { go func() { fmt.Println(v) // 输出结果3 3 3 }() } time.Sleep(1e9)} 无法得到预期结果123的原因是在没有将变量 v 的拷贝值传进匿名函数之前只能获取最后一次循环的值,是新手最容易遇到的坑之一。有效规避方式为每次将变量v的拷贝传进函数 package mainimport ( fmt time)func main() { s : []int{1, 2, 3} for _, v : range s { go func(v int) { fmt.Println(v) // 输出结果123或 132 或其他顺序 }(v) } time.Sleep(1e9)} 搭配defer使用往defer里传入一个闭包虽然是值传递但是拷贝的是函数指针可以解决一些使用defer会立刻拷贝函数中引用的外部参数引起的时机问题。 package mainimport fmtfunc main() { x, y : 1, 2 defer func(a int) { fmt.Printf(x:%d,y:%d\n, a, y) // y 为闭包引用最终结果为x1y102 }(x) // 复制 x 的值 x 100 y 100} 无法得到期待的结果x1y2的原因是defer 调用会在当前函数执行结束前才被执行这些调用被称为延迟调用而defer 中使用匿名函数是一个闭包y为闭包引用的外部变量会跟着闭包环境变化当延迟调用时y已经变成102所以最终输出的y也不再是2了。 有效规避方式只需要去掉defer即可 参考资料 [1] GO 匿名函数和闭包: https://segmentfault.com/a/1190000018689134 [2] Go基础系列函数(2)——回调函数和闭包 : https://www.cnblogs.com/f-ck-need-u/p/9878898.html [3] 【Go基础】搞懂函数回调和闭包: https://blog.csdn.net/dl962454/article/details/123460053 [4] 路由处理器: https://github.com/golang/go/blob/master/src/net/http/server.go#L2573 [5] GO 匿名函数和闭包: https://segmentfault.com/a/1190000018689134 本文由 mdnice 多平台发布
