Go語言學習之路-10-go函數

函數

  • 函數是用來解決重複代碼的,它把相同功能的代碼:組織、封裝到一個函數內
  • 經過函數就能夠調用,這個代碼塊

Go語言中支持函數、匿名函數和閉包,而且函數在Go語言中屬於「一等公民」golang

一等(頭等)函數、支持頭等函數(First Class Function)的編程語言,能夠把函數賦值給變量,也能夠把函數做爲其它函數的參數或者返回值編程

函數的定義

func function_name( [參數列表-能夠寫多個參數] ) (返回類型列表-能夠返回多個類型結果]){
   函數體
}
  • 函數名:由字母、數字、下劃線組成。但函數名的第一個字母不能是數字。在同一個包內,函數名也稱不能重複
  • 參數:參數由參數變量和參數變量的類型組成,多個參數之間使用,分隔
  • 返回值:返回值由返回值變量和其變量類型組成,也能夠只寫返回值的類型,多個返回值必須用()包裹,並用,分隔
  • 函數體:實現指定功能的代碼塊,作了什麼
// ageInfo 返回年齡相關的信息
func ageInfo(name string, age int) (string, int) {
	nextAget := age + 1
	return fmt.Sprintf("%s今年%d了", name, age), nextAget
}

函數的參數和返回值都是可選的,咱們能夠僅僅封裝一個從上到下執行的代碼把它放到函數內部閉包

// sayHello 僅僅是輸出一段文字
func sayHello(){
	fmt.Println("你好啊明天")
}

函數的調用

package main

import "fmt"

func main() {
	// 經過函數名加括號就能夠執行函數
	sayHello()
}

func sayHello() {
	// 重複的代碼能夠用函數封裝
	fmt.Println("這是重複的代碼xxxxxxxxx")
}

函數傳參

標準參數

函數能夠接受參數,而後函數內部的代碼能夠根據參數來動態計算產出不一樣的結果app

package main

import "fmt"

func main() {
	// 經過函數名加括號就能夠執行函數,括號內能夠傳入參數
	result := sayHello("eson")
	fmt.Println(result)  // 輸出結果: Hello eson
}

func sayHello(name string) string {
	// 重複的代碼能夠用函數封裝
	return fmt.Sprintf("Hello %s", name)
}

同類型參數簡寫

func sayHello(name string, add string) string {
	// 重複的代碼能夠用函數封裝
	return fmt.Sprintf("Hello %s", name)
}

上面函數接收了兩個相同類型的參數,能夠經過簡寫來優化下編程語言

func sayHello(name, address string) string {
	// 重複的代碼能夠用函數封裝
	return fmt.Sprintf("Hello %s, address is :%s", name, address)
}

同類型可變參數

package main

import "fmt"

func main() {
	ret := countNumber("eson", 1, 2, 3, 4, 5)
	fmt.Printf("%s", ret) // eson count number :[1 2 3 4 5]
}

// 可變長參數注意只能用在最後一個
func countNumber(name string, number ...int) string {
	// 重複的代碼能夠用函數封裝
	return fmt.Sprintf("%s count number :%v", name, number)
}

函數返回值

Go語言中經過return關鍵字向外輸出返返回值函數

返回單個返回值

package main

import "fmt"

func main() {
	result := calculate(1, 10)
	fmt.Printf("result is:%v", result) // result is:11
}

// 單個返回值
func calculate(x, y int) int {
	return x + y
}

返回多個返回值

package main

import "fmt"

func main() {
	sum, sub := calculate(10, 1)
	fmt.Printf("sum is:%v, sub is: %v\n", sum, sub) // sum is:11, sub is: 9
}

// 返回多個值
func calculate(x, y int) (int, int) {  // 固然這裏的值能夠返回不一樣類型的看實際狀況
	return x + y, x - y
}

返回值命名

package main

import "fmt"

func main() {
	sum, result := calculate(10, 1)
	fmt.Printf("sum is:%v, %v\n", sum, result) // sum is:11, calculate sum is:11
}

// 返回多個值命名
func calculate(x, y int) (sum int, result string) { // 固然這裏的值能夠返回不一樣類型的看實際狀況
	sum = x + y
	result = fmt.Sprintf("calculate sum is:%d", sum)
	return sum, result
}

變量的做用域

  • 全局做用域
  • 局部做用域
  • 包的做用域

全局做用域(全局變量)

函數內部能夠引用全局變量,可是函數內定義的變量只在函數內部有效優化

package main

import "fmt"

func main() {

}

// 全局變量
var num int = 18

func sum() {
	fmt.Printf("全局變量的值是:%d", num) // 函數內部能夠直接使用全局變量
	sumRet := num + 1
	fmt.Printf("%v\n", sumRet)
}

func referSum() {
	// 這裏想引用sum函數的sumRet的值? No 不能夠函數內部的值只能在函數內部使用
}

局部做用域(局部變量)

  • 函數做用域就是一個局部做用域,在函數內部定義的變量只能在函數使用
  • 邏輯運算也是一個局部做用域好比

局部做用域邏輯運算&向上引用

邏輯運算塊內部是一個獨立的做用域url

// 全局變量
var num int = 18

func sum() {
	fmt.Printf("全局變量的值是:%d", num) // 函數內部能夠直接使用全局變量
	sumRet := num + 1
	// if是邏輯運算從if開始到結束它nebula定義的值只能在內部使用,可是它能夠向上引用sumA
	// 函數內部是一個局部做用域能夠被它下層的做用域所調用
	if sumRet > 1 {
		sumA := 10
		fmt.Printf("sumA的值是:%v\n", sumA)
	}
	
	// fmt.Println(sumA) 可是這裏函數做用域想調用本身包含的子做用域代碼塊是不能夠的
}

局部做用域for循環&向上引用

循環邏輯內部也是一個獨立的做用域,而且能夠向上引用它上層的做用域指針

// 全局變量
var num int = 18

func sum() {
	fmt.Printf("全局變量的值是:%d", num) // 函數內部能夠直接使用全局變量
	sumRet := num + 1

	for i := 0; i < sumRet; i++ {
		// 這個i每次循環的時候都會被賦值新的值
		// 在循環內定義的值只有在本次循環內有效
		cycle := i + num
		fmt.Printf("i is:%d\n", cycle)
	}
}

函數傳參方式

值傳遞(每次傳遞賦值一份給函數引用)

什麼狀況下用值傳遞?code

  • 不想修改傳遞值,知識當錯一個計算條件
  • 這個參數不是很大,賦值一份開銷不大
package main

import "fmt"

type person struct {
	name string
	age  int
}

func main() {
	p1 := person{name: "John", age: 18}
	showInfo(p1)
}

// 只想展現的時候能夠傳值
func showInfo(student person) {
	fmt.Printf("學生:%s的年齡是:%d", student.name, student.age)
}

引用【指針】(每次傳遞一個值的指針)

什麼狀況下用指針傳遞?

  • 想修改參數的值
  • 參數佔的空間太大
package main

import "fmt"

type person struct {
	name string
	age  int
}

func main() {
	p1 := person{name: "John", age: 18}
	// 過了1年年齡加1
	happyNewYear(&p1)
	fmt.Printf("name:%s, age is:%d", p1.name, p1.age)
}

func happyNewYear(p *person) {
	p.age++
}

固然結構體能夠直接使用指針變量

package main

import "fmt"

type person struct {
	name string
	age  int
}

func main() {
	p1 := &person{name: "John", age: 18} // 初始化一個person的結構體指針變量
	// 過了1年年齡加1
	happyNewYear(p1)
	fmt.Printf("name:%s, age is:%d", p1.name, p1.age)
}

func happyNewYear(p *person) {
	p.age++
}

高階函數(參數爲函數或返回一個函數)

瞭解函數的類型並在參數內使用

package main

import "fmt"

func main() {
	// sum 這個函數的類型
	fmt.Printf("%T", sum)  // func(int, int) int 
}

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

從上面能夠看出來每一個函數都是有一個類型的,那咱們在函數裏傳函數的時候就能夠這麼寫

package main

import "fmt"

func main() {
	// sum 這個函數的類型
	fmt.Printf("%T\n", sum)                      // func(int, int) int
	fmt.Printf("%v\n", calculate(100, 200, sum)) // 300
}

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

// 定義了一個計算函數,它接收了3個參數
// x,y int類型
// op 是一個函數類型 func(x,y int) int
func calculate(x, y int, op func(x, y int) int) int {
	return op(x, y)
}

定義一個函數類型並在參數內使用

package main

import "fmt"

func main() {
	// sum 這個函數的類型
	fmt.Printf("%T\n", sum)                      // func(int, int) int
	fmt.Printf("%v\n", calculate(100, 200, sum)) // 300
}

func sum(x, y int) int {
	return x + y
}
// 定義一個函數類型並使用它
type fType func(int, int) int

// 定義了一個計算函數,它接收了3個參數
// x,y int類型
// op 是一個函數類型 ftype
func calculate(x, y int, op fType) int {
	return op(x, y)
}

同理返回一個函數

package main

import (
	"errors"
	"fmt"
)

func main() {
	a, b := 100, 200
	var method string
	// 獲取用戶輸入
	_, _ = fmt.Scanln(&method)
	// 獲取放方法
	job, err := calculate(method)
	// 執行
	if err != nil {
		fmt.Printf("%v\n", err)
	} else {
		fmt.Printf("%v\n", job(a, b))
	}

}

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

func sub(x, y int) int {
	return x - y
}

// 定義一個函數類型並使用它
type fType func(int, int) int

func calculate(op string) (fType, error) {
	switch op {
	case "+":
		return sum, nil
	case "-":
		return sub, nil
	default:
		return nil, errors.New("沒法識別的方法")
	}

}

匿名函數

當咱們須要臨時在函數內使用一個函數的時候不能像定義普通函數那樣使用了,這個時候就用到了匿名函數(沒有名稱的函數就是匿名函數)

// 匿名函數沒有名稱
func(參數)(返回值){
    函數體
}

匿名函數賦值變量並執行

package main

import "fmt"

func main() {
	// 匿名函數賦值變量並賦值
	sum := func(x, y int) int { return x + y }

	fmt.Printf("%v\n", sum(100, 200))
}

匿名函數定義自執行

package main

import "fmt"

func main() {
	// 定義一個匿名變量後面跟擴韓直接傳值並運行
	func(x, y int) {
		fmt.Printf("sum : %d + %d result is :%d\n", x, y, x+y)
	}(100, 200)
	// 結果: sum : 100 + 200 result is :300
}

匿名函數多用於實現回調函數和閉包

閉包

閉包就是:

  • 經過高階函數特性,函數能夠包含函數(外部函數),並返回一個函數(內部函數)
  • 當外部函數銷燬後,被內部函數引用的值將不會被銷燬
package main

import (
	"errors"
	"fmt"
)

// 這個函數返回一個func(int)類型的函數
func wapper() func(arg int) {
	x := 0
	return func(y int) {
		x += y
		fmt.Println(x)
	}
}

func main() {
	// 當這個時候外部函數銷燬了,可是x這個變量並無,由於它被內部函數所引用了
	f := wapper()
	f(10)
	f(10)
	f(10)
}
// 結果:
10
20
30

使用場景

  • 裝飾函數
  • 變量引用

裝飾函數例子(計算函數運行時間)

簡單例子

package main

import (
	"fmt"
	"time"
)

func main() {
	// 經過timed函數能夠獲取函數的運行時間
	timed(run)()
}

// 這個函數接收一個函數,並返回一個函數
func timed(f func()) func() {
	return func() {
		start := time.Now()
		f()

		fmt.Printf("這個函數運行須要:%dms\n", time.Since(start)/1000000)
	}
}

func run() {
	time.Sleep(time.Second * 3)
}

實際應用例子

package main

import (
  "fmt"
  "net/http"
  "time"
)

func main() {
  http.HandleFunc("/hello", timed(hello))
  http.ListenAndServe(":3000", nil)
}

func timed(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
  return func(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    f(w, r)
    end := time.Now()
    fmt.Println("The request took", end.Sub(start))
  }
}

func hello(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintln(w, "<h1>Hello!</h1>")
}

變量引用

正常來講要在函數內使用一個變量,要不就定義一個全局變量,在函數內可使用,或者經過閉包的方式調用

簡單例子

package main

import (
	"errors"
	"fmt"
)

// 這個函數返回一個func(int)類型的函數
func wapper() func(arg int) {
	x := 0
	return func(y int) {
		x += y
		fmt.Println(x)
	}
}

func main() {
	// 當這個時候外部函數銷燬了,可是x這個變量並無,由於它被內部函數所引用了
	f := wapper()
	f(10)
	f(10)
	f(10)
}
// 結果:
10
20
30

應用例子

package main

import (
  "fmt"
  "net/http"
)

type Database struct {
  Url string
}

func NewDatabase(url string) Database {
  return Database{url}
}

func main() {
  db := NewDatabase("localhost:5432")

  http.HandleFunc("/hello", hello(db))
  http.ListenAndServe(":3000", nil)
}

func hello(db Database) func(http.ResponseWriter, *http.Request) {
  return func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, db.Url)
  }
}

defer語句

defer是go語言裏的延遲函數,它定義在一個函數內defer後面跟的表達式將會延遲執行

  • 函數內其餘邏輯執行完後在執行defer語句
  • defer語句須要定義在return語句前面
  • 若是有多個defer會倒序執行
package main

import (
	"fmt"
	"time"
)

func main() {
	runTasks()
}

func runTasks() int {
	start := time.Now()
	// 直接在這裏定義一個延遲函數
	// 用來統計這個函數執行了多久
	defer func(stime time.Time) { fmt.Printf("這個函數運行須要:%dms\n", time.Since(start)/1000000) }(start)

	// 開始運行函數邏輯
	fmt.Println("函數內邏輯執行1")
	time.Sleep(time.Second * 2)
	fmt.Println("函數內邏輯執行2")

	return 666
	// 	defer fmt.Println("若是在return語句後面加延遲語句是不能夠的")
}

defer的使用場景

延遲動做

  • 關閉資源
  • 統計執行結果如上面的例子

異常處理

golang目前沒有像其餘語言異常處理相似:try catch,可是能夠經過defer來捕獲 異常舉例來講!~

在go函數內經過panic來觸發異常並退出程序,這裏須要注意不要濫用:panic,panic會讓整個程序掛掉

  • 程序啓動須要初始化數據的時候取不到這個時候須要退出
  • 出現問題就算後面繼續執行也沒有意義這個時候須要退出
  • 客戶端不合法的請求參數返回錯誤參數信息提示便可,讓調用者本身去處理問題,而不是本身panic掛掉

若是一個函數出現了一個未知的異常後,它的處理邏輯是根據調用鏈,不斷向上返回直到碰到recover函數

package main

import (
	"fmt"
)

func main() {
	f1()
}

func f1() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Printf("f1函數捕獲到異常了,異常報錯是:%v\n", err)
		}
	}()

	fmt.Println("這個是第1層函數")
	f2()
}

func f2() {
	panic("f2 觸發了panic")
	fmt.Println("這個是第2層函數") // panic後這裏是不執行的
}

總結下

  • recover() 必須搭配defer使用
  • panic異常後會不斷網上曾調用鏈返回,直到碰到recover()
  • panic不要亂用
相關文章
相關標籤/搜索