Go语言入门之函数

Go语言之函数

函数这种语法元素的诞生,源于将大问题分解为若干小任务与代码复用;函数是唯一一种基于特定输入,实现特定任务并可返回任务执行结果的代码块。

1.函数定义

// func:函数由 func 开始声明
// function_name:函数名:唯一,首字母大写可以在包外引用,小写则包内可见
// parameter list:参数列表,可有可无,可少可多,逗号分隔
// return_types: 返回值的类型定义,可省可多,多个返回值需要用括号包裹,逗号分隔
func function_name( [parameter list] ) [return_types] {
   函数体
}

(1)函数参数

参数可以不传,参数也可以传递多个,也可以参数数量不固定

函数的参数一般称为形参,而实际调用时使用的参数称为实参

函数参数的传递采用的是值传递的方式

值传递就是将实际参数在内存中的表示逐位拷贝(Bitwise Copy)到形式参数中

  • 1.自身传递:代表类型有整型,数组,结构体,拷贝自身数据内容
  • 2.引用传递:代表类型有string,slice,map,不拷贝实际数据内容而拷贝自身地址,故开销固定,称之为浅拷贝
// 不传参数
func num() (int, int) {
	return 10, 20
}

// 传多个参数
func nums(a, b int) (int, int) {
	return b, a
}

// 不固定参数 只能写在最后
func nums(a int, x ...int) (int, int) {
	return x[0], x[1]
}

(2)函数返回值

返回值可以没有,可以是一个,也可以是多个

// 无返回值
func num(x, y int) {
}

// 多返回值
func nums(x, y int) (int, int, string) {
	return x, y, ""
}

// 具名返回值 相当于局部变量 return隐式返回
func nums2(x, y int) (a int, b int, c string) {
	a = 10
	b = 20
	c = "hehe"
	return
}

2.高阶函数

(1)函数可以作为数据类型

func main() {
    // 定义a,数据类型函数
	var a func(x, y int) (int, int)
    // 构造函数体(函数实例化)
	a = func(x, y int) (int, int) {
		return y, x
	}
    // 传参并输出
	fmt.Println(a(10, 20))
}
// 结果 20 10

(2)函数可以作为返回值

func main() {
    // 最后必须加括号,不然a得到的是函数的地址而非具体返回值
    a := re(1, 2)()
	fmt.Println(a)
}

func re(a, b int) func() int {
    // 构造返回值函数体
	return func() int {
		return (a + b)
	}
}

(3)函数可以作为参数传递

func main() {
    // 定义a,数据类型函数
	var a func(x, y int) (int, int)
    // 构造数据类型函数体
	a = func(x, y int) (int, int) {
		return y, x
	}
    // 传参
	r := call(a)
    // 输出 20 + 10 的结果
	r(10)
}
// call函数,传参为函数,返回值为函数
func call(a func(x, y int) (int, int)) func(x int) {
    // 取出20
	y, _ := a(10, 20)
    // 返回值中打印
	return func(x int) {
		fmt.Println(y + x)
	}
}

(4)匿名函数

匿名函数就是没有函数名的函数,它可以在定义的地方直接使用,或者将其赋值给变量进行后续调用。匿名函数通常用于需要在函数内部定义并使用的简单逻辑块。匿名函数多用于实现回调函数和闭包。

func main() {
	// 这是一个匿名函数
	funA := func() int {
		return 10
	}
	// 其实在这里funA就是函数的名字,可以如此调用
	funA()
    
	// 这是一个匿名函数调用 可以不用将函数声明为一个变量再使用
	func() {
		fmt.Println("这是一个匿名函数")
	}()
}

(5)函数闭包

闭包可以简单理解为函数内部的匿名函数,其引用了函数体之外的变量,可以简单理解为由函数+引用环境组成

闭包允许函数内部定义的匿名函数捕获并访问函数外部的变量,形成一个封闭的作用域。

这种特性使得函数成为第一类对象,能够方便地进行参数传递和返回值使用

举个形象的例子:

你想吃邻居家树上的苹果,但是无法去他家院子

所以你叫出邻居家小孩,和他搞好关系,让他给你摘苹果

这个小孩就是闭包函数,苹果就是局部变量

// 本函数没有任何实用性,只是展示知识点
func main() {
	// 创建一个玩家a
	a := player("张三")
	// 给他一个血包b
	b := 100
	// 返回玩家的名字,初始血量和目前血量
	name, hp, x := a(b)
	// 打印值
	fmt.Println(name, hp, x)
    
    // 或者可以直接点
    //fmt.Println(a(b))
}

// 创建一个玩家生成器, 输入名称, 输出生成器
func player(name string) func(x int) (string, intint) {
	// 初始血量为100
	hp := 100
	// 返回创建的闭包
	return func(x int) (string, intint) {
        // 可以捕获变量hp将初始血量调整为200
        hp = 200
        x += hp
		// 将变量引用到闭包中
		return name, hp, x
	}
}

从这个例子可以看出,闭包函数的一些特点

  • 1.可以让我们访问到在其周围函数中定义的变量

  • 2.更改捕获到的变量

  • 3.逃逸变量,变量被闭包捕获后必须分配在堆上,确保函数被返回后仍可以访问它

    但是这个变量不被闭包之外的其他代码使用,因此可以用编译器优化使其分配在栈中

缺点:

  • 内存泄露:闭包可能导致其引用的外部变量生命周期延长,如果不小心可能会造成内存泄漏。
  • 循环引用:如果闭包捕获的变量包含闭包自身的引用,可能会形成循环引用,需要注意避免。
  • 并发安全:如果闭包在并发环境中被多个协程使用,而闭包又操作共享变量,则必须确保并发安全,比如通过互斥锁

(6)内置函数

内置函数 描述
close 关闭一个通道(channel)
len 返回字符串、数组、切片、字典或通道的长度
cap 返回切片的容量,通道的缓冲区大小
new 为类型分配内存并返回指向该类型的指针
make 用于创建切片、映射和通道
append 将元素追加到切片的末尾
copy 将源切片的元素复制到目标切片
delete 从字典中删除指定键的键值对
panic 触发一个运行时错误。
recover 从 panic 中恢复,用于处理运行时错误

2.defer 语句

在Go语言中,defer 是一种用于延迟执行函数调用的关键字

(1)defer定义

延迟调用:

可以让函数或方法在当前函数执行完毕后,在return赋值之后返回之前执行,同时也在panic之前执行

(注:跟在defer后的函数,我们一般称之为延迟函数,无论正常还是错误defer都会被执行)

func main() {
	x := 10
	defer func() {
		x++
        //这里后打印11
		fmt.Println("我后执行:", x)
	}()
    //这里先打印10
	fmt.Println("我先执行:", x)
	return
}

(2)defer底层实现

type _defer struct {
   siz     int32    // 参数和返回值的内存大小
   started bool
   heap    bool       // 是否分配在堆上面
   openDefer bool     // 是否经过开放编码优化
   sp        uintptr  // sp 计数器值,栈指针
   pc        uintptr  // pc 计数器值,程序计数器
   fn        *funcval // defer 传入的函数地址,也就是延后执行的函数
   _panic    *_panic  // defer 的 panic 结构体
   link      *_defer  // 同一个协程里面的defer 延迟函数,会通过该指针连接在一起
}

defer逆序执行的原因:

link指针指向的是defer单链表的头,每次插入defer都是从表头插入,每次执行也是从表头去取

defer如何实现延迟:

defer代码在编译后会有两个方法,分别负责创建和执行

  • 1.deferproc():在defer的声明处调用,将defer函数存于goroutine的链表中负责保存要执行的函数,称为defer注册
  • 2.deferreturn(),在return指令执行跳出函数前调用,负责将defer函数从链表中取出执行

可以简单理解为在defer声明时插入了一个deferproc()函数保存数据,在return内部执行退出之前插入后了一个deferreturn()函数

(3)defer规则

  • 1.延迟函数的参数在defer语句出现时就已经确定
  • 2.延迟函数执行按后进先出顺序执行(类似于栈), 即先出现的defer最后执行
  • 3.延迟函数可以操作主函数的具名返回值
  • 4.如果 defer 执行的函数为 nil, 那么会在最终调用函数的产生 panic
  • 5.defer一定要定义在return或panic之前,否则会不执行

规则1: 延迟函数的参数在defer语句出现时就已经确定

// 例子1
var a = 1
defer fmt.Println(a)
a = 2
return
// 这段代码最后会打印1而不是2,如果将defer后改成函数包裹,则输出2

// 例子2
var b = 1
defer func(a int) {
	b += a
	fmt.Println(b)
}(b + 1)
b = 10
return
// 猜猜b是几?
// 首先defer预加载参数,函数传入的实参为2
// 其次全部执行结束后执行函数此时b为10,所以就是10+2

规则2: 延迟函数执行按后进先出顺序执行, 即先出现的defer最后执行

func main() {
	x := 10
	defer func(x int) {
		fmt.Println("我最后执行:", x)
	}(x)
	defer func(x int) {
		fmt.Println("我再执行:", x)
	}(x)
	x++
	fmt.Println("我先执行:", x)
	return
}

规则3: 延迟函数可以操作主函数的具名返回值

func main() {
    // 打印结果为:2 
    // return i 并不是一个原子操作
    // return会分两步 1. 设值 2 return 所以result为先被赋值为i=1 
	x := deferTest()
	fmt.Println(x)
}
func deferTest() (result int) {
	i := 1
	defer func() {
		result++
	}()
	return i
}

规则4: 如果 defer 执行的函数为 nil, 那么会在最终调用函数的产生 panic

var a func()

func deferTest() *int {
	i := 1
	defer a()
	return &i
}

规则5: defer一定要定义在return或panic之前,否则会不执行

(4)使用场景

一般用于资源的释放和异常的捕捉((比如:文件打开、加锁、数据库连接、异常捕获)

  • 1.当函数执行完毕释放资源时

  • 2.打开网络连接socket的时候

  • 3.连接数据库时需要defer关闭数据库连接,不然会造成连接数过多

  • 4.可以用来捕获panic异常,让程序正常执行

相关推荐

  1. Go语言入门函数

    2024-07-20 21:48:03       27 阅读
  2. Go语言入门Go语言的方法,函数,接口】

    2024-07-20 21:48:03       62 阅读
  3. Go语言入门数组切片

    2024-07-20 21:48:03       41 阅读
  4. Go语言入门流程控制简述

    2024-07-20 21:48:03       32 阅读
  5. Go语法函数 defer使用

    2024-07-20 21:48:03       46 阅读
  6. Go 语言函数

    2024-07-20 21:48:03       80 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-20 21:48:03       138 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-20 21:48:03       152 阅读
  3. 在Django里面运行非项目文件

    2024-07-20 21:48:03       128 阅读
  4. Python语言-面向对象

    2024-07-20 21:48:03       140 阅读

热门阅读

  1. swift小知识点

    2024-07-20 21:48:03       22 阅读
  2. 项目中MyBatis要引入的内容

    2024-07-20 21:48:03       28 阅读
  3. ccf-csp认证--仓库规划

    2024-07-20 21:48:03       28 阅读
  4. Kraken代码阅读(一)

    2024-07-20 21:48:03       22 阅读
  5. Oracle数据库 v$access

    2024-07-20 21:48:03       25 阅读
  6. ZooKeeper 部署

    2024-07-20 21:48:03       25 阅读
  7. 【Webpack】提高打包速度

    2024-07-20 21:48:03       26 阅读
  8. Python(re模块的具体使用)

    2024-07-20 21:48:03       30 阅读
  9. 2024 暑假友谊赛 2

    2024-07-20 21:48:03       27 阅读