GO学习之 函数(Function)

news2024/11/23 13:12:59

GO系列

1、GO学习之Hello World
2、GO学习之入门语法
3、GO学习之切片操作
4、GO学习之 Map 操作
5、GO学习之 结构体 操作
6、GO学习之 通道(Channel)
7、GO学习之 多线程(goroutine)
8、GO学习之 函数(Function)

文章目录

  • GO系列
  • 前言
  • 一、什么是函数?
  • 二、函数声明
  • 三、函数调用
  • 四、匿名函数
  • 五、函数参数和返回值
  • 六、延迟执行函数
    • 6.1 defer 先进后出
    • 6.2 defer 闭包函数
  • 七、错误处理
    • 7.1 使用 error 作为返回参数
    • 7.2 使用 panic 触发异常
  • 八、总结

前言

按照公司目前的任务,go 学习是必经之路了,虽然行业卷,不过技多不压身,依旧努力!!!
之前在 结构体 篇中提到了方法,那方法其实和函数是差不多的,不过方法与函数的区别是,函数不属于任何类型,方法属于特定的类型,这句话在 结构体 篇中也说到了,但函数的使用上也有许多细节需要注意的,此篇则给予详解。

一、什么是函数?

  • 在 Go 语言中,函数(Function)是一种可执行的代码块(对特定功能进行提取,形成代码片段),用于执行特定的任务或操作。
  • 函数是 Go 语言中的基本组件,实现了模块化和复用的机制,让代码更加结构化和可维护。

下面列举了我能查到和想到的特点(不仅限于这些):

Go语言中函数的特点:

  • 无需声明原型
  • 支持不定参数
  • 支持多返回值
  • 支持命名返回参数
  • 支持匿名函数和闭包
  • 函数也是一种类型,可以把一个函数赋值给一个变量
  • 不能嵌套定义函数
  • 不能像JAVA中那样重载函数(overload)

二、函数声明

  • 函数的声明使用 关键字 func
  • 基本语法:func 函数名(参数列表) 返回值列表 { 函数体 }
  • 函数名命名规范:1、最好驼峰命名,见名知意,比如:addNum(a,b int){};2、首字母不能是数字;3、首字母大写表示可以被本包和其他包文件使用,类似 public,比如:AddNum(a,b int){},首字母小写则类似 private,比如:addNum(a,b int){}
  • 参数列表用逗号分隔,每个参数有参数名和类型组成,比如:func list(pageNo int, pageSize int) (int, error) { }
  • 如果多个参数是同一类型,则前面参数类型可以省略,比如:func list(pageNo, pageSize int) (int, error) { }
  • 使用 ... 语法可以为函数定义可变参数,运行函数接受不定数量参数,比如:``
  • 如果是无放回值则可省略,比如:func save(id int, name string) { }
  • 如果是一个返回值则不需要(或有都可)小括号,比如:func save(id int, name string) int { }
  • 如果是多个返回值则用小括号包起来,并且和 return 语句一 一对应,比如:func save(id int, name string) (int, string) { ...return 1,'success' }
  • 上面提到支持命名返回函数,比如:func divideAndRemainder(a, b int) (quotient, remainder int) { }
  • 使用关键词 func 定义函数,大括号不能另起一行

上代码:

三、函数调用

  • 在接受函数返回值时,如果多个返回值则一一对应接受,比如:count, result := save(1, "张三")
  • 如果只需要接受其中一个返回值,另一个不需要接受,则可以用下划线 _ 忽略,比如:count, _ := save(1, "张三")
  • 如果 main 包中想调用其他包中的函数,那其他包中的函数则需要定义为包外可访问的,函数名首字母大写,比如定义一个 func1 的包:func SumNum(a, b int) int { },此时需要注意此包名,必须使用包名调用,比如:s := func1.SumNum(1, 2)

下面例子,举例了 Go 语言常用到的函数定义的例子,可供参考。
例子是哥我一个一个敲的,测试通过,并且附上了运行结果,不过只靠眼睛看还是有点繁杂,还是自己在专门敲一遍为好,不过对于大佬无所谓了,对于像我这种小白,则只能按部就班敲一遍,解决各种报错,方能成长!

包路径是这样的:
函数包路径

package func1

import "fmt"

// 定义无参数无返回值函数
func test() {
	fmt.Println("call test函数")
}

// 定义有参数无返回值函数,此函数私有的,只有内部可调
func addNum(a, b int) {
	c := a + b
	fmt.Printf("a + b = c %+v\n", c)
}

// 定义有参数有一个返回值函数, 次函数共有的,内部、外部包均可调
func SumNum(a, b int) int {
	c := a + b
	return c
}

// 定义可变参数函数
func ParamsFunc(params ...string) {
	for index, item := range params {
		fmt.Printf("可变参数为 %d:%+v\n", index, item)
	}
}

// 定义有参数有多个返回值函数
func List(pageNo, pageSize int) (int, []string) {
	fmt.Printf("查询操作...%d, %d", pageNo, pageSize)
	result := []string{"特斯拉", "广汽", "丰田", "宝马", "奥迪"}
	return 5, result
}

// 定义命名返回函数
func divideAndRemainder(a, b int) (quotient, remainder int) {
	quotient = a / b
	remainder = a % b
	return // 省略了 return 语句,并且直接返回了命名的返回值变量
}

下面示例是对上面定义的函数进行调用。
主要,import 其他包的路径是 gotest.com/test/src/functionTest/func1

package main

import (
	"fmt"

	"gotest.com/test/src/functionTest/func1"
)

func main() {
	// 调用本包中的 save 函数,接受两个返回值
	count1, result := save(1, "张三")
	fmt.Printf("接受 save 函数的两个返回值 count1:%+v, result: %v\n", count1, result)

	// 调用本包中的 save 函数,接受一个返回值
	count, _ := save(1, "张三")
	fmt.Printf("接受 save 函数的一个返回值 count: %+v\n", count)

	// 调用无返回值函数
	list2(1, 10)

	// 调用 func1 包中的 SumNum 函数
	s := func1.SumNum(1, 2)
	fmt.Printf("调用 func1 包中的 SunNum 函数结果:%+v\n", s)
	
		// 调用可变参数函数
	func1.ParamsFunc("特斯拉", "广汽", "丰田", "宝马", "奥迪")

	// 调用 func1 包中的 List 函数
	totalCount, carBrands := func1.List(1, 10)
	fmt.Printf("调用 func1 包中的 List 函数,查询结果:%+v 条,数据:%v\n", totalCount, carBrands)
}

// 定义有参数有多个返回值函数
func save(id int, name string) (int, string) {
	fmt.Printf("保存%+v,%v\n", id, name)
	return 1, "success"
}

// 定义有多个参数无返回值函数
func list2(pageNo, pageSize int) {
	fmt.Println("list 接口")
}

运行结果:

PS D:\workspaceGo\src\functionTest\main> go run funcTest.go
保存1,张三
接受 save 函数的两个返回值 count1:1, result: success
保存1,张三
接受 save 函数的一个返回值 count: 1
list 接口
调用 func1 包中的 SunNum 函数结果:3
可变参数为 0:特斯拉
可变参数为 1:广汽
可变参数为 2:丰田
可变参数为 3:宝马
可变参数为 4:奥迪
查询操作...1, 10调用 func1 包中的 List 函数,查询结果:5 条,数据:[特斯拉 广汽 丰田 宝马 奥迪]

四、匿名函数

  • 在 Go 语言中,支持匿名函数,也就是没有函数名的函数
  • 可以将匿名函数赋值给变量
  • 也可以将匿名函数直接调用,则是闭包
package main

import "fmt"

func main() {
	// 定义匿名函数直接调用
	func() {
		fmt.Println("匿名函数调用!")
	}()
	// 定义匿名函数赋值给变量 hello
	hello := func() {
		fmt.Println("Hello 函数调用!")
	}
	// 调用匿名函数
	hello()
	// 定义有参数的匿名函数
	sum := func(a, b int) int {
		return a + b
	}
	fmt.Printf("加法计算:%+v\n", sum(1, 2))
}

运行结果:

PS D:\workspaceGo\src\functionTest\main> go run .\anonymousFunc.go
匿名函数调用!
Hello 函数调用!
加法计算:3

下面是一个稍微复杂点的例子:

下面例子中,我们把 函数 作为一个成员存放在了 数组 fns、结构体 s、管道 fc 中,并且获取到函数进行调用。

package main

func main() {
	// 定义数据,元素类型是一个函数
	fns := [](func(a int) int){func(a int) int { return a + 1 }, func(a int) int { return a + 2 }}
	// 获取数组中的第一个函数调用,传参 10
	for _, fn := range fns {
		println(fn(10))
	}

	// 定义一个结构体,成员是一个 函数,调用结构体的 函数成员
	s := struct {
		fn func() string
	}{
		fn: func() string { return "Hello World!" },
	}
	println(s.fn())

	// 定义一个管道,发送一个函数,再接受到函数进行调用
	fc := make(chan func() string, 2)
	fc <- func() string { return "fc: Hello World!" }
	println((<-fc)())
}

运行结果:

PS D:\workspaceGo\src\functionTest\main> go run .\anomymousFunc2.go
11
12
Hello World!
fc: Hello World!

五、函数参数和返回值

  • 在 Go 语言中,函数可以作为参数传递,也可以作为另一个函数的返回值

下面是一个比较简单的示例,例子中接受一个 函数类型参数 fc,返回一个 匿名函数。

package func1

import "fmt"

func CallFunc(fc func()) func() {
	fmt.Println("接受到函数 fc, 开始回调!")
	// 返回一个匿名函数
	return func() {
		fc()
		fmt.Println("call back...")
	}
}

调用代码:

package main

import (
	"fmt"

	"gotest.com/test/src/functionTest/func1"
)

func main() {
fc := func() {
		fmt.Println("我是参数 fc 执行!")
	}
	fr := func1.CallFunc(fc)
	fr()
}

运行结果:

PS D:\workspaceGo\src\functionTest\main> go run .\funcTest.go
接受到函数 fc, 开始回调!
我是参数 fc 执行!
call back...

下面是 ChatGPT给出的经典案例,方便更加深入理解函数如何作为参数和返回值在实际场景中的应用,示例我已测试,ojbk。

  • 函数作为参数使用:
package main

import "fmt"

// 函数类型作为参数
type MathFunc func(int, int) int

// 加法函数
func add(a, b int) int {
	return a + b
}

// 减法函数
func subtract(a, b int) int {
	return a - b
}

// 计算函数,接收一个函数类型参数,并执行该函数
func calculate(a, b int, op MathFunc) int {
	return op(a, b)
}

func main() {
	// 调用 calculate 函数,传入 add 函数作为参数
	result := calculate(10, 5, add)
	fmt.Println("加法结果:", result)

	// 调用 calculate 函数,传入 subtract 函数作为参数
	result = calculate(10, 5, subtract)
	fmt.Println("减法结果:", result)
}

运行结果:

PS D:\workspaceGo\src\functionTest\main> go run .\gptFunc.go
加法结果: 15
减法结果: 5
  • 函数作为返回值使用:
package main

import "fmt"

// 返回一个加法函数
func getAddFunc() func(int, int) int {
	// 返回一个匿名函数,来实现计算
	return func(a, b int) int {
		return a + b
	}
}

// 返回一个减法函数
func getSubtractFunc() func(int, int) int {
	return func(a, b int) int {
		return a - b
	}
}

func main() {
	// 获取加法函数并调用
	addFunc := getAddFunc()
	result := addFunc(10, 5)
	fmt.Println("加法结果:", result)

	// 获取减法函数并调用
	subtractFunc := getSubtractFunc()
	result = subtractFunc(10, 5)
	fmt.Println("减法结果:", result)
}

执行结果:

PS D:\workspaceGo\src\functionTest\main> go run .\gptFunc2.go
加法结果: 15
减法结果: 5

六、延迟执行函数

defer 的特性:

  • 关键字 defer 用户注册延迟调用,比如:defer println(i)
  • 注册的延迟调用直到 return 前才会执行,所以很适合做关闭、资源回收等操作
  • defer 语句,是按照先进后出的方式执行
  • defer 语句中的变量,在 defer 声明是就决定了

defer 适用场景:

  • 关闭流操作
  • 资源释放
  • 数据库连接释放
  • 等…

6.1 defer 先进后出

package main

func main() {
	arr := [5]int{1, 2, 3, 4, 5}
	for i := range arr {
		defer println(i)
	}
}

运行结果:

PS D:\workspaceGo\src\functionTest\main> go run .\deferTest.go
4
3
2
1
0

从结果可以看出,先循环到的 defer 等到后面才执行。

6.2 defer 闭包函数

package main

func main() {
	arr := [5]int{1, 2, 3, 4, 5}
	for i := range arr {
		defer func() {
			println(i)
		}()
	}
}

运行结果:

PS D:\workspaceGo\src\functionTest\main> go run .\deferTest.go
4
4
4
4
4

为啥全部变为了 4,由于循环体内的是闭包函数,声明完之后立马执行,但是在函数声明的时候 i 变量已经变为了 4,所以 4 个匿名函数都输出了 4。

由于 defer 看起来情况比较多,所以请移步到这里!

七、错误处理

  • Go 语言中的多数函数会返回一个错误 error 作为额外的返回值,用户表示函数是否执行成功
  • 调用函数通常需要检查错误,以便根据情况进行处理
  • 对于无返回值的函数,可以使用错误类型来表明函数是否执行成功,或者用 panic 来触发异常

7.1 使用 error 作为返回参数

在示例中,我们使用错误类型 error 来表示函数是否执行成功,如果函数出现错误,则返回 error。

package main

import (
	"errors"
	"fmt"
)

func main() {
	err := divide(10, 0)
	if err != nil {
		fmt.Println("发生异常:", err)
	}
}

func divide(a, b int) error {
	if b == 0 {
		return errors.New("参数不能为 0")
	}
	return nil
}

运行结果:

PS D:\workspaceGo\src\functionTest\main> go run .\errorFunc.go
发生异常: 参数不能为 0

7.2 使用 panic 触发异常

示例中,用 panic 来触发异常表示函数执行状态,当函数出现错误时,直接触发异常,并中断程序执行。
**注意:**我们用 recover() 函数来捕获并处理异常,避免程序崩溃。recover 函数只在 defer 块中才有效,所以在 main() 函数中使用 defer 来捕获异常。

package main

func main() {
	defer func() {
		if r := recover(); r != nil {
			println("发生异常:", r)
		}
	}()
	divide2(10, 0)
}

func divide2(a, b int) {
	if b == 0 {
		panic("参数不能为 0")
	}
}

运行结果:

PS D:\workspaceGo\src\functionTest\main> go run .\panicFunc.go
发生异常: (0xff1920,0x1011638)

八、总结

函数是 Go 语言中非常重要的组成部分,它们提供了模块化、代码复用和抽象的能力。通过函数,我们可以将复杂的逻辑划分为多个小模块,使得代码更加清晰、可读性更强,并且更易于维护和扩展。函数的灵活性和多样性使得 Go 语言可以用于解决各种不同的问题和场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/839968.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue响应数据为二维码如何渲染到页面

在postman测试请求后发现响应数据为一个二维码图片,不是链接,如何解决? 然后如果在vue中使用请求还会报Uncaught (in promise) SyntaxError: Unexpected token o in JSON at position 1的错误。这个就是使用了JSON.parse导致的响应格式不对)&#xff0c;使用JSON.stringify解决…

<dependency> idea中为什么这个变黄色

在IDE中&#xff0c;当你的代码出现黄色高亮时&#xff0c;通常表示存在警告或建议的提示。对于Maven的<dependency>标签来说&#xff0c;黄色高亮可能有以下几种原因&#xff1a; 依赖项未找到&#xff1a;黄色高亮可能表示IDE无法找到指定的依赖项。这可能是由于配置错…

【前端】鼠标事件计算与圆心形成的角度

在业务需求中&#xff0c;常常出现一些我们无法完成的效果图&#xff0c;这时需要UI切图给我们&#xff0c;而切图后不可避免的一些点击事件无法方便的监听 如该图圆环&#xff0c;其实是一张单独的图片&#xff0c;这种情况下只能通过js判断用户点击、拖动的鼠标位置&#xf…

【Python】基础:OpenCV库基本应用

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍OpenCV库基本应用。 学其所用&#xff0c;用其所学。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c;下次更新不迷路&…

【项目 计网1】4.1 网络结构模式 4.2MAC地址、IP地址、端口

文章目录 第四章 Linux网络编程4.1 网络结构模式C/S结构&#xff08;client-server&#xff09;B/S结构&#xff08;Browser/Server&#xff0c;浏览器/服务器模式&#xff09; 4.2 4.3MAC地址、IP地址、端口&#xff08;1&#xff09;&#xff08;2&#xff09;MAC地址IP地址(…

abp vnext升级到指定版本并处理升级后的问题

在使用abp vnext时当版本更新后可能会跨越net的版本&#xff0c;如果我们想升级到指定版本该怎么做呢&#xff0c;升级之后又有一些问题需要处理&#xff0c;下面一起看一下&#xff1a; 当前我的项目是.net5 abp vnext4.2.1 当前的最新abp版本是7.* 对应的net版本是 net7,由于…

FFmpeg常见命令行(二):FFmpeg转封装

前言 在Android音视频开发中&#xff0c;网上知识点过于零碎&#xff0c;自学起来难度非常大&#xff0c;不过音视频大牛Jhuster提出了《Android 音视频从入门到提高 - 任务列表》。本文是Android音视频任务列表的其中一个&#xff0c; 对应的要学习的内容是&#xff1a;如何使…

【Verilog/D8】

2023年8月5日 HDBits/Cs450/counter 2bc状态机异步复位noteHDBits/Cs450/history shiftHDBits/Cs450/gshare HDBits/Cs450/counter 2bc状态机 Cs450/counter 2bc LSB最低有效位 module top_module(input clk,input areset,input train_valid,input train_taken,output reg[1…

选读SQL经典实例笔记18_Exactly

1. 问题9 1.1. 只讲授一门课程的教授 1.2. sql select p.*from professor p,teach twhere p.lname t.lnameand p.lname not in ( select t1.lnamefrom teach t1,teach t2where t1.lname t2.lnameand t1.cno &#xff1e; t2.cno ) LNAME DEPT SALARY …

抖音seo矩阵系统源代码开发搭建技术分享

抖音SEO矩阵系统是一个较为复杂的系统&#xff0c;其开发和搭建需要掌握一定的技术。以下是一些技术分享&#xff1a; 技术分享 抖音SEO矩阵系统的源代码可以使用JAVA、Python、PHP等多种语言进行开发。其中&#xff0c;JAVA语言的应用较为广泛&#xff0c;因为JAVA语言有良好…

运输层---UDP协议

目录 一. 无连接运输&#xff1a;UDP1.1 定义1.2 特点1.3 应用 二. UDP报文段结构三. UDP检验和3.1 定义3.2 检验和计算实例3.2 UDP检验和的局限 一. 无连接运输&#xff1a;UDP 1.1 定义 UDP&#xff08;User Datagram Protocol&#xff09;用户数据报协议&#xff1a;由 [RF…

数字图像处理 使用 GIST 图像描述符

一、简述 GIST描述符是一种用于场景识别的全局图像特征,它能够有效地描述图像的空间结构信息。GIST描述符最初是在这篇论文中提出的:A Computational Approach to Edge Detection 1998。 图像的 GIST 描述符可以通过分析空间频率和方向来捕获。直观上,GIST 总结了图像不同部分…

九、pig安装

1.上传pig包 2.解压文件 3.改名 4.赋权 5.配置环境变量 export PIG_HOME/usr/local/pig export PATH$PATH:$JAVA_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$HIVE_HOME/bin:$HBASE_HOME/bin:$SQOOP_HOME/bin:$PIG_HOME/bin 6.测试

ensp-DHCP服务实验

ensp-DHCP服务实验 日期&#xff1a;6-26 &#x1f4ce;DHCP服务器.zip&#x1f4ce;dhcp服务作业.docx

剑指 Offer 50. 第一个只出现一次的字符

题目描述 在字符串 s 中找出第一个只出现一次的字符。如果没有&#xff0c;返回一个单空格。 s 只包含小写字母。 示例 思路1 如果一个字符首次出现indexOf和最后一次出现lastIndexOf的位置相同&#xff0c;说明只出现了一次 从左到右遍历即可得到第一个不重复出现的数字cla…

c++11 标准模板(STL)(std::basic_ofstream)(三)

定义于头文件 <fstream> template< class CharT, class Traits std::char_traits<CharT> > class basic_ofstream : public std::basic_ostream<CharT, Traits> 类模板 basic_ifstream 实现文件流上的高层输入操作。它将 std::basic_istrea…

DLA 神经网络的极限训练方法:gradient checkpointing

gradient checkpointing 一般来说&#xff0c;训练的过程需要保存中间结果&#xff08;不管是GPU还是CPU&#xff09;。前向传播根据输入(bottom_data)计算输出(top_data)&#xff0c;后向传播由top_diff计算bottom_diff&#xff08;如果某个变量打开梯度进行训练的话&#xff…

AcWing 4310:树的DFS ← vector、auto、邻接表

【题目来源】https://www.acwing.com/problem/content/description/4313/【题目描述】 给定一棵 n 个节点的树。 节点的编号为 1∼n&#xff0c;其中 1 号节点为根节点&#xff0c;每个节点的编号都大于其父节点的编号。 现在&#xff0c;你需要回答 q 个询问。 每个询问给定两…

Godot 4 源码分析 - Path2D与PathFollow2D

学习演示项目dodge_the_creeps&#xff0c;发现里面多了一个Path2D与PathFollow2D 研究GDScript代码发现&#xff0c;它主要用于随机生成Mob var mob_spawn_location get_node(^"MobPath/MobSpawnLocation")mob_spawn_location.progress randi()# Set the mobs dir…

面试热题(x的平方根)

给你一个非负整数 x &#xff0c;计算并返回 x 的 算术平方根 。 由于返回类型是整数&#xff0c;结果只保留 整数部分 &#xff0c;小数部分将被 舍去 。 注意&#xff1a;不允许使用任何内置指数函数和算符&#xff0c;例如 pow(x, 0.5) 或者 x ** 0.5 。 这道题虽然是简单题…