【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
活了
相关文章
相关标签/搜索