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不要乱用
相关文章
相关标签/搜索