1. 程式人生 > >Go學習(7):函式

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

一般步驟:

  1. 定義一個函式型別
  2. 實現定義的函式型別
  3. 作為引數呼叫

有點介面的感覺

函式當做值和型別在我們寫一些通用介面的時候非常有用,通過上面例子我們看到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
}