当前位置: 首页 > article >正文

13 go语言(golang) - 函数

函数

在 Go 语言中,函数是代码的基本组织单元。它们用于执行特定任务,并且可以接收输入参数和返回输出结果。

函数是一种一等公民(first-class citizen),这意味着函数可以像其他任何值一样被使用和操作。函数可以被赋值给变量,作为参数传递给其他函数,也可以作为其他函数的返回值。

1、函数定义

一个简单的函数定义包括关键字 func、函数名、参数列表、返回值列表(可选)以及函数体。

func 函数名(参数列表) 返回值类型 {
    函数体
}
  • func:用于声明一个新函数。
  • 函数名:这是函数的名称。
  • 参数列表:表示该函数接的参数。
  • 返回值类型:表示该函数返回的结果类型,没有返回时可以省略。
  • 函数体:包含了实际执行操作的代码块。

2、参数与返回值

2.1 多参数与多返回值

Go 支持多个参数和多个返回值:

func add(a int, b int, c int, d int) (int, int) {
	x := a + b
	y := c + d
	return x, y
}

func Test1(t *testing.T) {
	println(add(1, 2, 3, 4))
}

2.2 命名返回值

可以为返回值命名,这样在使用时可以直接对其进行赋值,不需要显式地使用 return value1, value2...

func add2(a int, b int, c int, d int) (x int, y int) {
	x = a + b
	y = c + d
	return // 隐式地以命名变量作为结果进行return 
}

func Test2(t *testing.T) {
	println(add2(1, 2, 3, 4))
}

2.3 可变参数

使用省略号语法来处理不定数量的输入参数:

// 省略变量名,不会报错,但是也用不到变量了,没有意义
//
//	func add3(...int) int {
//		return 1
//	}
func add3(numbers ...int) int {
	var result int
	for _, number := range numbers {
		result += number
	}
	return result
}

func Test3(t *testing.T) {
	println(add3(1, 2, 3, 4))
}

3、函数作为一等公民

Go 中,函数是一等公民意味着函数可以像任何其他值一样被传递和操作,可以赋给变量,也可以作为其他函数的参数或从其他函数中返回。

3.1 将函数作为变量存储

func sayHi(name string) {
	fmt.Println("你好:" + name)
}

func Test4(t *testing.T) {
	// 将函数作为变量存储
	f1 := sayHi
	f1("小明")

	// 匿名函数
	f2 := func(name string) {
		fmt.Println("你好:", name)
	}
	f2("小明") // 调用匿名函数
}

2. 将函数作为参数传递

// 定义一个函数,接受一个函数作为参数
func proxy(name string, f func(string)) {
	f(name)
}

func Test5(t *testing.T) {
	// 定义一个函数
	sayHi := func(name string) {
		fmt.Println("Hello,", name)
	}

	// 将函数作为参数传递
	proxy("小明", sayHi)
}

3. 从其他函数返回函数

// 定义一个函数,返回一个函数
func createSayHi() func(string) {
	return func(name string) {
		fmt.Println("Hello,", name)
	}
}

func Test6(t *testing.T) {
	// 从函数中返回函数,并将其存储在变量中
	greet := createSayHi()
	greet("小明") // 调用返回的函数
}

4. 简单应用

简单利用execSql类似代理模式,只需要传入一个参数和一个函数,他就帮你执行并返回结果

func selectTableA(condition string) string {
	return "select * from table_a where " + condition
}

func selectTableB(condition string) string {
	return "select * from table_b where " + condition
}

func execSql(sql string, f func(string) string) string {
	return f(sql)
}

func Test7(t *testing.T) {
	println(execSql("1=1", selectTableA))
	println(execSql("id = 1", selectTableB))
}

输出

select * from table_a where 1=1
select * from table_b where id = 1

解耦逻辑:通过将具体操作(如 SQL 查询构建)封装在独立函数中,可以使 execSql 本身保持简单和通用,而不需要关心具体执行细节。

灵活性:可以很容易地替换或添加新的行为,只需定义新的处理函数并传递给 execSql 即可。这种方式允许在运行时动态决定使用哪种处理逻辑。

复用性:由于 execSql 是通用的,它可以与任何符合签名要求的函数一起使用,从而提高代码复用率。

4、闭包

闭包是一个函数,它可以捕获并“记住”其所在作用域中的变量,即使这个函数在其原始作用域之外被调用。通俗地说,闭包让你能够创建一个“带着环境”的函数,这个环境包括了该函数定义时的所有局部变量。

func adder() func(int) int {
	sum := 0
	// 使用了匿名函数外部的变量sum
	return func(x int) int {
		sum += x
		return sum
	}
}

func Test8(t *testing.T) {
	/*
		想象一下,你有一个工厂(外部函数:adder),它生产玩具(内部函数)。
		每个玩具都有一些特定的零件(外部变量:sum)。即使这个玩具被带到其他地方去,它仍然携带着这些零件,并且可以使用它们。
		这就是闭包的概念:一个内部函数加上它所需要的外部变量。
	*/
	funcTest := adder()
	fmt.Println(funcTest(1)) // 输出: 1,因为sum从0开始,加上1后变成1。
	fmt.Println(funcTest(2)) // 输出: 3,因为sum现在是1,再加上2后变成3。
	fmt.Println(funcTest(3)) // 输出: 6,因为sum现在是3,再加上3后变成6。

	println(adder()(2)) // 输出: 2,因为sum从0开始,加上2后变成2。
}
  • adder 是一个工厂,它返回一个匿名内部函数。
  • 内部匿名函数使用了 adder 函数中的局部变量 sum,这就是所谓的“捕获”或“记住”。
  • 即使在 Test8 函数中调用返回的匿名内部函数,这些捕获到的变量依然存在并保持状态。

5、defer

defer 语句用于延迟执行一个函数或方法,直到包含该 defer 的函数返回时才会被调用。它通常用于确保资源的释放、文件的关闭、解锁互斥锁等清理操作,即使在函数执行过程中发生错误,也能保证这些操作被执行。

  • 当一个函数遇到 defer 语句时,它会将后面的函数调用推迟到当前函数结束时再执行。
  • 多个 defer 语句按照先进后出的顺序(LIFO)进行执行。
  • defer 后面必须跟一个函数调用,不能是表达式。
  • 参数评估defer 调用的函数的参数在 defer 语句执行时就被评估了,而不是在函数实际执行时。
func Test9(t *testing.T) {
	a := 1
	//defer b := 2 // Expression in defer must be a function call,defer后面必须是函数调用
	defer fmt.Println("执行defer1") // defer是先进后出,所以这个是最后执行
	defer fmt.Println("执行defer2")
	defer fmt.Println(a) //  在此时,捕获了当前值的变量 a,即捕获值为 1。
	a = 2
	defer fmt.Println(a) // 此时捕获的是修改后的值,即捕获值为 2。

	fmt.Println("正常执行")
}

输出:

正常执行
2
1
执行defer2
执行defer1
使用场景
  1. 文件处理:打开文件后立即使用 defer 确保文件关闭。
  2. 互斥锁:加锁后使用 defer 确保解锁。
  3. 数据库连接:打开连接后使用 defer 确保关闭。

6、自执行函数

自执行函数(Immediately Invoked Function Expression,IIFE)是一种设计模式,指在定义后立即执行的函数。它通常用于创建一个新的作用域,以避免变量污染全局命名空间。

  1. 立即执行:一旦定义,就会立即被调用,而不需要显式地调用。
  2. 独立作用域:自执行函数创建了自己的作用域,因此内部变量不会影响外部环境。
  3. 常用于封装:可以用来封装代码块,避免与其他代码发生冲突,特别适合需要模块化和隔离逻辑片段时使用。
func Test10(t *testing.T) {
	// 定义并立即调用一个匿名函数
	result := func(a int) int {
		b := 100
		return a + b
	}(123)

	fmt.Println(result) // 223

	// 后续如果尝试访问b会导致错误,因为b仅在匿名函数内有效
}

http://www.kler.cn/a/403872.html

相关文章:

  • Java中的TreeSet集合解析
  • 《Python浪漫的烟花表白特效》
  • 将网站地址改成https地址需要哪些材料
  • OBOO鸥柏车载广告屏:28.6寸液晶一体机的技术革新与应用前景
  • 什么是RESTful API,有什么特点
  • 鸿蒙实战:使用隐式Want启动Ability
  • Excel常用技巧分享
  • Android 网络请求(二)OKHttp网络通信
  • 公安、监管等政务部门实现数字化转型的解决方案
  • 【WPF】Prism学习(三)
  • Github 2024-11-17 php开源项目日报 Top10
  • ffplay音频SDL播放处理
  • MySQL之联合查询
  • Mysql的InnoDB存储引擎中的锁机制
  • 三十八、Python(pytest框架-上)
  • 商品管理系统引领时尚零售智能化升级 降价商品量锐减30%
  • Linux-第2集-打包压缩 zip、tar WindowsLinux互传
  • 速盾:海外服务器使用CDN加速有什么好处?
  • Python JSON 数据解析教程:从基础到高级
  • 掌握C#中的异步编程:async和await关键字详解
  • Spring Boot整合Nacos启动时 Failed to rename context [nacos] as [xxx]
  • 单电源运放
  • 当企业服务器受到网络攻击该怎样处理?
  • 【1】猫眼娱乐后端开发面试题整理
  • Spring Boot实现WebSocket详解
  • Docker Registry(镜像仓库)详解