Golang 函数

本文详细介绍了Go语言中的函数,包括函数的定义与调用、变量作用域(全局与局部)、参数传递(传值与引用类型的区别)、返回值以及函数的高阶用法如函数类型、函数变量、高阶函数、递归、匿名函数和闭包。此外,还讲解了defer语句的使用和panic/recover异常处理机制。
摘要由CSDN通过智能技术生成

一、函数的简介

1. 函数介绍

函数在 Go 中十分重要,是 Go 语言的 “一等公民”。我们常常将重复的功能代码抽取出来并封装成一个函数,可以实现对该功能的重复调用,从而减少代码文件的冗余,提高代码的可维护性。

2. 函数的使用

2.1 定义函数

Go 语言中使用 func 关键字来定义一个函数,具体格式如下:

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

一个完整的函数主要由以下几个部分组成:

  • 函数名:由字母、数字、下划线组成。函数名的第一个字母不能是数字,且在同一个包内,函数名不能重名
  • 参数:由参数变量和参数变量的类型组成,多个参数之间使用英文逗号分隔
  • 返回值:由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用英文小括号包裹,并用英文逗号分隔
  • 函数体:实现指定功能的代码块

函数定义实例:定义一个实现两数相加功能的函数。

package main

import "fmt"

// 两个数字相加
func addNum(x int, y int) int {
	ret := x + y
	return ret
}

2.2 调用函数

可以使用 函数名+英文括号 来实现对函数的调用。

package main

import "fmt"

// 两个数字相加
func addNum(x int, y int) int {
	ret := x + y
	return ret
}

func main() {
	// 调用函数上述定义的函数
	addRet := addNum(10, 5)
	fmt.Println("相加的和为:", addRet)
}
2.3 变量作用域

变量作用域指的是变量可以生效的范围。主要分为以下两种:

2.3.1 全局变量

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

package main

import "fmt"

// 声明一个全局的变量
var age int

func main() {
	age = 18  // 函数内部使用全局的变量
	fmt.Println(age)
}

2.3.2 局部变量

局部变量又可以分为以下两类:

  • 函数内定义的变量,该变量无法在全局或其他函数内部使用
package main

import "fmt"

func testLocalVar(){
	var name string
	name = "cdc"
	fmt.Println(name)
}

// 在全局中无法使用局部变量
name = "ctt"

func main() {
	testLocalVar()

	// 在函数内部无法使用其他函数内部的变量
	name = "ctt"
}
  • 语句块中的局部变量,该变量是条件判断和循环中临时定义的变量,只在判断和循环语句中生效
package main

import "fmt"

func main() {
	for i := 0; i < 10; i++ {
		fmt.Println(i)
	}

	fmt.Println(i)  // 脱离for循环,i 变量不生效
    
	
	if j:=10; j < 100 {
		fmt.Println(j)
	}
	fmt.Println(j)  // 脱离if判断,j 变量不生效
    

	switch k:=2; k{
	case 1:
		fmt.Println("aa")
	case 2:
		fmt.Println("bb")
	default:
		fmt.Println("cc")
	}
	fmt.Println(k)  // 脱离switch判断,k 变量不生效
}
2.3.3 查找顺序

当全局变量和局部变量重名时,优先使用局部变量,局部内找不到变量时,再去全局找。

package main

import "fmt"

var address = "南京"
var country = "中国"

func main() {
	var address = "上海"
	fmt.Println(address)  // 上海
	fmt.Println(country)  // 中国
}

二、函数的构成

1. 函数的参数

函数可以接收0个或多个参数,有时不确定具体的参数个数时,可以使用可变长参数接收,参数需要指定数据类型。定义函数时的参数叫形参,调用函数时传入的参数叫实参。函数的参数是以传值方式传参的,传入到函数内部的参数相当于对原值进行一次拷贝,即在函数内部修改传入的参数值,不会影响原来的值。

1.1 普通传参
package main

import "fmt"

func say(name string) {

	fmt.Println("hello, ", name)
}

func main() {
	say("cdc")
}
1.2 传入多个参数
package main

import "fmt"

func say2(age int, name string) {
	fmt.Printf("hello, my name is %v and I'm %v years old\n", name, age)
}

func main() {
	say2(18, "cdc")  // hello, my name is cdc and I'm 18 years old
}

对于相同类型的参数,可以进行类型简写

package main

import "fmt"

func say3(name, address string, age int) {
	fmt.Printf("hello, my name is %v and I'm %v years old\n", name, age)
	fmt.Println(address)
}

func main() {
	say3("cdc", "南京", 18)
}
1.3 可变长参数

可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数名后加 ... 来标识,接收的参数会作为元素存储在一个切片中。可变参数要作为函数的最后一个参数且参数的数据类型要一致。

package main

import "fmt"

func say4(age int, args ...string){
	fmt.Println(args)

	for index, value := range args {
		fmt.Println(index, value)
	}
}

func main() {
	say4(18, "cdc", "南京", "中国")
}
1.4 参数是传值方式传递的
package main

import "fmt"

func do(array [4]int) {
	array[0] = 100
	fmt.Println("do内部的array:", array)  // do内部的array: [100 2 3 4]
}

func main() {
	numArray := [4]int{1, 2, 3, 4}
	do(numArray)
	fmt.Println("main中的array:", numArray)  // main中的array: [1 2 3 4]
}

注意mapslicechannelinterface 等类型本身是属于引用类型,对于这些类型而言,拷贝的实际上是这些类型指向底层数据结构的指针,因此对这些类型变量进行修改,可能会导致原来的值也跟着改变。例如:

package main

import "fmt"

func do2(slice []int) {
	slice[0] = 100
	fmt.Println("do内部的slice:", slice)  // do内部的slice: [100 2 3 4]
}


func main() {
	numSlice := []int{1, 2, 3, 4}
	do2(numSlice)
	fmt.Println("main中的slice:", numSlice)  // main中的slice: [100 2 3 4]
}

2. 函数的返回值

2.1 无返回值

函数无返回值时不能使用变量接收,强行接收编译不通过

package main

import "fmt"

func demo1() {
	fmt.Println("aaa")
}

func main() {
	// demo1() doesn't return a value
	ret1 := demo1()  
}
2.2 有一个返回值
package main

import "fmt"

func demo2() int {
	age := 18
	return age
}

func main() {
	ret := demo2()
	fmt.Println(ret)
}
2.3 有多个返回值
package main

import "fmt"

// 返回多个值
func demo3() (int, string, string) {
	age := 18
	name := "cdc"
	address := "南京"

	return age, name, address
}

func main() {
	ret1, ret2, ret3 := demo3()
	fmt.Println("age: ", ret1)
	fmt.Println("name: ", ret2)
	fmt.Println("address: ", ret3)
}

2.4 指定返回内容

指定返回内容相当于声明了变量,并将返回值赋值给了变量,因此指定的返回值变量可以使用,不用声明。

package main

import "fmt"

// 指定返回的内容,变量不需要声明
func demo4() (name string, age int) {
	age = 18
	name = "cdc"

	return name, age
}

func main() {
	ret1, ret2 := demo4()
	fmt.Println("name: ", ret1)
	fmt.Println("age: ", ret2)
}

2.5 指定返回内容,但是未使用
package main

import "fmt"

// 指定返回内容,但是未使用
func demo5() (name string, age int) {
	age = 18
	name = "cdc"
	return //  等价于 return name, age
}

func main() {
	ret1, ret2 := demo5()
	fmt.Println("name: ", ret1)
	fmt.Println("age: ", ret2)
}
2.6 覆盖命令返回值
package main

import "fmt"

// 覆盖命令返回值
func demo6() (name string, age int) {
	a := 18
	b := "cdc"

	return b, a
}

func main() {
	ret1, ret2 := demo6()
	fmt.Println("name: ", ret1)
	fmt.Println("age: ", ret2)
}

三、函数的高阶用法

1. 函数类型与函数变量

1.1 定义函数类型

可以使用 type 关键字来声明一个函数类型,例如:

type cal func(int, int) int

上述语句定义了一个 cal 类型,它是一种函数类型,该类型需要接收两个整型数据,并返回一个整型数据。从理论上来说,只要能满足上述条件的函数,都是 cal 类型的函数。

1.2 函数变量

以下示例中,add 函数和 sub 函数都是满足 cal 函数类型的,因此可以将两个函数作为值赋值给 cal 类型的变量。

package main

import "fmt"

type cal func(int, int) int

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

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

func main() {

	var func1 cal
	var func2 cal

	func1 = add  // add函数满足cal类型,所以可以将add赋值给cal类型变量func1
	ret1 := func1(10, 5)
	fmt.Println(ret1)

	func2 = sub  // sub函数满足cal类型,所以可以将sub赋值给cal类型变量func2
	ret2 := func2(10, 5)
	fmt.Println(ret2)
}

2. 高阶函数

2.1 函数作为参数

函数可以作为其他函数的参数:

package main

import "fmt"

// 函数可以作为其他函数的参数
func add(x, y int) int {
	return x + y
}

// 接收三个参数:两个整型数据,一个函数
func f(x, y int, op func(int, int) int) int {
	return op(x, y)
}

func main() {
	ret := f(10, 5, add)
	fmt.Println(ret)
}

2.2 函数作为返回值
package main

import "fmt"

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

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

// 函数可以作为其他函数的返回值
func cal(op string) func(int, int) int {
	switch op {
	case "add":
		return add
	case "sub":
		return sub
	default:
		fmt.Println("操作不合法")
		return nil

	}
}

func main() {
	f := cal("sub")

	if f != nil{
		ret := f(10, 5)
		fmt.Println(ret)
	}
}

3. 递归函数

函数内部调用自身的函数称为递归函数。使用递归函数时,一定要有可以让函数调用停止的条件,否则函数会一直调用下去,从而进入死循环。

递归的错误用法:

package main

import "fmt"

func recursionErrorDemo() {
	fmt.Println("aaaaa")
	recursionErrorDemo()

}

func main() {
	recursionErrorDemo()
}

递归示例1:斐波那契数列,计算公式为 f(n) = f(n-1) + f(n-2),且 f(2) = f(1) = 1

package main

import "fmt"

// 斐波那契数列
func fibDemo(n int) int {
	if n == 1 || n == 2 {
		return 1
	}

	return fibDemo(n-1) + fibDemo(n-2)

}

func main() {
	ret := fibDemo(5)
	fmt.Println("斐波那契的值:", ret)
}

递归示例2:求阶乘

package main

import "fmt"

// 求阶乘
func factorialDemo(n int) int {
	if n == 0 {
		return 1
	}

	return n * factorialDemo(n-1)
}

func main() {
	ret := factorialDemo(5)
	fmt.Println("阶乘计算结果:", ret)
}

4. 匿名函数

Go 语言中,在函数内部无法再次定义函数,因此想要实现函数的嵌套,必须借助匿名函数。所谓匿名函数,即没有函数名的函数,没办法像普通函数那样调用,所以匿名函数需要保存到某个变量或者作为立即执行函数。匿名函数多用于实现回调函数和闭包。

匿名函数的签名如下:

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

匿名函数的使用示例如下:

package main

func main() {

	// 使用变量接收匿名函数
	add := func(x, y int) int {
		return x + y
	}
	add(10, 5)

	// 匿名函数作为立即执行函数使用
	func(x, y int) int {
		return x + y
	}(10, 20)
}

5. 函数闭包

闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+引用环境。例如:

package main

import "fmt"

func add(x int) func(int) int {

	return func(y int) int {
		x += y
		return x
	}
}

func main() {
	f := add(10)
	fmt.Println(f(10))  // 20
	fmt.Println(f(20))  // 40
	fmt.Println(f(30))  // 70
	fmt.Println(f(40))  // 110
}

我们对上述的示例进行一个解析,add 函数内部返回了一个匿名函数,匿名函数内部使用到了外部的 add 函数接收到的参数 x,因此内部的匿名函数满足闭包条件。调用函数时,我们用变量 f 去接收了 add 函数的返回值,即此时的变量 f 就是内部的那个匿名函数,因此在 f 的生命周期内,外层的 add 函数接收的变量 x 会一直生效。

闭包进阶示例1:判断文件名是否带指定后缀,不带则补全后缀。

package main

import (
	"fmt"
	"strings"
)

func makeSuffix(suffix string) func(string) string {
	return func(name string) string {
		if !strings.HasSuffix(name, suffix) {
			return name + suffix
		}

		return name
	}
}

func main() {
	txtJudgeFunc := makeSuffix(".txt")
	jpgJudgeFunc := makeSuffix(".jpg")

	ret1 := txtJudgeFunc("test")
	ret2 := jpgJudgeFunc("test")

	fmt.Println(ret1)  // test.txt
	fmt.Println(ret2)  // test.jpg
}

闭包进阶示例2:对基础数进行运算

package main

import s"fmt"

func calc(base int) (func(int) int, func(int) int) {
	add := func(i int) int {
		base += i
		return base
	}

	sub := func(i int) int {
		base -= i
		return base
	}

	return add, sub
}

func main() {
	f1, f2 := calc(10)
	fmt.Println(f1(1))  // 11
	fmt.Println(f1(2))  // 13
	fmt.Println(f2(3))  // 10
	fmt.Println(f2(4))  // 6
}

6. defer 语句

defer 语句会将其后面跟随的语句进行延迟处理。在Go语言的函数中,return 语句在底层并不是原子操作,它分为给返回值赋值和RET 指令两步。而 defer 语句执行的时机就在返回值赋值操作后,RET 指令执行前。

在这里插入图片描述
在这里插入图片描述

defer 语句使用示例:

package main

import "fmt"

func main() {

	fmt.Println("start connect ...")
	defer fmt.Println("close connect")
	fmt.Println("do something ....")
}

/*
	执行结果:
	start connect ...
	do something ....
	close connect

*/

如果有多个 defer ,按定义的逆序进行执行,也就是说,先被 defer 的语句最后被执行,最后被 defer 的语句,最先被执行。

package main

import "fmt"

func deferFun() {

	fmt.Println("statrt ...")
	defer fmt.Println("step1")
	defer fmt.Println("step2")
	defer fmt.Println("step3")
	defer fmt.Println("step4")
	fmt.Println("end ...")
}

func main() {
	deferFun()
}

/*
	执行结果:
	statrt ...
    end ...
    step4
    step3
    step2
    step1
*/

7. init 函数

Go 语言中有一个特殊的函数 init,该函数主要有以下特点:

  • 先于 main 函数自动执行,且不能被其他函数调用;
  • 没有输入参数和返回值;
  • 每个包/每个源文件下可以有多个 init 函数;
  • 同一个包的 init 执行顺序,Go 中没有明确定义,编程时要注意程序不要依赖这个执行顺序;
  • 不同包的 init 函数按照包导入的依赖关系决定执行顺序。

init 函数使用示例:

package main

import "fmt"

func init(){
	fmt.Println("init1...")
}

func init()  {
	fmt.Println("init2...")
}

func init()  {
	fmt.Println("init3...")
}

func main() {
	fmt.Println("这里是main函数...")
}

/*
	执行结果:
	init1...
    init2...
    init3...
    这里是main函数...
*/

8. panic/recover

Go 语言中常常使用 panic/recover 模式来处理代码中的异常场景, panic 可以在任何地方引发,但 recover 只有在 defer 调用的函数中有效,因此 recover() 必须搭配 defer 使用,且 defer 一定要在可能引发 panic 的语句之前定义。

package main

import "fmt"

func A() {
	fmt.Println("function A")
}

func B() {
	panic("panic in function B")
}

func C() {
	fmt.Println("function C")
}

func main() {
	A()
	B()
	C()
}

上述代码会引发 panic ,程序崩溃,异常退出:

function A
panic: panic in function B

goroutine 1 [running]:
main.B(...)
        F:/GitProject/Code/GoCode/04_函数/10_panic_and_recover/main.go:10
main.main()
        F:/GitProject/Code/GoCode/04_函数/10_panic_and_recover/main.go:19 +0xa5

可以通过 recover 将程序恢复回来,继续往后执行:

package main

import "fmt"

func A() {
	fmt.Println("function A")
}

func B() {
	defer func(){
		err := recover()

		//如果程序出出现了panic错误,可以通过recover恢复过来
		if err != nil {
			fmt.Println("recover in function B")
		}
	}()

	panic("panic in function B")
}

func C() {
	fmt.Println("function C")
}

func main() {
	A()
	B()
	C()
}

结果输出:

function A
recover in function B
function C
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值