Go學習(7):函式
一、函式
1.1 什麼是函式
函式是執行特定任務的程式碼塊。
1.2 函式的宣告
go語言至少有一個main函式
語法格式:
func funcName(parametername type1, parametername type2) (output1 type1, output2 type2) {
//這裡是處理邏輯程式碼
//返回多個值
return value1, value2
}
- func:函式由 func 開始宣告
- funcName:函式名稱,函式名和引數列表一起構成了函式簽名。
- parametername type:引數列表,引數就像一個佔位符,當函式被呼叫時,你可以將值傳遞給引數,這個值被稱為實際引數。引數列表指定的是引數型別、順序、及引數個數。引數是可選的,也就是說函式也可以不包含引數。
- output1 type1, output2 type2:返回型別,函式返回一列值。return_types 是該列值的資料型別。有些功能不需要返回值,這種情況下 return_types 不是必須的。
- 上面返回值聲明瞭兩個變數output1和output2,如果你不想宣告也可以,直接就兩個型別。
- 如果只有一個返回值且不宣告返回值變數,那麼你可以省略包括返回值的括號(即一個返回值可以不宣告返回型別)
- 函式體:函式定義的程式碼集合。
1.3 函式的使用
示例程式碼:
package main
import "fmt"
func main() {
/* 定義區域性變數 */
var a int = 100
var b int = 200
var ret int
/* 呼叫函式並返回最大值 */
ret = max(a, b)
fmt.Printf( "最大值是 : %d\n", ret )
}
/* 函式返回兩個數的最大值 */
func max(num1, num2 int) int {
/* 定義區域性變數 */
var result int
if (num1 > num2) {
result = num1
} else {
result = num2
}
return result
}
執行結果:
最大值是 : 200
1.4 Go 函式可以返回多個值
一個函式可以沒有返回值,也可以有一個返回值,也可以有返回多個值。
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("Mahesh", "Kumar")
fmt.Println(a, b)
}
func SumAndProduct(A, B int) (add int, Multiplied int) {
add = A+B
Multiplied = A*B
return
}
1.5 空白識別符號
_是Go中的空白識別符號。它可以代替任何型別的任何值。讓我們看看這個空白識別符號的用法。
比如rectProps函式返回的結果是面積和周長,如果我們只要面積,不要周長,就可以使用空白識別符號。
示例程式碼:
package main
import (
"fmt"
)
func rectProps(length, width float64) (float64, float64) {
var area = length * width
var perimeter = (length + width) * 2
return area, perimeter
}
func main() {
area, _ := rectProps(10.8, 5.6) // perimeter is discarded
fmt.Printf("Area %f ", area)
}
1.6 可變參
Go函式支援變參。接受變參的函式是有著不定數量的引數的。為了做到這點,首先需要定義函式使其接受變參:
func myfunc(arg ...int) {}
arg ...int
告訴Go這個函式接受不定數量的引數。注意,這些引數的型別全部是int。在函式體中,變數arg是一個int的slice:
for _, n := range arg {
fmt.Printf("And the number is: %d\n", n)
}
1.7 引數傳遞
go語言函式的引數也是存在值傳遞和引用傳遞
函式運用場景
值傳遞
package main
import (
"fmt"
"math"
)
func main(){
/* 宣告函式變數 */
getSquareRoot := func(x float64) float64 {
return math.Sqrt(x)
}
/* 使用函式 */
fmt.Println(getSquareRoot(9))
}
引用傳遞
這就牽扯到了所謂的指標。我們知道,變數在記憶體中是存放於一定地址上的,修改變數實際是修改變數地址處的記憶體。只有add1函式知道x變數所在的地址,才能修改x變數的值。所以我們需要將x所在地址&x傳入函式,並將函式的引數的型別由int改為*int,即改為指標型別,才能在函式中修改x變數的值。此時引數仍然是按copy傳遞的,只是copy的是一個指標。請看下面的例子:
package main
import "fmt"
//簡單的一個函式,實現了引數+1的操作
func add1(a *int) int { // 請注意,
*a = *a+1 // 修改了a的值
return *a // 返回新值
}
func main() {
x := 3
fmt.Println("x = ", x) // 應該輸出 "x = 3"
x1 := add1(&x) // 呼叫 add1(&x) 傳x的地址
fmt.Println("x+1 = ", x1) // 應該輸出 "x+1 = 4"
fmt.Println("x = ", x) // 應該輸出 "x = 4"
}
- 傳指標使得多個函式能操作同一個物件。
- 傳指標比較輕量級 (8bytes),只是傳記憶體地址,我們可以用指標傳遞體積大的結構體。如果用引數值傳遞的話, 在每次copy上面就會花費相對較多的系統開銷(記憶體和時間)。所以當你要傳遞大的結構體的時候,用指標是一個明智的選擇。
- Go語言中slice,map這三種類型的實現機制類似指標,所以可以直接傳遞,而不用取地址後傳遞指標。(注:若函式需改變slice的長度,則仍需要取地址傳遞指標)
1.8 閉包
Go 語言支援匿名函式,可作為閉包。匿名函式是一個"內聯"語句或表示式。匿名函式的優越性在於可以直接使用函式內的變數,不必申明。
package main
import "fmt"
func getSequence() func() int {
i:=0
return func() int {
i+=1
return i
}
}
func main(){
/* nextNumber 為一個函式,函式 i 為 0 */
nextNumber := getSequence()
/* 呼叫 nextNumber 函式,i 變數自增 1 並返回 */
fmt.Println(nextNumber())
fmt.Println(nextNumber())
fmt.Println(nextNumber())
/* 建立新的函式 nextNumber1,並檢視結果 */
nextNumber1 := getSequence()
fmt.Println(nextNumber1())
fmt.Println(nextNumber1())
}
執行結果:
1
2
3
1
2
函式做為值
在Go中函式也是一種變數,我們可以通過type來定義它
**同種方法:**引數型別、個數、順序相同,返回值相同
package main
import "fmt"
type testInt func(int) bool // 聲明瞭一個函式型別
func isOdd(integer int) bool {
if integer%2 == 0 {
return false
}
return true
}
func isEven(integer int) bool {
if integer%2 == 0 {
return true
}
return false
}
func filter(slice []int, f testInt) []int {
var result []int
for _, value := range slice {
if f(value) {
result = append(result, value)
}
}
return result
}
func main(){
slice := []int {1, 2, 3, 4, 5, 7}
fmt.Println("slice = ", slice)
odd := filter(slice, isOdd) // 函式當做值來傳遞了
fmt.Println("Odd elements of slice are: ", odd)
even := filter(slice, isEven) // 函式當做值來傳遞了
fmt.Println("Even elements of slice are: ", even)
}
type testInt func(int) bool
就是將該種函式型別賦值給testInt
一般步驟:
- 定義一個函式型別
- 實現定義的函式型別
- 作為引數呼叫
有點介面的感覺
函式當做值和型別在我們寫一些通用介面的時候非常有用,通過上面例子我們看到testInt這個型別是一個函式型別,然後兩個filter函式的引數和返回值與testInt型別是一樣的,但是我們可以實現很多種的邏輯,這樣使得我們的程式變得非常的靈活
range 的用法前瞻:
package main
import "fmt"
func main(){
//1.range 作用於 str ,返回的第一個值為index,第二個值為char
str := "helloworld"
PrintStr(str)
fmt.Printf("--------------------------\n" )
//2.當range作用於array時, 第一個返回值為index,第二次是value
array := [5]int{1,2,3,4,5}
PrintArray(array)
fmt.Printf("-------------------------- \n")
//3.當range作用於slice時, 第一個返回值為index,第二次是value
arr1 := [10]int {1,2,3,4,5,6,7,8,9,10}
var slice2 []int = arr1[:5]
//sliceModify1(slice2)
//sliceModify(&slice2)
PrintSlice(slice2)
}
func PrintStr(str string){
for index,ch := range str{
fmt.Printf("%d---%c\n",index,ch)
}
}
func PrintArray(array [5]int) {
for index, res := range array {
fmt.Println(index, "--", res)
}
}
func PrintSlice(slice []int) {
for index, res := range slice {
fmt.Println(index, "--", res)
}
}
//無效的slicemodify方法
func sliceModify1(slice []int) {
// slice[0] = 88
slice = append(slice, 6)
}
//有效的slicemodify方法,接收指標作為引數,這樣改動才會在原slice上體現出來
func sliceModify(slice *[]int) {
*slice = append(*slice, 6)
}
1.9 Panic和Recover
Go語言追求簡潔優雅,所以,Go語言不支援傳統的 try…catch…finally 這種異常,因為Go語言的設計者們認為,將異常與控制結構混在一起會很容易使得程式碼變得混亂。因為開發者很容易濫用異常,甚至一個小小的錯誤都丟擲一個異常。
在Go語言中,使用多值返回來返回錯誤。不要用異常代替錯誤,更不要用來控制流程。
在極個別的情況下,也就是說,遇到真正的異常的情況下(比如除數為0了)。
才使用Go中引入的Exception處理:defer, panic, recover。
Panic和Recover
Go沒有像Java那樣的異常機制,它不能丟擲異常,而是使用了panic和recover機制。一定要記住,你應當把它作為最後的手段來使用,也就是說,你的程式碼中應當沒有,或者很少有panic的東西。這是個強大的工具,請明智地使用它。那麼,我們應該如何使用它呢?
Panic
是一個內建函式,可以中斷原有的控制流程,進入一個令人恐慌的流程中。當函式F呼叫panic,函式F的執行被中斷,但是F中的延遲函式會正常執行,然後F返回到呼叫它的地方。在呼叫的地方,F的行為就像呼叫了panic。這一過程繼續向上,直到發生panic的goroutine中所有呼叫的函式返回,此時程式退出。恐慌可以直接呼叫panic產生。也可以由執行時錯誤產生,例如訪問越界的陣列。
Recover
是一個內建的函式,可以讓進入令人恐慌的流程中的goroutine恢復過來。recover僅在延遲函式中有效。在正常的執行過程中,呼叫recover會返回nil,並且沒有其它任何效果。如果當前的goroutine陷入恐慌,呼叫recover可以捕獲到panic的輸入值,並且恢復正常的執行。
下面這個函式演示瞭如何在過程中使用panic
var user = os.Getenv("USER")
func init() {
if user == "" {
panic("no value for $USER")
}
}
下面這個函式檢查作為其引數的函式在執行時是否會產生panic:
func throwsPanic(f func()) (b bool) {
defer func() {
if x := recover(); x != nil {
b = true
}
}()
f() //執行函式f,如果f中出現了panic,那麼就可以恢復回來
return
}