Go语言学习之路-10-go函数

函数

  • 函数是用来解决重复代码的,它把相同功能的代码:组织、封装到一个函数内
  • 通过函数就可以调用,这个代码块

Go语言中支持函数、匿名函数和闭包,并且函数在Go语言中属于“一等公民”

一等(头等)函数、支持头等函数(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")
}

函数传参

标准参数

函数可以接受参数,然后函数内部的代码可以根据参数来动态计算产出不同的结果

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 不可以函数内部的值只能在函数内部使用
}

局部作用域(局部变量)

  • 函数作用域就是一个局部作用域,在函数内部定义的变量只能在函数使用
  • 逻辑运算也是一个局部作用域比如

局部作用域逻辑运算&向上引用

逻辑运算块内部是一个独立的作用域

// 全局变量
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)
	}
}

函数传参方式

值传递(每次传递赋值一份给函数引用)

什么情况下用值传递?

  • 不想修改传递值,知识当错一个计算条件
  • 这个参数不是很大,赋值一份开销不大
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不要乱用
posted @ 2021-01-21 00:00  天帅  阅读(168)  评论(0编辑  收藏  举报