Go 1.19.4 函數-Day 08

1. 函數概念和調用原理

1.1 基本介紹

函數是基本的代碼塊,用于執行一個任務。
Go 語言最少有個 main() 函數。
你可以通過函數來劃分不同功能,邏輯上每個函數執行的是指定的任務。
函數聲明告訴了編譯器函數的名稱,返回類型,和參數。
Go 語言標準庫提供了多種可動用的內置的函數。例如,len() 函數可以接受不同類型參數并返回該類型的長度。如果我們傳入的是字符串則返回字符串的長度,如果傳入的是數組,則返回數組中包含的元素個數。

簡單點理解,函數就是一個或者多個功能的集合體。

1.2 函數的作用

  1. 結構化編程對代碼的最基本的封裝,一般按照功能組織一段代碼
  2. 封裝的目的為了復用,減少冗余代碼
  3. 代碼更加簡潔美觀、可讀易懂

1.3 函數的分類

  1. 內建函數,如make、new、panic等
  2. 庫函數,如math.Ceil()等
  3. 自定義函數,使用func關鍵字定義

1.4 函數定義與調用

func 函數名(參數列表) [(返回值列表)]{函數體(代碼塊)[return 返回值]
}
這里[]表示其中的內容可有可無func main(){被調用函數()
}

函數名就是標識符,命名要求一樣
定義中的參數列表稱為形式參數,只是一種符號表達(標識符),簡稱形參
返回值列表可有可無,需要return語句配合,表示一個功能函數執行完返回的結果
函數名(參數列表) [(返回值列表)] 這部分稱為函數簽名
Go語言中形參也被稱為入參,返回值也被稱為出參

1.4.1 普通函數定義

package main// 函數聲明/定義(定義時)
func add(){Println("函數體,具體執行功能的代碼塊。")
}func main() {add()// 函數調用(調用時)
}

1.4.2 帶參數的函數定義

1.4.2.1 使用形參(入參)與實參
package mainimport "fmt"// x int, y int屬于形式參數(形參或入參)。還可以寫成x, y int
// add(x int, y int)屬于簽名
func add(x int, y int) {fmt.Println(x + y)
}func main() {// 函數調用時,把add函數所需形參傳過去,這種參數被稱為“實參”,還能成為“傳參”add(4, 5) // 注意參數的對應關系
}
==========調試結果==========
9

1.4.3 定義包含返回值的函數

調用時,如果有返回值,需要在調用時定義對應返回值個數的變量去接收。

package mainimport "fmt"// add(x int, y int) int,括號后的int表示返回值的類型和數量。
func add(x int, y int) int {fmt.Println("add函數的執行結果:", x+y)// 定義返回值為1return 1 // return之后的語句不會執行,函數將結束執行
}func main() {// add函數的返回值賦值給v變量v := add(4, 5) // go語言中,只能在有返回值的情況下,才能用變量接住,不然定義了會報錯。fmt.Println("add函數的返回值:", v)//fmt.Println(add(4, 5)) // 如果返回值只用一次那這樣也可以,等同于上面兩條。多次調用還是建議定義變量,使用起來方便
}
==========調試結果==========
add函數的執行結果: 9
add函數的返回值: 1

上述代碼執行過程:

  1. 系統從上到下加載代碼,加載到v := add(4, 5)時,先執行add(4, 5)。
  2. 在內存中找到add函數,并把4和5作為實參傳遞給add函數中的x和y。
  3. add函數開始執行fmt.Println(“add函數的執行結果:”, x+y),也就是把x+y的結果輸出到控制臺。
  4. add函數中的println函數執行完畢后,開始執行return 1,最終會返回一個1給到main函數中的v。
  5. 執行main中的print函數,輸出add函數的返回值。

1.5 函數調用原理

特別注意,函數定義只是告訴你有一個函數可以用,但這不是函數調用執行其代碼。至于函數什么時候被調用,不知道。一定要分清楚定義和調用的區別。
函數調用相當于運行一次函數定義好的代碼,函數本來就是為了復用,試想你可以用加法函數,我也可以用加法函數,你加你的,我加我的,應該互不干擾的使用函數。為了實現這個目標,函數調用的一般實現,都是把函數壓棧(LIFO),每一個函數調用都會在棧中分配專用的棧幀,本地變量、實參、返回值等數據都保存在這里。
一句不準確的口訣:函數的每一次調用都是獨立的,不相干的。—— wayne
上面的代碼,首先調用main函數,main壓棧,接著調用add(4, 5)時,add函數壓棧,壓在main的棧幀之上,add調用return,add棧幀消亡,回到main棧幀,將add返回值保存在main棧幀的本地變量out上。

2. 函數類型與返回值

2.1 函數類型

可以看出同一種簽名的函數是同一種類型

package mainimport "fmt"func fn1() {}func fn2(i int) int { return 100 }func fn3(j int) (r int) { return 200 }func main() {fmt.Printf("%T\n", fn1)fmt.Printf("%T\n", fn2)fmt.Printf("%T\n", fn3)
}
==========調試結果==========
func() // 沒有參數的函數,就是這樣的
func(int) int
func(int) int

2.2 函數返回值

函數返回值為局部變量。

2.2.1 無返回值函數

注意:在Go中,無返回值的函數不允許使用變量去接,會報錯。

package mainimport "fmt"func fn1() {fmt.Println("這是一個無返回值函數!")
}func main() {// a := fn1() // 由于沒有返回值,所以千萬不要用變量去接,會報錯fn1()// 無返回值,不需要傳參的函數直接調用即可。
}
==========調試結果==========
這是一個無返回值函數!

2.2.2 帶返回值的函數

2.2.2.1 返回一個值
package mainimport "fmt"func fn2() int { // 此處的int表示該函數有1個返回值return 100 // 返回一個值
}func main() {v := fn2() // 變量接收返回值fmt.Println("fn2函數的返回值為:", v)
}
==========調試結果==========
fn2函數的返回值為: 100
package mainimport "fmt"func fn3() int {var r = 100return r
}func main() {v := fn3()fmt.Println("fn3函數的返回值為:", v)
}
==========調試結果==========
fn3函數的返回值為: 100
package mainimport "fmt"func fn4() (r int) { // 直接在函數后面定義變量和類型也可以,r也相當于就是個占位符r = 300return r// return // 還可以這樣,return后面不接變量,由系統自動推斷到r int
}func main() {v := fn4()fmt.Println("fn4函數的返回值為:", v)
}
==========調試結果==========
fn4函數的返回值為: 300
package mainimport "fmt"func fn4() (r int) {t := 300return t //該方式相當于r = t
}func main() {v := fn4()fmt.Println("fn4函數的返回值為:", v)
}
==========調試結果==========
fn4函數的返回值為: 300
2.2.2.2 返回多個值
package mainimport "fmt"// go允許多返回值
func fn5() (int, int) { // (int, int),兩個int表示有兩個返回值a, b := 4, 50return a, b
}func main() {v1, v2 := fn5() // 注意:返回值變量要和返回值數量相對應fmt.Println("fn5函數的返回值為:", v1, v2)
}
==========調試結果==========
fn5函數的返回值為: 4 50
package mainimport "fmt"func fn6() (i int, j bool) {// return 100, false // 這樣可以i, j = 200, truereturn // 這樣也可以,相當于return i, j
}func main() {v1, v2 := fn6()fmt.Println("fn6函數的返回值為:", v1, v2)
}
==========調試結果==========
fn6函數的返回值為: 200 true

這里看一個特殊示例

package mainimport "fmt"func fn7() (i int, j bool) {// 這里的i j也是充當了出參占位符的角色,同時也是局部變量,僅在該函數中使用return
}func main() {v1, v2 := fn7()fmt.Println("fn7函數的返回值為:", v1, v2)
}
==========調試結果==========
fn7函數的返回值為: 0 false

從結果可以看出,不明確指定返回值的情況下,int默認為0,bool默認為false。
其實就是只聲明,但是不賦值,使用了對應數據類型的默認值。

再看一個非常特殊的示例
在這里插入圖片描述

為什么上圖return報錯了?
根本原因是return不知道把結果返回給誰!因為if f,err := xxx,這里相當于重新在if中聲明了一個f局部變量和err局部變量,但是fn8函數的出參也定義了err變量,由于存在兩個err變量,導致return不知道把結果返回給哪個err,所以報錯了。
解決辦法如下如圖:
在這里插入圖片描述

2.2.3 返回值總結

  • 實際工作中,還是更建議顯示的指定return 返回值,便于閱讀。
  • 可以返回0個或多個值。
  • 可以在函數定義中寫好返回值參數列表。
    ○ 可以沒有標識符,只寫類型。但是有時候不便于代碼閱讀,不知道返回參數的含義
    ○ 可以和形參一樣,寫標識符和類型來命名返回值變量,相鄰類型相同可以合并寫
    ○ 如果返回值參數列表中只有一個返回參數類型,小括號可以省略
    ○ 以上2種方式不能混用,也就是返回值參數要么都命名,要么都不要命名
  • return
    ○ return之后的語句不會執行,函數將結束執行
    ○ 如果函數無返回值,函數體內根據實際情況使用return
    ○ return后如果寫值,必須寫和返回值參數類型和個數一致的數據
    ○ return后什么都不寫那么就使用返回值參數列表中的返回參數的值,如果返回值參數沒有賦過值,就用零值

3. 函數的形參與可變參數

3.1 形參

  • 可以無形參,也可以多個形參
  • 不支持形式參數的默認值
  • 形參是局部變量

3.1.1 定義無標識符形參

無標識符形參不建議使用,因為沒辦法在函數中拿到這個值(實參)。

定義形參的目的是為了在函數中使用,這種在函數中無法使用的形參,定義了也是沒有意義的。

package mainimport "fmt"func fn1(int) { // 不建議這樣用,因為沒有辦法獲取傳進來的實參100fmt.Println("無標識符形參!")
}func main() {// 傳遞實參fn1(100)
}
==========調試結果==========
無標識符形參!

3.1.2 定義有標識符形參

推薦使用該方式。

package mainimport "fmt"func fn2(x, y int) {// 明確定義形參標識符后,就可以在函數體內部調用了fmt.Printf("有標識符形參:x=%v,y=%v", x, y)
}func main() {fn2(100, 200)
}
==========調試結果==========
有標識符形參:x=100,y=200
3.1.2.1 形參默認值示例

注意:Go語言不支持形參默認值,所以不要這樣定義。

package mainimport "fmt"// func config(a,b int, c string = "OK"){ // 這樣是不可以的
func config(a, b int, c string) {fmt.Println(a, b, c)
}func main() {// config(1,2)config(1, 2, "ok")
}
==========調試結果==========
1 2 ok

3.2 可變參數(name … type)

在 Go 語言中,可變參數是指函數可以接受任意數量的參數。
這通過在函數的參數列表中使用省略號 … 和參數類型來實現,最終可變參數收集實參到一個切片中,注意最終數據類型是切片
使用可變參數可以讓你的函數更加靈活,能夠處理不同數量的輸入。
注意:如果有可變參數,那它必須位于參數列表中最后。

3.2.1 定義可變參數函數

package mainimport "fmt"// ...表示任意數量的參數(0到n個),int為參數類型,nums為可變參數名稱。
func fn6(nums ...int) {fmt.Printf("可變參數nums的值:%d\n可變參數nums的類型:%[1]T\n可變參數nums的長度:%[2]d\n可變參數nums的容量:%[3]d\n", nums, len(nums), cap(nums))
}func main() {fn6()fn6(1, 3, 100)
}
==========調試結果==========
可變參數nums的值:[]
可變參數nums的類型:[]int
可變參數nums的長度:0
可變參數nums的容量:0
可變參數nums的值:[1 3 100]
可變參數nums的類型:[]int
可變參數nums的長度:3
可變參數nums的容量:3

3.2.2 切片分解(切片傳遞)

切片分解其實就是把傳入的切片的header復制給了新的切片。
切片分解不會導致底層數組擴容,因為復制的header。

3.2.2.1 示例一
package mainimport "fmt"func fn6(nums ...int) {fmt.Println(nums)fmt.Printf("%p %p\n", &nums, &nums[0])
}func main() {var p = []int{1, 3}fn6(p...)// 這里就相當于是header復制fmt.Printf("%p %p\n", &p, &p[0])
}
==========調試結果==========
[1 3]
0xc000008090 0xc0000180a0
0xc000008078 0xc0000180a0
3.2.2.2 示例二
func fn7(x, y int, nums ...int) {fmt.Printf("%d %d; %T %[3]v, %d, %d\n", x, y, nums, len(nums),
cap(nums))
}
p := []int{4, 5}
fn7(p...)          // 錯誤,不能用在普通參數上
fn7(1, p...)       // 錯誤,不能用在普通參數上
fn7(1, 2, 3, p...) // 錯誤,不能用2種方式為可變參數傳參,不能混用
// fn7(1, 2, p..., 9, 10) // 語法錯誤
// fn7(1, 2, []int{4, 5}..., []int{6, 7}...) // 語法錯誤,不能連續使用p...,只能一次
// 正確的如下
fn7(1, 2, []int{4, 5}...)
fn7(1, 2, p...)
fn7(1, 2, 3, 4, 5)

3.2.3 小練習:編寫一個函數,它可以接受任意數量的整數參數,并返回它們的總和。

package mainimport ("fmt"
)// 編寫一個函數 sum,它可以接受任意數量的整數參數,并返回它們的總和。
func Sum(nums ...int) int {a := 0for _, v := range nums {a += v}return a
}func main() {fmt.Println(Sum(10, 2, 10))
}
==========調試結果==========
22

4. 作用域

作用域實際上就是在說“標識符”的可見范圍,有點類似于全局變量和局部變量這種概念。
函數天然就是一個作用域,Go中的作用域主要如下:

  1. 語句塊作用域
    如if、for、switch等語句中使用短格式定義的變量,可以認為就是該語句塊的變量,作用域僅在該語句塊中。
  2. 顯示的塊作用域
    {xxx},這就是顯示的塊作用域。
  3. universe塊作用域
  4. 包塊作用域
  5. 函數塊作用域

4.1 語句塊作用域

package mainimport "fmt"func main() {t := []int{1, 2, 3, 4}for _, v := range t {fmt.Println(v)}// 語句塊外部引用變量v失敗,因為v的作用域只在for循環內部//fmt.Println(v)fmt.Println("------------------")var v = 1 // 這里再定義一個v也不會有沖突,因為兩個v的作用域不同fmt.Println(v)
}
==========調試結果==========
1
2
3
4
------------------
1

4.2 顯示的塊作用域

package mainimport "fmt"func main() {{const a = 100var b = 200c := 300fmt.Println(a,b,c)}// 這樣是不可以的,abc標識符只能在{}中生效。//fmt.Println(a,b,c)
}
==========調試結果==========
100 200 300

4.3 universe塊(宇宙塊)

宇宙塊,意思就是全局塊,不過是語言內建的(就是go系統的內置函數)。
預定義的標識符就在這個全局環境中,因此什么bool、int、nil、true、false、iota、append等標識符全局可見,隨處可用。

4.4 包塊作用域

所謂包塊作用域,就是說多份代碼文件都屬于同一個包,那么在main包中就可以調用其他包的變量或函數。
所有包內定義全局標識符,包內可見。包外需要大寫首字母導出,使用時也要加上包名。如fmt.Print
如下圖:
在這里插入圖片描述


在這里插入圖片描述

4.5 函數塊作用域

函數聲明的時候使用了花括號,所以整個函數體就是一個顯式代碼塊。這個函數就是一個塊作用域。

4.6 作用域綜合測試

package mainimport "fmt"var a = 100const b = 200//c := 300 // 錯誤,定義全局變量,不能使用短格式func main() {// 全局變量可以在函數體內部調用(向內穿透)a = 500fmt.Println("調用全局變量a:", a)// 由于作用域不同,所以a可以在函數體內部二次定義,此時的a為函數體內局部變量var a = 1000               // 重復定義不代表覆蓋,全局a和局部a是兩個完全獨立的個體fmt.Println("調用局部變量a:", a) //同時存在相同全局和局部變量時,優先采用就近原則fmt.Println("調用全局常量b:", b)const b = "abc"fmt.Println("調用局部常量b:", b)}
==========調試結果==========
調用全局變量a: 500
調用局部變量a: 1000
調用全局常量b: 200
調用局部常量b: abc

再來看個特殊例子

package mainimport "fmt"var a = 100func showA() int { // 看這里return a
}func main() {a = 500fmt.Println("調用全局變量a:", a)var a = 1000fmt.Println("調用局部變量a:", a)fmt.Println("return返回值:", showA()) // 看這里
}

看下上面showA函數最終的返回值是多少?
答案是500。
因為showA函數體內部是沒有a這個變量的,所以它只能向函數體外尋找,只能找到a這個全局變量,而在main函數中,全局變量a的結果已經被修改為500了,所以最終返回值為500。

5. 遞歸函數

什么是遞歸?
可以理解為在linux系統中的某個目錄下找某一個文件,會一層一層目錄去找,直到找到為止,這就是遞歸。

什么是遞歸函數?
有兩種遞歸方式:

  1. 直接在自己函數中調用自己。
  2. 間接在自己函數中調用的其他函數中調用了自己。可以理解為A調用B,B函數體中又調用了A。
    這種間接的遞歸非常危險,要盡量避免出現間接遞歸。
    并且不管是1或2這種遞歸調用,假設函數體內部沒有返回,那么每次調用都會生成一個“棧爭”,有點類似于疊盤子(可以稱為遞歸前進段),直到內存中分配的棧空間耗盡(盤子疊滿了),程序就崩潰了。
    正常應該是能疊就能收,收這個操作被稱為“遞歸返回段”。

注意:

  • 遞歸函數要有邊界條件(遞歸終止條件,防止無限遞歸)、遞歸前進段、遞歸返回段。
  • 遞歸函數必須有邊界條件(遞歸終止條件,防止無限遞歸)。
  • 當邊界條件不滿足時,遞歸前進。
  • 當邊界條件滿足時,遞歸返回。

5.1 斐波那契數列遞歸

5.1.1 版本一:普通循環實現

package mainimport "fmt"func fib1(n int) int {switch {// 如果小于0,說明傳參為負數case n < 0:panic("n is negative!!!")// 如果為0,就直接返回0,因為斐波那契數列的第一個數字就是0case n == 0:return 0// 如果是1或2,就直接返回1,因為0,1,1,……case n == 1 || n == 2:return 1}// 開始計算第三個數字a, b := 0, 1 // 先定義兩個初始值for i := 0; i < n-2; i++ { // n-2是因為上面已經輸出了2個數字(0和1)a, b = b, a+b}return b
}func main() {v := fib1(4) // 顯示單個斐波那契數列fmt.Println(v)
}
==========調試結果==========
2

5.1.2 版本二:遞歸實現

斐波那契數列:1,1,2,3,……
實現公式:F(n)=F(n-1)+F(n-2)。n-1就是前一個數字,n-2就是前面第二個數字
還是理解為:從第三個數開始往后,都是前兩數的和

package mainimport "fmt"func fib2(n int) int {// (2)判斷函數傳參值為1或2就直接返回1,相當于就是“邊界條件”。if n == 0 {panic("傳參不能為0")} else if n == 1 || n == 2 { // 此處的1和2表示的是第一個數和第二個數return 1}// (1)遞歸調用return fib2(n-1) + fib2(n-2)
}func main() {v := fib2(6)fmt.Println(v)
}
==========調試結果==========
8

執行過程解釋

  1. return fib2(n-1) + fib2(n-2)
    假設我傳參為3,也就是fib2(3),那么fib2(n-1) + fib2(n-2)就變成了fib2(3-1) + fib2(3-2)=return fib2(2) + fib2(1),這就形成了遞推公式。
    但是在遞歸調用中,必須有前進段和返回段,也就是必須要加邊界條件防止無限遞歸。
    這里如果不加邊界條件,那么3就會分裂成2和1,2分裂成1和0,1分裂成0和-1,就這么無限分裂下去且沒有返回階段。
  2. 定義邊界條件,if判斷。
    還是假定傳參為fib2(3),那么return fib2(2) + fib2(1)=fib2(3),這么看著是不是不對。
    實際的執行過程是先把fib2(3-1)=2帶入到形參處落棧,如下圖:
    加法先執行左邊。
    在這里插入圖片描述> 然后再把fib2(3-2)=1帶入到形參處落棧,入下圖:在這里插入圖片描述
    但是在這個過程中,單單n-2就能無限次分裂了,所以必須設置邊界條件來阻止無限遞歸。
    在這里插入圖片描述
    else if n == 1 || n == 2 {return 1},有了這個判斷,就阻止了遞歸調用的無限調用,同時,還返回了值給了fib2(3),這個1相當于被它進行了暫計,等到計算fib2(n-2)時,也會把結果暫計下來,最后運行return 1 + 1,并把最終返回值返回給main函數,到此整個函數調用結束。

5.1.3 版本三:循環改調用實現

就是把版本一里面你的循環,改成函數遞歸調用。
循環的次數等于遞歸調用的次數。

package mainimport "fmt"func fib3(n, a, b int) int {if n < 3 {return b}return fib3(n-1, b, a+b)
}func main() {v := fib3(10, 1, 1)fmt.Println(v)
}
==========調試結果==========
55

5.1.4 三種方式效率對比

上面三種方式,效率最高的是fib1、其次是fib3,效率最差的是fib2。
為什么fib2效率最差?主要就是因為這個公式fib2(n-1) + fib2(n-2),看下圖:在這里插入圖片描述
稍微傳參一個大一點的數字,函數體內部會存在大量的重復計算,嚴重浪費時間和資源。
不過也有解決辦法,就是把fib分裂出來的結果先到map中查詢一次,沒有匹配到的就把結果存到map中,一旦匹配到的結果,就說明之前肯定已經計算過了,就不用重復計算了。
唯一的缺點可能就是需要多消耗一點內存,但是運行速度變快了。

5.2 間接遞歸

func foo() {bar()
}
func bar() {foo()
}
foo()

就像上面的代碼,foo中調用bar,bar中調用foo。
不推薦這么玩,特別是復雜代碼,出了問題非常不利于排查。

5.3 遞歸總結

能不用就不用

  • 遞歸是一種很自然的表達,符合邏輯思維
  • 遞歸相對運行效率低,每一次調用函數都要開辟棧幀
  • 遞歸有深度限制,如果遞歸層次太深,函數連續壓棧,棧內存就溢出了
  • 如果是有限次數的遞歸,可以使用遞歸調用,或者使用循環代替,循環代碼稍微復雜一些,但是只
  • 要不是死循環,可以多次迭代直至算出結果
  • 絕大多數遞歸,都可以使用循環實現
  • 即使遞歸代碼很簡潔,但是能不用則不用遞歸

6. 匿名函數

6.1 什么是匿名函數

匿名函數是Go語言中的一種特殊函數,它沒有函數名,通常用于快速定義一個功能,然后立即使用它。它們可以作為參數傳遞給其他函數,或者存儲在變量中,以便稍后使用。

6.2 為什么要用匿名函數

在Go語言中,匿名函數是一種沒有名稱的函數,它們在某些情況下非常有用,比如當需要一個簡單的功能,但又不想為此創建一個完整的函數定義時。
調用的話,由于沒有名字,所以只能選擇立即調用或者賦值給一個標識符。
主要使用場景是用作高階函數中,是傳入的邏輯,函數允許傳入參數,就是把邏輯外置。
所謂高階函數就是返回值或形參是一個函數,兩者滿足其一皆為高階函數。
如下例子就會演示這種場景。

6.3 定義匿名函數

6.3.1 方式一:純匿名函數

package mainimport "fmt"func main() {// 定義匿名函數并調用,但是只能使用一次,因為它沒有標識符。v := func(x, y int) int {return x + y}(4, 5)fmt.Println(v)
}
==========調試結果==========
9

6.3.2 方式二:匿名函數加高階函數

package mainimport "fmt"// fn func(x,y int) int,這一步就屬于邏輯外移了
// 所謂邏輯外移,就是calc函數本身并不管運算邏輯如何實現,而是由fn函數把運算邏輯作為參數傳遞進來
func calc(a, b int, fn func(x, y int) int) int {// fn是高階函數// 這個r對應的是fn func(x,y int) int中最后的這個intr := fn(a, b)// 這個r對應的是這個int {return r
}func minus(x, y int) int {return x - y
}func main() {// fn函數的運算邏輯,在這里作為實參傳入{return x + y}fmt.Println(calc(4, 5, func(x, y int) int { return x + y }))fmt.Println(calc(4, 5, func(x, y int) int { return x * y }))fmt.Println(calc(4, 5, minus))// 注意minus不要寫成minus()
}
==========調試結果==========
9
20
-1

7. 函數嵌套

package mainimport "fmt"func outer() {c := 99var inner = func() {fmt.Println("1 inner c=", c)}inner()fmt.Println("2 outer c=", c)
}func main() {outer()
}
==========調試結果==========
1 inner c= 99
2 outer c= 99

可以看到outer中定義了另外一個函數inner,并且調用了inner。outer是包級變量,main可見,可以調用。而inner是outer中的局部變量,outer中可見。

8. 閉包

自由變量: 未在本地作用域中定義的變量。例如定義在內層函數外的外層函數的作用域中的變量。
閉包: 就是一個概念,出現在嵌套函數中,指的是內層函數引用到了外層函數的自由變量(局部變量),就形成了閉包。閉包是運行期動態的概念(只有在運行期間才會有閉包)。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/44946.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/44946.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/44946.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

STM32 - PWR 筆記

PWR&#xff08;Power Control&#xff09;電源控制 PWR 負責管理 STM32 內部的電源供電部分&#xff0c;可以實現 可編程電壓監測器 和 低功耗模式 的功能 可編程電壓監測器&#xff08;PVD&#xff09;可以監控VDD電源電壓&#xff0c;當VDD下降到PVD閥值以下或上升到PVD…

usbserver工程師手記(三)手工開通 OTP功能

1、設定密鑰&#xff0c;用戶自行選擇一個密鑰&#xff0c;以下以密鑰為 EAZAYOKNGETBOPC5 為例說明 2、usb server 配置otp 密鑰&#xff0c;目前還沒有UI 界面開通&#xff0c;后續版本會支持從管理界面開通 curl -X POST -H Content-Type: application/json -H Accept: app…

關于transformers庫驗證時不進入compute_metrics方法的一些坑

生成式任務輸入就是標簽 transformers在進入compute_metrics前會有一個判斷&#xff0c;源碼如下&#xff1a; # 版本 transformers4.41.2 # 在trainer.py 的 3842 行 # Metrics! if (self.compute_metrics is not Noneand all_preds is not Noneand all_labels is not Nonea…

Centos7下zabbix安裝與部署

Centos7下zabbix安裝與部署 一、Zabbix介紹 1、zabbix是一個基于WEB界面的提供分布式系統監視以及網絡監視功能的企業級的開源解決方案 2、zabbix能監視各種網絡參數&#xff0c;保證服務器系統的安全運營&#xff1b;并提供靈活的通知機制以讓系統管理員快速定位/解決存在的各…

活動策劃秘籍:如何讓企業活動引爆市場?

作為一個活動策劃&#xff0c;我的經驗是&#xff0c;活動策劃是一場精心編排的交響樂&#xff0c;每一個音符都要恰到好處。 想要做好企業活動策劃工作的關鍵在于綜合考慮多個方面&#xff0c;并確保每個環節的順暢執行。 以下是7個關鍵要素&#xff0c;只要用心體會&#x…

學習小記-使用Redis的令牌桶算法實現分布式限流

在介紹令牌桶算法前先介紹一下漏桶算法&#xff08;Leaky Bucket&#xff09; 漏桶算法&#xff08;Leaky Bucket&#xff09; 漏桶算法是一種固定容量的容器模型&#xff0c;它通過控制數據流入和流出的速度來限制數據的傳輸速率。漏桶算法的主要特點包括&#xff1a; 固定…

鴻蒙開發:Universal Keystore Kit(密鑰管理服務)【密鑰派生(C/C++)】

密鑰派生(C/C) 以HKDF256密鑰為例&#xff0c;完成密鑰派生。具體的場景介紹及支持的算法規格&#xff0c;請參考[密鑰生成支持的算法]。 在CMake腳本中鏈接相關動態庫 target_link_libraries(entry PUBLIC libhuks_ndk.z.so)開發步驟 生成密鑰 指定密鑰別名。 初始化密鑰屬…

通過電壓差判定無源晶振是否起振正確嗎?

在電子工程中&#xff0c;無源晶振作為許多數字電路的基礎組件&#xff0c;其是否成功起振對于系統的正常運行至關重要。然而&#xff0c;通過簡單檢測晶振兩端的電壓差來判斷晶振是否工作&#xff0c;這一方法存在一定的誤區&#xff0c;晶發電子將深入探討這一話題&#xff0…

2008年下半年軟件設計師【下午題】真題及答案

文章目錄 2008年下半年軟件設計師下午題--真題2008年下半年軟件設計師下午題--答案 2008年下半年軟件設計師下午題–真題 2008年下半年軟件設計師下午題–答案

四川赤橙宏海商務信息咨詢有限公司抖音電商服務靠譜嗎?

在數字化浪潮席卷全球的今天&#xff0c;電商行業蓬勃發展&#xff0c;各種新興電商平臺層出不窮。其中&#xff0c;抖音電商以其獨特的社交屬性和龐大的用戶基礎&#xff0c;迅速崛起為行業新星。四川赤橙宏海商務信息咨詢有限公司&#xff0c;作為專注于抖音電商服務的佼佼者…

個人怎么交易現貨黃金:加速形態

我們作為普通個人&#xff0c;在現貨黃金市場中交易就需要掌握相應的現貨黃金投資技巧。下面我們就來介紹一個&#xff0c;個人怎么交易現貨黃金的形態——加速形態。 加速形態是用于判斷市場趨勢力竭的情況&#xff0c;這種趨勢可以是上升&#xff0c;也可以是下跌。但是要注意…

用Qwt進行圖表和數據可視化開發

目錄 Qwt介紹 示例應用場景 典型QWT開發流程 舉一些Qwt的例子&#xff0c;多繪制幾種類型的圖像 1. 繪制折線圖 (Line Plot) 2. 繪制散點圖 (Scatter Plot) 3. 繪制柱狀圖 (Bar Plot) 4. 繪制直方圖 (Histogram) Qwt介紹 QWT開發主要涉及使用QWT庫進行圖表和數據可視化…

晉升業內新寵兒,MoE模型給了AI行業兩條關鍵出路

文 | 智能相對論 作者 | 陳泊丞 今年以來&#xff0c;MoE模型成了AI行業的新寵兒。 一方面&#xff0c;越來越多的廠商在自家的閉源模型上采用了MoE架構。在海外&#xff0c;OpenAI的GPT-4、谷歌的Gemini、Mistral AI的Mistral、xAI的Grok-1等主流大模型都采用了MoE架構。 …

第三方配件也能適配蘋果了,iOS 18與iPadOS 18將支持快速配對

蘋果公司以其對用戶體驗的不懈追求和對創新技術的不斷探索而聞名。隨著iOS 18和iPadOS 18的發布&#xff0c;蘋果再次證明了其在移動操作系統領域的領先地位。 最新系統版本中的一項引人注目的功能&#xff0c;便是對藍牙和Wi-Fi配件的配對方式進行了重大改進&#xff0c;不僅…

python如何計算兩個時間相差多少秒鐘,分鐘,小時,天,月,年

使用場景&#xff1a;在做上課記錄系統的時候&#xff0c;有上課開始時間和上課結束時間&#xff0c;需要計算這兩個時間的插值&#xff0c;以分鐘為單位。 封裝方法如下&#xff1a; from datetime import datetimedef sub_seconds(date1: str "2024-07-11 12:33:33&q…

【CORS 報錯】跨域請求問題:CORS 多種環境下的解決方案

&#x1f525; 個人主頁&#xff1a;空白詩 文章目錄 一、CORS錯誤的常見原因二、解決方案1. Vue3 Vite項目下的解決方案創建Vue3 Vite項目配置Vite的代理發送請求 2. jQuery項目下的解決方案使用CORS請求頭使用JSONP 3. 其他環境下的解決方案使用服務器端代理設置CORS頭使用…

PS拉框選擇工具

Photoshop&#xff08;PS&#xff09;中的拉框選擇工具&#xff0c;也稱為選框工具&#xff0c;是圖像處理中非常基礎且強大的工具之一。它允許用戶通過繪制矩形、橢圓形以及單行、單列的選擇框來選定圖像中的特定區域。本教程將詳細介紹選框工具的使用方法、技巧及其屬性設置。…

嵌入式Qt開發C++核心編程知識萬字總結

C核心編程 文章目錄 C核心編程1、程序的內存模型2、函數高級1.函數的默認參數2.函數的占位參數3.函數重載1.基本語法2.注意事項 3、類和對象1.類1.類的組成2.類的訪問權限3.class和struct的區別 2.構造函數&#xff08;Constructor&#xff09;1.示例2.特點 3.析構函數&#xf…

前端vue3 登錄頁面 響應式開發

一個登錄頁面 我直接上代碼了 結構是這樣的 Login 頁面 <template><a-layout class"login-box"><a-layout-content class"login-content"><a-row align"middle" justify"center" class"login-content-ma…

蝙蝠避障:為盲人出行插上科技的翅膀

在這個五彩斑斕的世界里&#xff0c;每一步都充滿了探索與驚喜。但對于我這樣的視障者來說&#xff0c;每一次出行都是一場未知的冒險。我時常面臨著難以想象的挑戰&#xff1a;如何安全地穿越繁忙的街道&#xff0c;怎樣準確地識別前方的障礙物&#xff0c;乃至簡單地找到回家…