【go語言學習】函數function

函數是組織好的、可重複使用的、用於執行指定任務的代碼塊。
Go語言中支持函數、匿名函數和閉包,而且函數在Go語言中屬於「一等公民」。數據庫

1、函數的聲明和調用

一、函數的聲明

Go語言中定義函數使用func關鍵字,具體格式以下:編程

func funcName(parametername type) (output type) {
    //這裏是處理邏輯代碼
    //返回多個值
    return valu
}
  • func 函數由func開始聲明
  • funcName 函數名
  • () 函數的標誌
  • parametername type 函數的參數列表,參數列表指定參數的類型、順序以及個數,參數是可選,無關緊要;這裏的參數至關於一個佔位符,因此也叫形式參數,當函數被調用時,傳入的值傳遞給參數,這個值被稱爲實際參數
  • ouput type 返回值列表,返回值能夠由名稱和類型組成,也能夠只寫類型不寫名稱;返回值能夠有一個或多個,當有一個返回值時能夠不加(),多個返回值時必須加()
  • 函數體:實現指定功能的代碼塊{}裏面的內容。
二、函數的調用

能夠經過funcName(parameter)的方式調用函數。調用有返回值的函數時,能夠不接收其返回值。數組

package main

import "fmt"

func main() {
    a := 10
    b := 20
    res := sum(a, b)
    fmt.Printf("%v + %v = %v", a, b, res)
}

func sum(a, b int) int {
    return a + b
}

運行結果閉包

10 + 20 = 30

2、函數的參數

一、參數的使用:

形式參數:定義函數時,用於接收外部傳入的數據,叫作形式參數,簡稱形參。
實際參數:調用函數時,傳給形參的實際的數據,叫作實際參數,簡稱實參。
函數調用:app

  • A:函數名稱必須匹配
  • B:實參與形參必須一一對應:順序,個數,類型
    函數的參數中若是相鄰變量的類型相同,則能夠省略類型,如:
package main

import "fmt"

func main() {
    a := 10
    b := 20
    res := add(a, b)
    fmt.Printf("%v + %v = %v", a, b, res)
}

func add(a, b int) sum int {
    sum = a + b
    return
}
二、可變參數

可變參數是指函數的參數數量不固定。Go語言中的可變參數經過在參數名後加...來標識。socket

注意:可變參數一般要做爲函數的最後一個參數。函數式編程

func funcName(arg ...int) {}

arg ...int告訴Go這個函數接受不定數量的參數。注意,這些參數的類型所有是int。在函數體中,變量arg是一個int類型的slice函數

三、參數的傳遞

go語言函數的參數也是存在值傳遞引用傳遞指針

數據類型:code

  • 按照數據類型來分:

    • 基本數據類型
      int,float,string,bool
    • 複合數據類型
      arry,slice,map,struct,interface,chan...
  • 按照數據的存儲特色來分:

    • 值類型的數據,操做的是數據自己。
      int,float,string,bool,arry,struct
    • 引用類型的數據,操做的數據的地址。
      slice,map,chan
  • 值傳遞:值類型的數據傳遞爲值傳遞,傳遞的是數據的副本。修改數據,對原始數據沒有影響。

package main

import "fmt"

func main() {
	arr1 := [3]int{1, 2, 3}
	fmt.Println("函數調用前,數組的數據:", arr1)
	fun(arr1)
	fmt.Println("函數調用後,數組的數據:", arr1)
}

func fun(arr2 [3]int) {
	fmt.Println("函數中,數組的數據:", arr2)
	arr2[0] = 100
	fmt.Println("函數中,修改後數據的數據:", arr2)
}

運行結果

函數調用前,數組的數據: [1 2 3]
函數中,數組的數據: [1 2 3]
函數中,修改後數據的數據: [100 2 3]
函數調用後,數組的數據: [1 2 3]
  • 引用傳遞:引用類型的數據傳遞爲引用傳遞,傳遞的數據的地址,致使多個變量指向同一塊內存。
package main

import "fmt"

func main() {
	slice1 := []int{1, 2, 3}
	fmt.Println("函數調用前,切片的數據:", slice1)
	fun(slice1)
	fmt.Println("函數調用後,切片的數據:", slice1)
}

func fun(slice2 []int) {
	fmt.Println("函數中,切片的數據:", slice2)
	slice2[0] = 100
	fmt.Println("函數中,修改後切片的數據:", slice2)
}

運行結果

函數調用前,切片的數據: [1 2 3]
函數中,切片的數據: [1 2 3]
函數中,修改後切片的數據: [100 2 3]
函數調用後,切片的數據: [100 2 3]
  • 指針傳遞:傳遞的就是數據的內存地址。

3、函數的返回值

函數的返回值:
一個函數的執行結果,返回給函數調用處,執行結果就叫函數的返回值。
return語句:
一個函數的定義上有返回值,那麼函數中必須有return語句,將執行結果返回給函數的調用處。
函數的返回結果必須和函數定義的一致,類型、數量、順序。

  • 返回值的命名
    函數定義時能夠給返回值命名,並在函數體中直接使用這些變量,最後經過return關鍵字返回。
func add(x, y int) (sum int) {
	sum = x + y
	return
}
  • 多返回值
    一個函數能夠沒有返回值,也能夠有一個返回值,也能夠有返回多個值。
func calc(x, y int) (sum, sub int) {
	sum = x + y
	sub = x - y
	return
}
  • 空白標識符-可用來捨棄某些返回值。
func calc(x, y int) (sum, sub int) {
	sum = x + y
	sub = x - y
	return
}
_, sub := calc(10, 20) //捨棄sum

4、函數的做用域

做用域:變量可使用的範圍。

一、全局變量

全局變量是定義在函數外部的變量,它在程序整個運行週期內都有效。 全部的函數均可以使用,並且共享這一份數據。

package main

import "fmt"

var a = 10

func main() {
	fmt.Println("test調用前,main中訪問a:", a)
	test()
	fmt.Println("test調用後,main中訪問a:", a)
}

func test() {
	fmt.Println("操做前,test中訪問a: ", a)
	a = 20
	fmt.Println("操做後,test中訪問a: ", a)
}

運行結果

test調用前,main中訪問a: 10
操做前,test中訪問a:  10
操做後,test中訪問a:  20
test調用後,main中訪問a: 20
二、局部變量

一個函數內部定義的變量,就叫作局部變量
局部變量只能在定義的範圍內訪問操做

package main

import "fmt"

func main() {
	test()
	fmt.Println("main中訪問a:", a) //undefined: a
}

func test() {
	a := 20
	fmt.Println("test中訪問a: ", a)
}

運行結果

# command-line-arguments
.\main.go:7:35: undefined: a

局部變量和全局變量重名,優先訪問局部變量。

package main

import "fmt"

var a = 100

func main() {
	test()
}

func test() {
	a := 20
	fmt.Println("test中訪問a: ", a)
}

運行結果

test中訪問a:  20

另外,ifswitchfor語句中聲明的變量也屬於局部變量,在代碼塊外沒法訪問。

5、函數的本質

函數也是Go語言中的一種數據類型,能夠做爲另外一個函數的參數,也能夠做爲另外一個函數的返回值。

一、函數是一種數據類型
package main

import "fmt"

func main() {
	fmt.Printf("%T\n", fun1) //fun1的類型是func(int, int)
	fmt.Printf("%T\n", fun2) //fun2的類型是func(int, int) int
}

func fun1(a, b int) {
	fmt.Println(a, b)
}

func fun2(c, d int) int {
	fmt.Println(c, d)
	return 0
}

運行結果

func(int, int)
func(int, int) int
二、定義函數類型的變量
var f fun(int, int) int

上面語句定義了一個變量f,它是一個函數類型,這種函數接收兩個int類型的參數而且返回一個int類型的返回值。
全部參數和返回值符合條件的函數能夠賦值給f變量

package main

import "fmt"

func main() {
	var f func(int, int) int
	f = sum
	res := f(20, 10)
	fmt.Println("20 + 10 = ", res)
	f = sub
	res = f(20, 10)
	fmt.Println("20 - 10 = ", res)
}

func sum(a, b int) int {
	return a + b
}

func sub(a, b int) int {
	return a - b
}

運行結果

20 + 10 =  30
20 - 10 =  10

6、匿名函數

匿名函數就是沒有函數名的函數

func (參數) (返回值) {
    函數體
}

匿名函數由於沒有函數名,因此沒辦法像普通函數那樣調用,因此匿名函數須要保存到某個變量或者做爲當即執行函數。

package main

import "fmt"

func main() {
	// 將匿名函數保存到變量中
	sum := func(a, b int) int {
		return a + b
	}
	// 經過變量調用匿名函數
	res := sum(10, 20)
	fmt.Println("10 + 20 =", res)
	// 自執行函數,匿名函數定義完直接加()執行
	func(c, d int) {
		fmt.Printf("%v + %v = %v\n", c, d, c+d)
	}(10, 20)
}

運行結果

10 + 20 = 30
10 + 20 = 30

7、高階函數

go語言支持函數式編程:

  • 將一個函數做爲另外一個函數的參數
  • 將一個函數做爲另外一個函數的返回值

一、回調函數

一個函數被做爲參數傳遞給另外一個函數,那麼這個函數就叫作回調函數。
回調函數並不會立刻被調用執行,它會在包含它的函數內的某個特定的時間點被「回調」(就像它的名字同樣)。

package main

import "fmt"

func main() {
	res := calc(10, 20, add)
	fmt.Println(res)
}
// add是一個func(int, int)int類型的函數,能夠做爲參數傳遞給calc函數
func add(a, b int) int {
	return a + b
}

// calc 高階函數,它有兩個int類型的參數和一個func(int, int)int函數類型的參數
// oper 回調函數,它被做爲參數傳遞給calc函數
func calc(a, b int, oper func(int, int) int) int {
	res := oper(a, b)
	return res
}

運行結果

30
二、函數做爲返回值
package main

import "fmt"

func main() {
	fun := calc("+")
	res := fun(10, 20)
	fmt.Println("10 + 20 =", res)
}

func sum(a, b int) int {
	return a + b
}

func sub(a, b int) int {
	return a - b
}

func calc(s string) func(int, int) int {
	switch s {
	case "+":
		return sum
	case "-":
		return sub
	default:
		fmt.Println("你傳的是個啥玩意!")
		return nil
	}
}

運行結果

10 + 20 = 30
三、閉包

一個外層函數,有內層函數,該內層函數會操做外層函數的局部變量(外層函數的參數,或外層函數定義的變量),而且該內層函數做爲外層函數的返回值。
這個內層函數和外層函數的局部變量,統稱爲閉包結構

局部變量的生命週期會發生改變。正常的局部變量隨着函數的調用而建立,隨着函數的結束而銷燬。
可是閉包結構的外層函數的局部變量並不會隨着外層函數的結束而銷燬,由於內層函數還要繼續使用。

package main

import "fmt"

func main() {
	fun := add()
	res := fun()
	fmt.Println("第一次調用,res=", res)
	res = fun()
	fmt.Println("第二次調用,res=", res)
	res = fun()
	fmt.Println("第二次調用,res=", res)
}

func add() func() int {
	i := 0
	return func() int {
		i++
		return i
	}
}

運行結果

第一次調用,res= 1
第二次調用,res= 2
第二次調用,res= 3

7、defer語句

defer是Go語言中的延遲執行語句,用來添加函數結束時執行的代碼,經常使用於釋放某些已分配的資源、關閉數據庫鏈接、斷開socket鏈接、解鎖一個加鎖的資源。
Go語言機制擔保必定會執行defer語句中的代碼。

一、延遲函數
  • 被延遲的函數,在離開所在的函數或方法時,執行(報錯的時候也會執行
  • 若是有不少defer語句,聽從「先進後出」的模式
package main

import "fmt"

func main() {
	a := 1
	b := 2
	c := 3
	d := 4
    //defer a++ //a++ 是一個語句,並不是函數或方法,程序報錯
	defer fmt.Println("defer", a)
	defer fmt.Println("defer", b)
	defer fmt.Println("defer", c)
	defer fmt.Println("defer", d)
	fmt.Println(a)
	fmt.Println(b)
	fmt.Println(c)
	fmt.Println(d)
}

運行結果

1
2
3
4
defer 4
defer 3
defer 2
defer 1
二、延遲方法

延遲並不只僅侷限於函數。延遲一個方法調用也是徹底合法的。

package main

import "fmt"

// Student 學生結構體
type Student struct {
	name string
	city string
}

func (s Student) hello() {
	fmt.Printf("我叫%v, 我來自%v。\n", s.name, s.city)
}

func main() {
	s := Student{
		name: "jack",
		city: "北京市",
	}
	defer s.hello()
	fmt.Print("你們好,")
}

運行結果

你們好,我叫jack, 我來自北京市
三、延遲參數

defer聲明時會先計算肯定參數的值,defer推遲執行的僅是其函數體。

package main

import "fmt"

func main() {
	a := 1
	defer fun(a)
	a++
	fmt.Println("main中的a =", a)
}

func fun(a int) {
	fmt.Println("fun中的a =", a)
}

運行結果

main中的a = 2
fun中的a = 1
四、defer與return
  • 全部函數在執行 RET 返回指令以前,都會先檢查是否存在 defer 語句,若存在則先逆序調用 defer 語句進行收尾工做再退出返回;
  • 匿名返回值是在 return 執行時被聲明,有名返回值則是在函數聲明的同時被聲明,所以在 defer 語句中只能訪問有名返回值,而不能直接訪問匿名返回值;
  • return 其實應該包含先後兩個步驟:第一步是給返回值賦值(若爲有名返回值則直接賦值,若爲匿名返回值則先聲明再賦值);第二步是調用 RET 返回指令並傳入返回值,而 RET 則會檢查 defer 是否存在,若存在就先逆序插播 defer 語句,最後 RET 攜帶返回值退出函數;
  • 所以,‍‍defer、return、返回值三者的執行順序應該是:return最早給返回值賦值;接着 defer 開始執行一些收尾工做;最後 RET 指令攜帶返回值退出函數。
(1)匿名返回值的狀況
package main

import "fmt"

func main() {
	fmt.Println(fun1())
}

func fun1() int {
	var i int
	defer func() {
		i++
	}()
	return i
}

運行結果

0
(2)有名返回值的狀況
package main

import "fmt"

func main() {
	fmt.Println(fun2())
}

func fun2() (i int) {
	defer func() {
		i++
	}()
	return i
}

運行結果

1

分析

  • fun1()int 函數的返回值沒有被提早聲名,其值來自於其餘變量的賦值,而 defer 中修改的也是其餘變量(其實該 defer 根本沒法直接訪問到返回值),所以函數退出時返回值並無被修改。
  • fun2()(i int) 函數的返回值被提早聲名,這使得 defer 能夠訪問該返回值,所以在 return 賦值返回值 i 以後,defer 調用返回值 i 並進行了修改,最後導致 return 調用 RET 退出函數後的返回值纔會是 defer 修改過的值。

經典案例

package main

import "fmt"

func main() {
	fmt.Println(f1())
	fmt.Println(f2())
	fmt.Println(f3())
	fmt.Println(f4())
}

func f1() int {
	x := 5
	defer func() {
		x++ // defer 訪問的是變量x,訪問不到返回值
		// fmt.Println("f1函數defer中的x =", x) //6
	}()
	return x // 返回值 = 5     //返回5
}

func f2() (x int) {
	defer func() {
		x++ //defer 訪問x, 能夠訪問返回值,在RET以前,將返回值修改成6
		// fmt.Println("f2函數defer中的x =", x) //6
	}()
	return 5 // 返回值(x) = 5   //返回6
}

func f3() (y int) {
	x := 5
	defer func() {
		x++ // defer 訪問變量x,將變量x修改成6
		// fmt.Println("f3函數defer中的x =", x) //6
	}()
	return x // 返回值(y) = 5   //返回5
}
func f4() (x int) {
	defer func(x int) {
		x++ // 這裏修改的defer時傳入的x(0),將其修改成1
		// fmt.Println("f4函數defer中的x =", x) //1
	}(x) // defer 語句調用時傳入x的值爲int類型的默認值0
	return 5 // 返回值(x) = 5   //返回5
}

運行結果

5
6
5
5
五、defer的做用域
  • defer 只對當前協程有效(main 能夠看做是主協程);
  • 當任意一條(主)協程發生 panic 時,會執行當前協程中 panic 以前已聲明的 defer;
  • 在發生 panic 的(主)協程中,若是沒有一個 defer 調用 recover()進行恢復,則會在執行完最後一個已聲明的 defer 後,引起整個進程崩潰;
  • 主動調用 os.Exit(int) 退出進程時,defer 將再也不被執行。
package main

import (
	"fmt"
	// "os"
)

func main() {
	fmt.Println("start")
	// panic("崩潰了")              // defer和以後的語句都再也不執行
	// os.Exit(1)                  // defer和以後的語句都再也不執行
	defer fmt.Println("defer")
	// go func() {
	// 	panic("崩潰了")
	// }()                         // defer不被執行
	// panic("崩潰了")             // defer會執行,但後面的語句再也不執行
	fmt.Println("over")
	// os.Exit(1)                  // defer不被執行
}

8、內置函數

內置函數 介紹
close 主要用來關閉channel
len 用來求長度,好比string、array、slice、map、channel
new 用來分配內存,主要用來分配值類型,好比int、struct。返回的是指針
make 用來分配內存,主要用來分配引用類型,好比chan、map、slice
append 用來追加元素到數組、slice中
panic和recover 用來作錯誤處理

panic和recover

Go語言中目前是沒有異常機制,可是使用panic/recover模式來處理錯誤。 panic能夠在任何地方引起,但recover只有在defer調用的函數中有效。

package main

import (
	"fmt"
)

func main() {
	fmt.Println("start")
	defer func() {
		err := recover()
		if err != nil {
			fmt.Println("recover")
			fmt.Println("活了")
		}
	}()

	panic("panic")
	fmt.Println("over")
}

運行結果

start
recover
活了
相關文章
相關標籤/搜索