Go語言學習筆記04--特殊函式&工程化結構&陣列&隨機數
1.匿名函式 go語言中的函式都是宣告在函式之外的,並不存在函式內宣告函式的問題 但是也會存在一些特殊情況,在這寫情況中允許在函式內部去再次定義一個函式。 這種情況下,在函式內部定義的函式就必須遵守一些go語言定義的特殊規則。 而這些內部的函式,被統稱為:匿名函式。 func main (){ func (..){..} } (1)對於go語言中的匿名函式而言,由於其不存在函式名,無法使用傳統函式的呼叫功能 所以如何呼叫匿名函式就必須從兩個角度出發來解決問題。 1)利用函式指標,完成匿名函式的"重新命名",然後再次呼叫 func main(){ fpointer := func (num int)int{ return num+10; } fmt.Println(fpointer(100)); } 利用函式指標的情況時需要注意,由於函式指標是在函式內部被定義的 所以函式指標其實是一個區域性變數,因此函式指標是隻能夠在當前所在的函式內使用的 一旦超出當前所在函式範圍,即宣告函式指標失效。 2)利用AIIFE,即Anonymous-Immediately-Invoked-Function-Expression 匿名立即自動執行的函式表示式,來完成匿名函式的呼叫 func main(){ func (num int){ fmt.Println(num+100); }(35); } 這種方式雖然能夠在“真正匿名”的情況下完成函式的呼叫,但是缺陷也相當明顯。 那就是AIIFE只能在它宣告的時候被立即呼叫一次,無法延遲呼叫,也無法多次重複呼叫。 (2)閉包 閉包是go語言中匿名函式的另外一種表現形式。 從概念上來說,閉包是一種: 使得函式外部可以間接訪問函式內部定義的區域性變數的手段,在go語言中經常表現為匿名函式的樣子。 func createClousre() func()int{ var num int = 100; //在這裡閉包就是 返回的這個匿名函式 return func ()int{ return num; } } 閉包在go語言中主要有兩個作用: 1)能夠使得在函式外部訪問到函式內部的區域性變數,打破了“區域性視障” func main(){ clo := createClousre(); fmt.Println(clo());//輸出區域性變數num的值 100 } 原本在createClousre函式中定義的區域性變數num,在main函式中是不可能直接訪問到的 但是我們藉助於閉包clo,完成了一次打破“區域性視障”的操作 2)能夠將區域性變數的生命週期延長,具體延長時間視閉包存在的函式而定 func main(){ clo := createClousre();//createClousre函式在此執行結束 fmt.Println(clo()); } 原本createClousre函式在呼叫完成後就會將其佔有的記憶體銷燬, 即區域性變數num也會跟隨消失不見,再也無法訪問。 但是由於閉包中持有了區域性變數num,於是閉包就將區域性變數num的宣告週期延長到了clo上 所以變數clo只要還繼續存在,那麼區域性變數num就得以繼續被訪問。 2.遞迴函式 go語言中的遞迴函式與傳統c語言語法類似,只不過考慮到在go語言中很多運算子是不能直接與呼叫語句結合。 故而寫法上可能存在一點點差異。 (1)遞迴函式: 遞迴函式是指在函式的內部,再次呼叫本身的函式。 eg: func f(){ ... f(); } 乍一看這樣的寫法似乎是一個錯誤的寫法,會導致回撥地獄的產生。 實際上遞迴只是結構上需要滿足這樣,而事實中遞迴函式的構成還需要三個要素。 (2)遞迴三要素: 遞迴變數賦初值、遞迴結束條件、遞迴變數向著遞迴結束的趨勢發生變化 1)遞迴變數賦初值 指的是遞迴函式總是要有一個“標識”來提供作為判斷遞迴結束的標準 並且這個“標識”是需要有一個初始值的。 eg: func f(num int){ ... f(num); } 此時形參num就可以充當遞迴函式的遞迴變數。 2)遞迴結束條件 指的是遞迴函式總是要有一個通過標識來進行的判斷。 這個判斷用於決定遞迴何時結束, 畢竟大家都不希望回撥地獄的發生 eg: func f(num int){ if num <= 1{ return num; } f(num); } 此時針對遞迴變數的條件判斷if就成為了遞迴結束條件, 只要一旦遞迴變數滿足結束條件,那麼遞迴函式就會立即結束。避免了回撥地獄的發生。 3)遞迴變數向著遞迴結束的趨勢發生變化 這一點非常關鍵,因為遞迴結束條件之所以能夠觸發, 是因為遞迴變數必須一直在發生變化,而變化就會有趨勢 在遞迴函式中由於函式最終必須要執行結束,因此遞迴變數就必須向著遞迴結束的趨勢發生變化 eg: func f(num int){ if num <= 1{ return num; } num --; f(num); } 這樣一來,每次遞迴呼叫f()函式的時候,num都是在上一次呼叫的基礎上減少了1 那麼總有一個時刻num的值會滿足遞迴結束條件。 (3)遞迴案例-階乘問題 func getJieChengSum(num int) int{ if num<=1{ return num; } tempNum := num; num--; return tempNum*getJieChengSum(num); } func main() { sum := getJieChengSum(10); fmt.Println(sum); } 3.工程管理 工程管理指的是goland在編譯過程中,一個模組化思想的體現。 主要變現為: 1)在一個檔案中,可以通過“匯入包”的操作後,訪問其他檔案中的函式。 2)整個工程分為三類資料夾:src(程式碼原始檔)、pkg(編譯生成檔案)、bin(系統資原始檔) (1)“包” 包,即package。是go語言中為檔案分類,而後在編譯檔案的過程中對檔案合併時的一個名詞。 包中存放著不同模組,每個模組有著自己獨立的功能。 eg: package user -userLogin.go -userRegist.go .. 此時如果載入了user package這個包,就相當於引入了包中所有模組的功能。 而後就可以通過user包名,來訪問包中模組所提供的功能。 eg: user.Login();//假定Login是userLogin.go檔案中宣告的方法 user.Regist()//假定Regist是userRegist.go檔案中宣告的方法 (2)包與資料夾 包並不是資料夾,但是通常包名和包檔案所在的資料夾設定為相同名字,以便於理解和檢視。 一般上來講包可以認為是編譯完成後在pkg資料夾下的.a檔案 (3)導包 匯入包的目的其實就相當於JS中的link檔案或者script引入檔案。其目的都只有一個 那就是在導包後,能夠使用包中所提供的不同功能,將工程模組化與元件化。 eg: import "包路徑" 包名.包中提供的介面方法 eg: src -test//包名 -testFile.go//檔名 package test func TTest(){...}//方法名 main.go package main import "test"//匯入時候,使用的是包名 func main(){ test.TTest();//呼叫的時候,也是使用的包名 } 案例中能夠看到在導包結束後,呼叫包中提供的方法時,使用的是包名而不是檔名!! (4)注意事項(重點) 由於初學go語言,的確在匯入自定義包的這個問題上碰了個大坑。 我使用的是goLang IDE。在匯入系統包的時候並不會出現什麼問題,主要集中在匯入自定義包的時候! 問題: import 無法檢索到自定義包,但是手打卻會出現包中方法的系統提示。(雖然執行不了) 緣由: 1)核心到爆炸的問題: goLang這個坑貨IDE在載入包的時候不會主動查詢當前路徑下的檔案,即系統環境變數不能自動配置! 必須採用手動配置的方式,將$GOPATH配置完畢才可以! eg: file -> preferences for New Projects And Settings -> GO -> GOPATH -> + -> 工程目錄 注意一點,不要新增src路徑進去,而是直接新增目錄路徑即可! eg: /Users/xxx/Desktop/FuncAndArray 其中FuncAndArray就是我的工程資料夾名稱,也就是說直接將工程路徑丟在這就可以了!!!!! 2)次要問題 Go語言要求自定義的包中,所提供的模組方法首字母必須大寫,否則檢索不到! eg: -test -testFile.go package test func tTest(){...}//方法名小寫了,匯入包後也無法檢測到 4.陣列 go語言中的陣列結構與傳統c語言中的結構類似,但是和JS等弱型別語言卻截然不同。 eg: var 陣列名 [陣列長度]資料型別 = [陣列長度]資料型別{初始化的資料內容} eg: var arr [10]int = [10]int{10,11,12,13,14,15,16,17,18,19}; 對於go語言而言,陣列並不是一個可以存放任意資料型別的複雜資料結構。 陣列能存放的資料的型別在陣列被定義的時候就已經被約定了, 儲存約定資料意外的其他資料型別,會導致go語言給出錯誤提示。 (1)不同的陣列初始化方式 var arr [長度]資料型別 = [長度]資料型別 {初始化資料內容}; arr := [長度]資料型別 {初始化資料內容} arr := [...]資料型別 {初始化資料內容} arr := []資料型別 {初始化資料內容} (2)陣列不能夠通過賦值的方式來進行“硬擴充” 對於弱型別語言中的陣列來說,陣列的長度是一個動態可變的值。比如在JavaScript中 var arr = [1,2,3];//陣列長度最初只有3 arr[100] = 10;//此時陣列的長度被擴充套件到了101 但是對於go語言來說這種方式則一定會報錯 arr := [3]int{1,2,3}; arr[100] = 10;//報錯,陣列下標訪問越界! (3)氣泡排序(我居然在這栽了個跟斗,簡直瞎眼,事實證明老程式員也會在最基礎的地方摔) arr := [10]int{9,8,7,6,5,4,3,2,1,0}; for i:=0; i<len(arr)-1; i++{ for j:=0; j<len(arr)-1-i; j++{ if(arr[j]>arr[j+1]){ temp := arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } fmt.Println(arr); (4)特別注意的是,在go語言中陣列作為引數在函式中傳入的時候,是值傳遞!!! func allAdd(arr [4]int)(resultArr [4]int){ for i,_ := range arr{ arr[i]++; } return arr; } func main(){ arr := [4]int{1,2,3,4}; newArr := allAdd(arr); fmt.Println(arr);//[1,2,3,4] 傳入函式後,原陣列不會受到任何影響!! fmt.Println(newArr);//[2,3,4,5] } 5.隨機數 在計算機的程式語言中,隨機數的概念其實是不存在的。真正的隨機數的名稱應當是:概率。 但是我們可以通過計算機時鐘來模擬出隨機數的樣子,也就是偽隨機數。 而go語言對於偽隨機數的構建不像很多弱型別語言一樣封裝到脖子,讓開發者直接使用 而是需要開發者手動對隨機數種子進行時鐘混淆,來保證每次獲取的樣本都不相同。 eg: rand.Seed(time.Now().UnixNano()); rand.Intn(10); 在go語言中想要使用隨機數需要使用到兩個系統庫,【math/rand】和【time】. 其中time庫提供時間函式,來作為隨機數種子 而math/rand庫則提供隨機數函式,從隨機數種子混淆後的範圍中獲取樣本。 eg: rand.Seed(time.Now().UnixNano()); for i:=0;i<100;i++{ ranNum := rand.Intn(100); fmt.Println(ranNum); } 注意:rand.Intn()方法獲取的隨機數是從下界到上界,但是不包括上界的隨機數。