[TOC]
## 引入
`foo` 函數功能已經開發好了,并且運行一切正常。現在有個小需求就是要統計該函數被調用的次數。
```go
package main
import fmt
var count int
func foo() {
fmt.Println("foo函數功能")
count++
}
func main() {
foo()
foo()
fmt.Printf("foo函數被調用的次數: %d", count)
}
// 運行結果:
// foo函數功能
// foo函數功能
// foo函數被調用的次數: 2
```
以上的代碼可以實現到需求,但是不是很完美。原因其一,添加全局變量,會帶來的風險是污染全局變量。其二,改動原來正常運行的函數。
基于以上兩個原因,可以改進成閉包函數。
## 閉包函數
閉包的作用:
- 可以讓一個變量常駐內存
- 可以讓一個變量不污染全局
閉包可以理解成 “定義在一個函數內部的函數,內部函數(匿名函數)引用非自己作用域的變量”。閉包是匿名函數的應用。
- 閉包是指有權訪問另一個函數作用域中的變量的函數
- 創建閉包的常見的方式就是在一個函數內部創建另一個函數,通過另一個函數訪問這個函數的局部變量
>[warning] 注意:由于閉包里作用域返回的局部變量資源不會被立刻銷毀,所以可能會占用更多的內存,過度使用閉包會導致性能下降,建議在非常有必要的時候才使用閉包。
以下代碼解決上述說到的第一個問題(污染全局變量)。
```go
package main
import fmt
func foo() func() int8 {
var count int8
return func() int8 {
fmt.Println("foo函數功能")
count++
return count
}
}
func main() {
f := foo()
f()
count := f()
fmt.Printf("foo函數被調用的次數: %d\n", count)
}
// 運行結果:
// foo函數功能
// foo函數功能
// foo函數被調用的次數: 2
```
>[info] 以上代碼,foo函數不是閉包函數。而 `f` 函數則是閉包函數。其中 `f` 函數體在 foo 函數的匿名函數函數體。函數 `f` 調用 foo 函數的 count 變量。即是**閉包函數**。
以下代碼是通過上面改良版本,解決修改原有函數體內容。該函數可以統計相同類型的函數調用次數
```go
package main
import fmt
func foo() {
fmt.Println("foo函數功能")
}
func bar() {
fmt.Println("bar函數功能")
}
func funcCounter(f func()) func() int8 {
var count int8
return func() int8 {
f()
count++
return count
}
}
func main() {
foo := funcCounter(foo)
foo()
fooCount := foo()
fmt.Printf("foo函數被調用的次數: %d\n", fooCount)
bar := funcCounter(bar)
barCount := bar()
fmt.Printf("bar函數被調用的次數: %d\n", barCount)
}
// 運行結果:
// foo函數功能
// foo函數功能
// foo函數被調用的次數: 2
// bar函數功能
// bar函數被調用的次數: 1
```
>[info] 以上代碼,main 函數的賦值 foo 、 bar 函數是閉包函數,兩個函數體在 funcCounter 的匿名函數體里面。且匿名函數調用了 funcCounter 函數的 count 變量。即調用 funcCounter 函數,返回的函數即為閉包函數。 funcCounter 函數叫做裝飾器函數。
由此可得,裝飾器函數是閉包函數的應用。
## 閉包相關示例
示例1
```go
/* 計數器(1)
調用一次,則累加一。
*/
func counter() func() {
var i int
return func() {
i++
fmt.Printf("i: %v\n", i)
}
}
func main() {
f1 := counter()
f1()
f1()
f1()
}
// 運行結果:
// i: 1
// i: 2
// i: 3
```
示例2
```go
/* 自定義初始化值累加器
調用一次,則累加一。
*/
func counterInitValue(i int) func() int {
return func() int {
i++
return i
}
}
func main() {
i := 5
f2 := counterInitValue(i)
i = f2()
i = f2()
i = f2()
fmt.Printf("f2-> i: %v\n", i)
}
// 運行結果:
// f2-> i: 8
```
示例3
```go
/* 文件擴展名
判斷文件是否為指定擴展名。如果不是的話添加,否則直接返回文件名。
*/
func fileExtension(suffix string) func(string) string {
return func(name string) string {
if strings.HasSuffix(name, suffix) {
return name
} else {
return name + suffix
}
}
}
func main() {
f3 := fileExtension(".json")
filename1 := f3("test")
filename2 := f3("config.json")
fmt.Printf("filename: %s, filename: %s\n", filename1, filename2)
}
// 運行結果:
// filename: test.json, filename: config.json
```
示例4
```go
/* 計算器
兩數相加或相減函數
*/
func calc(x int, y int) (func() int, func() int) {
add := func() int {
return x + y
}
sub := func() int {
return x - y
}
return add, sub
}
func main() {
add, sub := calc(10, 5)
fmt.Printf("add(): %v\n", add())
fmt.Printf("sub(): %v\n", sub())
}
// 運行結果:
// add(): 15
// sub(): 5
```
- Golang簡介
- 開發環境
- Golang安裝
- 編輯器及快捷鍵
- vscode插件
- 第一個程序
- 基礎數據類型
- 變量及匿名變量
- 常量與iota
- 整型與浮點型
- 復數與布爾值
- 字符串
- 運算符
- 算術運算符
- 關系運算符
- 邏輯運算符
- 位運算符
- 賦值運算符
- 流程控制語句
- 獲取用戶輸入
- if分支語句
- for循環語句
- switch語句
- break_continue_goto語法
- 高階數據類型
- pointer指針
- array數組
- slice切片
- slice切片擴展
- map映射
- 函數
- 函數定義和調用
- 函數參數
- 函數返回值
- 作用域
- 函數形參傳遞
- 匿名函數
- 高階函數
- 閉包
- defer語句
- 內置函數
- fmt
- strconv
- strings
- time
- os
- io
- 文件操作
- 編碼
- 字符與字節
- 字符串
- 讀寫文件
- 結構體
- 類型別名和自定義類型
- 結構體聲明
- 結構體實例化
- 模擬構造函數
- 方法接收器
- 匿名字段
- 嵌套與繼承
- 序列化
- 接口
- 接口類型
- 值接收者和指針接收者
- 類型與接口對應關系
- 空接口
- 接口值
- 類型斷言
- 并發編程
- 基本概念
- goroutine
- channel
- select
- 并發安全
- 練習題
- 第三方庫
- Survey
- cobra