1. 程式人生 > >Go學習筆記二(函式)

Go學習筆記二(函式)

函式

函式作用域

在 Go 中,定義在函式外的變數是全域性的,那些定義在函式內部的變數,對於函式來說是區域性的。
區域性 變數 僅僅 在執行定義它的函式時有效。

package main
import "fmt"

var a int
func main() {
    a = 5
    println(a)
    f()
}
func f() {
    a := 6
    println(a)
    g()
}
func g() {
    println(a)
}
//565

延遲程式碼(defer )

//在這裡有許多重複的程式碼。為了解決這些,Go 有了 defer 語句。在 defer 後指定的函式會在函式退出 前 呼叫。
func ReadWrite() bool { file.Open("file") // 做一些工作 if failureX { file.Close() //←重複程式碼 return false } if failureY { file.Close() //←重複程式碼 return false } file.Close() //←重複程式碼 return true } //==================== //用defer改進 func ReadWrite() bool { file.Open("file"
) defer file.Close() //← file.Close() 被新增到了 defer 列表 // 做一些工作 if failureX { return false //← Close() 現在自動呼叫 } if failureY { return false //← 這裡也是 } return true// ← And here }

可以將多個函式放入 “延遲列表”中

package main
import "fmt"

var a int
func main() {
    for i := 0 ; i < 5
; i++ { //fmt.Printf("%d ", i)//0 1 2 3 4 //延遲的函式是按照後進先出(LIFO)的順序執行 defer fmt.Printf("%d ", i)//4 3 2 1 0 } }

利用 defer 甚至可以修改返回值

package main
// import "fmt"

var a int
func main() {
    println(f())
}

//在這個(匿名)函式中,可以訪問任何命名返回引數:
//在 defer 中訪問返回值
func f() (ret int ) { //← ret 初始化為零
    defer func () {
        ret++ //← ret 增加為 1
    } ()
    return 0 //← 返回的是 1 而 不是 0 !
}
defer func () {
/* ... */
} () // ← () 在這裡是必須的


//或者這個例子,更加容易瞭解為什麼,以及在哪裡需要括號:
//帶引數的函式符號
defer func (x int ) {
/* ... */
} (5) //← 為輸入引數 x 賦值 5

變參

接受不定數量的引數的函式叫做變參函式。定義函式使其接受變參:
arg ... int 告訴 Go 這個函式接受不定數量的引數。注意,這些引數的型別全部是 int 。
如果不指定變參的型別,預設是空的介面 interface{}

func myfunc(arg ... int ) {}

恐慌(Panic)和恢復(Recover)

Go 沒有像 Java 那樣的異常機制,例如你無法像在 Java 中那樣丟擲一個異常。作為替代,它使用了恐慌和恢復(panic-and-recover)機制。一定要記得,這應當作為最後的手段被使用,你的程式碼中應當沒有,或者很少的令人恐慌的東西。這是個強大的工具,明智的使用它。那麼,應該如何使用它呢。
Panic
是一個內建函式,可以中斷原有的控制流程,進入一個令人恐慌的流程中。當函式 F 呼叫 panic ,函式 F 的執行被中斷,並且 F 中的延遲函式會正常執行,然後 F 返回到呼叫它的地方。在呼叫的地方, F 的行為就像呼叫了 panic 。這一過程繼續向上,直到程式崩潰時的所有 goroutine 返回。
恐慌可以直接呼叫 panic 產生。也可以由 執行時錯誤 產生,例如訪問越界的陣列。
Recover
是一個內建的函式,可以讓進入令人恐慌的流程中的goroutine恢復過來。recover僅在延遲 函式中有效。
在正常的執行過程中,呼叫 recover 會返回 nil 並且沒有其他任何效果。如果
當前的 goroutine 陷入恐慌,呼叫 recover 可以捕獲到 panic 的輸入值,並且恢復正常的執行。

函式練習

// Q5. (0) 平均值
// 1. 編寫一個函式用於計算一個 float64 型別的 slice 的平均值。
package main 
import "fmt"
func main() {
    s := [] float64{1.25,0.98,3.96}
    fmt.Printf("%v\n",average(s))
}

func average(xs []float64) (avg float64) {
    sum := 0.0
    switch len(xs){
    case 0:
        avg = 0 //如果長度是零,返回 0;
    default :
        for _, v := range xs {
            sum += v
        }        
        avg = sum /float64(len(xs)) //為了使除法能正常計算,必須將值轉換為 float64 ;
    }
    return  //得到平均值,返回它
}
// Q6. (0) 整數順序
// 1. 編寫函式,返回其(兩個)引數正確的(自然)數字順序:
// f(7,2) → 2,7
// f(2,7) → 2,7
package main 
import "fmt"
func main() {
    s := [] float64{1.25,0.98,3.96}
    fmt.Printf("%v\n",average(s))
}

func order(a,b int) (int , int) {
    if a > b
        return b,a
    return a,b    
}
// 1. 建立一個固定大小儲存整數的棧。它無須超出限制的增長。定義 push 函式——
// 將資料放入棧,和 pop 函式——從棧中取得內容。棧應當是先進後出,後進先出(LIFO)
// 的。
package main

import "fmt"

type stack struct { //← 棧 不應該被匯出
    i int   //指向最後一個元素的索引
    data [10] int
}
//向函式 push 提供一個指向棧的指標
//這裡不用指標不會有真正的結果,因為func (s stack) push(k int ) push 函式得到的是s的副本

func (s *stack) push(k int ) { //← 工作於引數的副本
    if s.i+1 > 9 {
        return
    }
    // println(k)
    // println(s.i)
    s.data[s.i] = k
    s.i++
}
func (s *stack) pop() int {
    s.i--
    return s.data[s.i]
}
func main() {
    var s stack //← 讓 s 是一個 stack 變數
    s.push(25)
    fmt.Printf("stack %v\n", s) ;
    s.push(14)
    fmt.Printf("stack %v\n", s) ;
}
// 更進一步。編寫一個 String 方法將棧轉化為字串形式的表達。可以這樣的
// 方式列印整個棧: fmt.Printf("My stack %v\n", stack)
// 棧可以被輸出成這樣的形式: [0:m] [1:l] [2:k]
package main

import (
    "fmt"
    "strconv"
)

type stack struct { //← 棧 不應該被匯出
    i int   //指向最後一個元素的索引
    data [10] int
}
func (s *stack) push(k int ) { //← 工作於引數的副本
    if s.i+1 > 9 {
        return
    }
    // println(k)
    // println(s.i)
    s.data[s.i] = k
    s.i++
}
func (s stack) String() string {
    var str string
    for i := 0 ; i <= s.i ; i++ {
        str = str + "[" +
        strconv.Itoa(i) + ":" + strconv.Itoa(s.data[i]) + "]"
    }
    return str
}
func main() {
    var s stack
     s.push(25)
     s.push(14)
     s.push(88)
    fmt.Printf("%v\n",s)
    fmt.Printf("%v\n",s.String())
}


// Q9. (1) 變參
// 1. 編寫函式接受整數型別變參,並且每行列印一個數字。
package main 

import "fmt"

func main() {
    printthem(1,4,5,7,8)
    //printthem(1,6,9)
}

func printthem(numbers ... int ) {//← numbers 現在是整數型別的 slice
    for _,d := range numbers{
        fmt.Printf("%d\n",d)
    }
}
// Q10. (1) 斐波那契
// 1. 斐波那契數列以:1,1,2,3,5,8,13,... 開始。或者用數學形式表達:x 1 = 1;x 2 =
// 1;x n = x n−1 + x n−2 ∀n > 2。
// 編寫一個接受 int 值的函式,並給出這個值得到的斐波那契數列。

package main 

// import "fmt"

func fibonacci( value int ) []int {
    x := make([]int , value)//建立一個用於儲存函式執行結果的 array ;
    x[0],x[1] = 1 ,1
    for n := 2; n < value ; n++{
        x[n] = x[n-1] + x[n-2]
    }
    return x //返回 整個 array;
}
func main() {
    //使用關鍵字 range 可以 “遍歷” 數字得到斐波那契函式返回的序列。這有 10 個,且列印了出來
    for _, term := range fibonacci(10){
        println(term)
        //fmt.Printf("%v,",term)
    }
}
// map() 函式是一個接受一個函式和一個列表作為引數的函式。函
// 數應用於列表中的每個元素,而一個新的包含有計算結果的列表被返回。因此:
// map(f(),(a 1 ,a 2 ,...,a n−1 ,a n )) = (f(a 1 ),f(a 2 ),...,f(a n−1 ),f(a n ))
// 1. 編寫 Go 中的簡單的 map() 函式。它能工作於操作整數的函式就可以了。
// 2. 擴充套件程式碼使其工作於字串列表
package main
import "fmt"
func main() {
    m := []int {1,2,3}
    f := func (i int) int {
        return i*i
    }
    fmt.Printf("%v",Map(f,m))
}

func Map(f func(int) int,l []int) []int {
    j := make([]int,len(l))
    for k,v:=range l{
        j[k] = f(v)
    }
    return j
}
// Q12. (0) 最小值和最大值
// 1. 編寫一個函式,找到 int slice ( []int ) 中的最大值。
// 2. 編寫一個函式,找到 int slice ( []int ) 中的最小值。
package main 

func main() {
    l := []int{0,2,6,9,10}
    println(max(l))

    println(min(l))
}
func max(l []int) (max int) {//使用了命名返回引數;
    max = l[0]
    for _, v:=range l{//對 l 迴圈。元素的序號不重要;

        if v > max {
            max = v // 如果找到了新的最大值,記住它;
        }
    }
    return  //當前的 max 值被返回
}

func min(l []int)(min int) {
    min = l[0]
    for _,v := range l{
        if v < min {
            min = v
        }
    }
    return
}
// Q13. (1) 氣泡排序
// 1. 編寫一個針對 int 型別的 slice 氣泡排序的函式。
// 它在一個列表上重複步驟來排序,比較每個相鄰的元素,並且順序錯
// 誤的時候,交換它們。一遍一遍掃描列表,直到沒有交換為止,這意
// 味著列表排序完成。演算法得名於更小的元素就像 “ 泡泡 ” 一樣冒到列表
// 的頂端。

// 1. 氣泡排序並不是最有效率的,對於 n 個元素它的演算法複雜度是 O(n 2 )。快速排
// 序  是更好的排序演算法。
// 但是氣泡排序容易實現。

package main 

import "fmt"

func main() {
    n := []int {5,-1,12,0,3,6} //定義一個引用型別slice
    fmt.Printf("unsorted %v\n",n)

    bubbleSort(n)
    fmt.Printf("sorted %v\n",n)
}
//由於 slice 是一個引用型別, bubblesort 函式可以工作,並且無須返回排序後的 slice。
func bubbleSort(n []int) {
    for i:=0;i < len(n) - 1; i++{
        for j:= i+1; j<len(n);j++{
            if n[j] < n[i] {
                n[i],n[j] = n[j],n[i]
            }
        }
    }
}
// Q14. (1) 函式返回一個函式
// 1. 編寫一個函式返回另一個函式,返回的函式的作用是對一個整數 +2。函式的名
// 稱叫做 plusTwo 。然後可以像下面這樣使用:
// p := plusTwo()
// fmt.Printf("%v\n", p(2))
// 應該列印 4。
// 2. 使 1 中的函式更加通用化,建立一個 plusX(x) 函式,返回一個函式用於對整
// 數加上 x 。
package main 

import "fmt"

func main() {
    p2 := plusTwo()
    fmt.Printf("%v\n",p2(2))
    p3 := plusX(3)
    fmt.Printf("%v\n",p3(5))
}
func plusTwo() func(int) int {//定義新的函式返回一個函式
    return func(x int) int{ return x + 2} //函式符號,在返回語句中定義了一個 +2 的 函式
}
//這裡使用了閉包
func plusX(x int ) func( int ) int {

    return func (y int ) int { return x + y }

}

函式閉包