Go--函数

函数:是组织好的、可重复使用的、用于执行指定任务的代码块

 

Go语言中支持函数、匿名函数以及闭包函数

 

Go语言中定义函数使用func关键字

func 函数名(参数)(返回值){
    函数体
}

  函数名:由字母、数字、下划线组成。但是函数名的第一个字母不能是数字。在同一个包内,函数名也是不能重名的

  参数:参数由参数变量和参数变量的类型组成,多个参数之间使用逗号进行分隔

  返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,要是有多个返回值的话就必须用括号进行包裹,里面用逗号进行分隔

  函数体:实现指定功能的代码块

 

实现两数之和 的函数:

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

 

函数的参数和返回值都是可选的,我们也可以实现一个既不需要参数也没有返回值的函数

func sayHello() {
	fmt.Println("Hello 沙河")
}

  

 

函数的调用:

  定义了函数之后,我们可以通过函数名加括号的方式调用函数,这里要是在调用有返回值的函数的时候,可以不接受其的返回值

  

参数:

  类型简写:

    函数的参数中如果相邻的变量的类型相同,是可以省略的

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

    上面的代码中,intSum函数有两个参数,这两个参数的类型都是int类型,所以可以省略x的类型,因为y后面有类型说明,x参数的类型也是该类型

  可变参数:

    可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数后面加上...来比标识

    ps:可变参数通常要作为函数的最后一个参数

func intSum2(x ...int) int {
	fmt.Println(x) //x是一个切片
	sum := 0
	for _, v := range x {
		sum = sum + v
	}
	return sum
}

  当有固定参数搭配可变参数一起使用的时候,要把可变参数放在固定参数的后面

func intSum3(x int, y ...int) int {
	fmt.Println(x, y)
	sum := x
	for _, v := range y {
		sum = sum + v
	}
	return sum
}

  本质上函数的可变参数是通过切片来实现的

  

返回值:

  Go语言中的return关键字向外输出返回值

 

多个返回值:

  Go语言中函数支持多个返回值,函数如果有多个返回值的时候就必须使用括号将所有的返回值包裹起来

func calc(x, y int) (int, int) {
	sum := x + y
	sub := x - y
	return sum, sub
}

  

返回值命名:

  函数定义的时候可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回

func calc(x, y int) (sum, sub int) {
	sum = x + y
	sub = x - y
	return
}

  

defer语句:

  Go语言中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回的时候,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句最先被执行

func main() {
	fmt.Println("start")
	defer fmt.Println(1)
	defer fmt.Println(2)
	defer fmt.Println(3)
	fmt.Println("end")
}


start
end
3
2
1

  由于defer语句延迟调用的特性,所以defer语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、解锁以及记录时间等等。

  defer执行时机:

    在Go语言的函数中return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer语句执行的时机就是在返回值赋值操作之后,RET指令执行之前

 

变量作用域:

  全局变量:

    全局变量是定义在函数外部的变量,它在程序整个运行周期内都是生效的。在函数中是可以访问到全局变量的

  局部变量:

    局部变量分为两种:

      在函数内定义的变量无法在函数外部进行调用

      如果局部变量和全部变量重名了,优先访问局部变量

      在if、for中定义的变量也只会在当前语句块中生效

  

函数类型与变量:

  定义函数的类型:

    可以使用type关键字来定义一个函数的类型

type calculation func(int, int) int

    上面的语句定义了一个calculation类型,它是一种函数类型,这种函数接受两个int类型的参数并且返回一个int类型的参数

    简单来说只要满足这个条件的函数都是calculation类型的函数

 

  函数类型变量:

    可以声明函数类型的变量,并且为该变量赋值

func main() {
	var c calculation               // 声明一个calculation类型的变量c
	c = add                         // 把add赋值给c
	fmt.Printf("type of c:%T\n", c) // type of c:main.calculation
	fmt.Println(c(1, 2))            // 像调用add一样调用c

	f := add                        // 将函数add赋值给变量f1
	fmt.Printf("type of f:%T\n", f) // type of f:func(int, int) int
	fmt.Println(f(10, 20))          // 像调用add一样调用f
}

  

高阶函数:

  分为函数作为参数和函数作为返回值两部分 

  函数作为参数:

func add(x, y int) int {
	return x + y
}
func calc(x, y int, op func(int, int) int) int {
	return op(x, y)
}
func main() {
	ret2 := calc(10, 20, add)
	fmt.Println(ret2) //30
}

  函数作为返回值:    

func do(s string) (func(int, int) int, error) {
	switch s {
	case "+":
		return add, nil
	case "-":
		return sub, nil
	default:
		err := errors.New("无法识别的操作符")
		return nil, err
	}
}

  

package main

import "fmt"

type testInt func(int) bool // 声明了一个函数类型

func isOdd(integer int) bool {
	if integer%2 == 0 {
		return false
	}
	return true
}

func isEven(integer int) bool {
	if integer%2 == 0 {
		return true
	}
	return false
}

// 声明的函数类型在这个地方当做了一个参数

func filter(slice []int, f testInt) []int {
	var result []int
	for _, value := range slice {
		if f(value) {
			result = append(result, value)
		}
	}
	return result
}

func main(){
	slice := []int {1, 2, 3, 4, 5, 7}
	fmt.Println("slice = ", slice)
	odd := filter(slice, isOdd)    // 函数当做值来传递了
	fmt.Println("Odd elements of slice are: ", odd)
	even := filter(slice, isEven)  // 函数当做值来传递了
	fmt.Println("Even elements of slice are: ", even)
}

  

  函数当作值和类型在我们写一些通用接口的时候非常有用,上面的testInt是一个函数类型,然后两个filter函数的参数和返回值与testInt这个类型是一个函数类型,然后两个filter函数的参数和返回值与testInt类型是一样的,但是我们可以实现很多种的逻辑,这样会使我们的程序变得非常灵活

 

Panic和Recover

  Go没有像Java那样的异常处理机制,它不能抛出异常,而是使用了panic和recover机制。记着这个应该当成最后的手段来使用,也就是说你的代码中应该没有甚至很少有panic的东西。这是个很强大的东西。

  Panic:

    是一个内建函数,可以中断原有的控制流程,进入一个panic状态中。当函数F调用panic,函数F的执行被中断,但是F中的延迟函数会正常执行,然后F返回到调用它的地方,在调用的地方,F的行为就像调用了panic。这一过程继续向上,直到发生panic的goroutine中所有调用的函数返回,此时程序退出。panic可以直接调用panic产生。也可以由运行时错误产生,例如访问越界的数组。

var user = os.Getenv("USER")

func init() {
	if user == "" {
		panic("no value for $USER")
	}
}

  Recover:

    是一个内建函数,可以让进入panic的goroutine恢复过来。recover仅仅在延迟函数中有效。在正常的执行过程中,调用recover会返回nil,并且没有其它任何的效果。如果当前的goroutine陷入了panic状态,调用recover可以捕获到panic的输入值,并且恢复正常的执行。

  下面这个函数检查作为其参数的函数在执行的时候是否会产生panic

func throwsPanic(f func()) (b bool) {
	defer func() {
		if x := recover(); x != nil {
			b = true
		}
	}()
	f() //执行函数f,如果f中出现了panic,那么就可以恢复回来
	return
}

 

main函数和init函数:

  Go里面有两个保留函数:init函数(能够应用与所有的package)和main函数(只能应用于package main)。这两个函数的定义是不能有任何的参数和返回值的。虽然一个package里面可以写任意多个init函数,但是这不论是对于可读性还是对于以后的可维护性来说,都还是推荐在一个package中只写一个init函数

  Go程序会自动调用init()和main(),所以你不需要在任何地方调用这两个函数。每个package中的init函数都是可选的,但是package main就必须包含一个main函数

  程序的初始化和执行都起始于main包。如果main包还倒入了其它的包,那么就会在编译的时候将它们一次进行导入。有时有一个包会被多个包同时的导入,那么它就会被导入一次(例如很多包都会用到fmt包,那么它只会被导入一次,因为没有必要导入很多次)。当一个包被导入时,还倒入了其它的包,那么会先将其它包导入进来,然后再对这些包中的变量和常量进行初始化,接着开始执行init函数(如果有的话就会执行),以此类推。等所有被导入的包加载完毕了,就会开始对main包中的包级常量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后执行main函数

 

 main函数引入包初始化的整个流程

 

import

  在写Go代码的时候会经常用到import命令来导入包文件,我们经常可以看到

import(
    "fmt"
)

  然后我们可以

fmt.Println("hello world")

  上面这个fmt是Go的标准库,其实就是去GOROOT环境变量执行目录下加载该模块,当然Go的import还支持相对路径和绝对路径来加载自己写的模块:

    相对路径:

import “./model” //当前文件同一目录的model目录,但是不建议这种方式来import

    绝对路径:

import “shorturl/model” //加载gopath/src/shorturl/model模块

  还有一些常用的导入的方式

  点操作

import(
     . "fmt"
 )

    点操作就是这个包导入之后在你调用这个包的函数的时候,你可以省略包的前缀名

  别名操作

import(
     f "fmt"
 )

    调用包函数的时候前缀编程我们自己定义的前缀

  _操作

    _操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数

 

匿名函数、闭包

  匿名函数:

    函数当然还可以作为返回值,但是在Go语言中函数内部不能在像之前那样定义函数了,只能定义匿名函数。匿名函数顾名思义就是没有函数名的函数

func(参数)(返回值){
    函数体
}

    匿名函数因为没有函数名,所以没有办法像普通函数那样进行调用,所以匿名函数需要保存到某个变量或者作为立即执行的函数:

func main() {
	// 将匿名函数保存到变量
	add := func(x, y int) {
		fmt.Println(x + y)
	}
	add(10, 20) // 通过变量调用匿名函数

	//自执行函数:匿名函数定义完加()直接执行
	func(x, y int) {
		fmt.Println(x + y)
	}(10, 20)
}

  

 

 

 

 

 

 

 

 

posted @ 2019-11-08 21:01  tulintao  阅读(220)  评论(0编辑  收藏  举报