go基础-函数
概述
在任何语言中函数都是极其重要的内容,业务功能都是由一个或多个函数组合完成。go
语言是函数式编程语言,函数是一等公民,可以被传递、有函数类型,go
语言有三种类型的函数,普通函数、匿名函数(Lambda函数)、方法函数。go
语言函数有独特属性,可以有多个返回值,需要使用多个变量接收、函数也是一种类型,函数签名是函数类型、函数不能被重载
基本使用
声明函数时必须包含参数类型、返回值类型。单个返回值可省略括号
fun sum(x int, y int) int {
return x+y
}
调用函数
n := sum(10, 20) // 30
两个返回值,多个返回值必须使用括号。
fun sum(x int, y int) (int, int) {
return x+y, x-y
}
使用两个变量接收
n, m := sum(x, y)
返回值变量也可以在声明中定义
func sum(x int, y int) (a int, b int) {
a = x + y
b = x - y
return
}
在函数声明定义返回类型和返回值变量,使用return
时可省略返回对象。
两个返回值一般用于错误处理,一个表示结果,一个表示错误。很多库函数都有类似的应用
func Open(name string) (file *File, err error)
第一个返回值file
表示文件指针,第二个返回值err
表示打开文件异常,所以一般先判断是否有异常
file, err := open("test.txt")
if err != nil {
fmt.Pringln("打开文件失败, ", err)
return
}
使用_
表示忽略返回值
file, _ := open("test.txt")
go
语言函数也支持可变参数,注意可变参数必须在最后位置,固定是切片类型。
func sum(x int, args... int) int {
for _, arg := range args {
x += arg
}
return x
} // 使用
i := sum(10, 20, 30, 40)
当可变参数是一个空接口类型时,调用者是否解包可变参数会导致不同的结果:
func Print(a ...interface{}) {
fmt.Println(a...)
} func main() {
var a = []interface{}{123, "abc"} Print(a...) // 123 abc
Print(a) // [123 abc]
}
第一个 Print 调用时传入的参数是 a...
,等价于直接调用 Print(123, "abc")
。第二个 Print 调用传入的是未解包的 a
,等价于直接调用 Print([]interface{}{123, "abc"})
。
Go 语言函数还可以直接或间接地调用自己,也就是支持递归调用。相比其他语言 Go 语言函数的递归调用深度逻辑上没有限制,函数调用的栈不会出现溢出错误,因为 Go 语言运行时会根据需要动态地调整函数栈的大小。每个 goroutine 刚启动时只会分配很小的栈(4 或 8KB,具体依赖实现),根据需要动态调整栈的大小,栈最大可以达到 GB 级(依赖具体实现,在目前的实现中,32 位体系结构为 250MB,64 位体系结构为 1GB)
func f(x int) {
if x > 1 {
f(x-1)
} else {
fmt.Println(x)
}
}
因为Go 语言函数的栈会自动调整大小,所以程序员很少需要关心栈的运行机制的,在 Go 语言规范中甚至故意没有讲到栈和堆的概念。我们无法知道函数参数或局部变量到底是保存在栈中还是堆中,只需要知道它们能够正常工作就可以了
func f(x int) *int {
return &x
} func g() *int {
i := 20
return i
}
有C/C++经验的程序员会惊讶这两个函数有bug,因为参数变量在栈上维护,函数返回之后栈变量就失效了,返回的地址自然也应该失效了,返回的是野指针。在Go语言中可以正常工作,Go 编译器会保证指针指向的变量在合适的地方,不用关心 Go 语言中函数栈和堆的问题,编译器和运行时会帮我们搞定;同样不要假设变量在内存中的位置是固定不变的,指针随时可能会变化,特别是在你不期望它变化的时候。
函数类型
函数也是一种类型,即函数类型,也称为函数签名。
func sum(x int, y int) int {
return x + y
} func main() {
fmt.Printf("%T\n", sum) // 输出:func(int, int) int
}
注意,签名中不包括函数名称
和C
语言一样,go
的函数名是只读指针,指向函数的首地址,所以go
函数是引用类型。
把函数当参数传递时,复制出来的新指针,也指向相同的函数地址。注意不是复制函数,是复制函数指针,还有多种引用类型,如slice、map、chan、interface等。
func main() {
fmt.Println(sum) // 0x108ef60
fmt.Printf("%v\n", sum) // 0x108ef60
fmt.Printf("%v\n", &sum) // err
}
两条语句结果一样,都打印函数的起始内存地址。注意无法使用地址符获取函数地址,这也说明不存在函数指针,更无法通过指针调用函数,这与C语言有区别,C语言回调函数经常使用这招儿。
在go
语言中函数是一等公民,可以被传递、调用,这时就依赖函数类型
func handler(sum func(int, int, ) int, x int, y int) int {
return sum(x, y)
}
第一个参数是函数类型,接收函数做为参数,使用函数签名定义。
也可自定义类型,简单理解就是定义别名,简化写法
// 自定义类型
type sum func(int, int) int // 接收自定义类型
func handler(plus sum, x int, y int) int {
return plus(x, y)
}
匿名函数
与JavaScript
一样go
也支持匿名函数,声明函数时不写名称
func main() {
f := func(x int, y int) int {
return x + y
} fmt.Printf("%T\n", f) // func(int, int) int
fmt.Println(f(1, 2)) // 3
}
与普通函数一样使用、传递,匿名函数也有函数签名,也可自定义类型
// 自定义类型
type sum func(int, int) int func main() {
// 声明变量
var f sum = func(x int, y int) int {
return x + y
} fmt.Printf("%T\n", f)
fmt.Println(f(1, 2))
}
函数闭包
闭包是函数式编程语言的招牌功能之一,go
当然也支持闭包。简单来说就是函数可记住诞生时的环境信息,也称为记忆效应。
var str string = "hello" func func1() {
fmt.Println(str)
}
函数func1
应用引用了外部变量str
,注意是“引用”,而非值传递,两者会相互影响,这与函数调用传参有本质区别。
func1() // hello
str = "world"
func1() // world
更多使用动态生成匿名函数的方式,如下
func Accumulate(value int) func() int {
// 返回一个闭包
return func() int {
// 引入外部变量value并累加
value++
// 返回一个累加值
return value
}
}
返回值是一个函数,并且引用了外部变量value
,该变量被会记录在函数内部,外部环境被销毁也不受影响,有点类似Python的装饰器。
// 创建一个累加器, 初始值为1
accumulator := Accumulate(1) fmt.Println(accumulator()) // 2
fmt.Println(accumulator()) // 3
每次调用value
都会被累加,有点Java中lombda
的感觉,
延迟执行
这是go
特有的技能,函数内部被defer
修饰的语句总是最后执行,有点类似java中finally
的特性。
func main() {
fmt.Println("defer begin")
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
fmt.Println("defer end")
}
可以有多条defer
修饰,与栈的数据结构一致,先进后出,输出如下
defer begin
defer end
3
2
1
注意,被defer
修饰的预计总是最后执行,不再是顺序执行,与语句所在的位置无关。
func main() {
fmt.Println("start time: ", time.Now())
defer fmt.Println("end time: ", time.Now()) time.Sleep(time.Duration(rand.Intn(1000)))
fmt.Println("ok")
return
}
不受return
影响,end time
预计总是在最后打印,输出如下
start time: 2023-07-19 23:45:51.551994 +0800 CST m=+0.000133126
ok
end time: 2023-07-19 23:45:51.552634 +0800 CST m=+0.000773430
也不受panic
的影响,程序崩溃前也会执行被defer
修饰的语句
主要使用场景是异常捕获、回收资源、释放互斥锁等,因为被defer
修饰的语句一定会在最后执行
func main() {
fp, err := os.Open(filename)
if err != nil {
fmt.Println("open file error", err)
return
} // 最后一定会关闭文件
defer f.Close() // 对文件指针fp进行操作
...
}
也可以用于申请和释放锁
func main(key string) int {
// 申请锁
sync.Mutex.Lock()
// 释放锁,延迟到函数结束时执行
defer sync.Mutex.Unlock() // 业务逻辑
...
}
特殊函数
go
语言中有两个比较特殊的函数,在固定场景下使用
main
函数,同C
一样是程序的入口函数,只能有一个main
函数,程序从这里开始执行,注意main
函数只能属于main
包。只有当代码包含有main
函数时才可编译出可执行文件。
package main // main包
import "fmt" func main() { // main函数
fmt.Println("hello world")
}
init
函数,也称为初始化函数,会在main
函数之前被自动调用,只要被import
导入,该包所有init
函数都被会自动执行,多次导入只执行一次。import
是链式导入,init
执行也是链式执行,如下图。
go
执行顺序是:常量定义(const) -> 全局变量定义(var) -> 初始化函数(init) -> 程序入口(main)
要注意的是,在 main 函数执行之前所有代码都运行在同一个 Goroutine 中,也是运行在程序的主系统线程中。如果某个 init 函数内部用 go 关键字启动了新的 Goroutine 的话,新的 Goroutine 和 main.main 函数是并发执行的
go基础-函数的更多相关文章
- 速战速决 (3) - PHP: 函数基础, 函数参数, 函数返回值, 可变函数, 匿名函数, 闭包函数, 回调函数
[源码下载] 速战速决 (3) - PHP: 函数基础, 函数参数, 函数返回值, 可变函数, 匿名函数, 闭包函数, 回调函数 作者:webabcd 介绍速战速决 之 PHP 函数基础 函数参数 函 ...
- python基础——函数的参数
python基础——函数的参数 定义函数的时候,我们把参数的名字和位置确定下来,函数的接口定义就完成了.对于函数的调用者来说,只需要知道如何传递正确的参数,以及函数将返回什么样的值就够了,函数内部的复 ...
- python基础—函数嵌套与闭包
python基础-函数嵌套与闭包 1.名称空间与作用域 1 名称空间分为: 1 内置名称空间 内置在解释器中的名称 2 全局名称空间 顶头写的名称 3 局部名称空间 2 找一个名称的查找顺序: ...
- python基础—函数装饰器
python基础-函数装饰器 1.什么是装饰器 装饰器本质上是一个python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能. 装饰器的返回值是也是一个函数对象. 装饰器经常用于有切 ...
- iOS 基础函数解析 - Foundation Functions Reference
iOS 基础函数解析 - Foundation Functions Reference 太阳火神的漂亮人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名- ...
- (2.16)Mysql之SQL基础——函数
(2.16)Mysql之SQL基础——函数 关键词:mysql函数,mysql自定义函数,mysql聚合函数,mysql字符串函数,mysql数值函数 1.自定义函数 -- (1)一般形式 creat ...
- Python学习---基础函数的学习
1.1. 基础函数 函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可. 灌输一个概念:Python中函数就是对象,函数和我们之前的[1,2,3],'abc ...
- Python基础-函数参数
Python基础-函数参数 写在前面 如非特别说明,下文均基于Python3 摘要 本文详细介绍了函数的各种形参类型,包括位置参数,默认参数值,关键字参数,任意参数列表,强制关键字参数:也介绍了调用函 ...
- PHP基础函数、自定义函数以及数组
2.10 星期五 我们已经真正开始学习PHP 了,今天的主要内容是php基础函数.自定义函数以及数组, 内容有点碎,但是对于初学者来说比较重要,下面是对今天所讲内容的整理: 1 php的基本语法和 ...
- JavaScript Allongé 第一呷 :基础函数 (1)
第一呷 :基础函数 关于函数,尽管少,但毫不逊色. 在javascript中,函数是值,但它们不仅仅是简单的数值,字符串,或者甚至复杂的数据结构树或者地图.函数表示要执行的运算.就像数值.字符串和数组 ...
随机推荐
- Git练习网址
爲了方便学习git指令,让新手们更容易地理解,所以推荐一些git练习和博文网址 推荐的网址如下 网址一:Learn Git Branching! https://learngitbranching.j ...
- 【Nacos篇】Nacos基本操作及配置
官方文档:https://nacos.io/zh-cn/docs/v2/ecology/use-nacos-with-spring-cloud.html 前置条件:SpringCloud脚手架 单机模 ...
- Linux 过滤进程和端口号
IDEA覆盖率测速显示百分比 ctrl + alt + F6 取消勾选 ps - ef | grep java过滤Java进程 netstat -anop | grep 74933 过滤端口号 重命名 ...
- 【Hexo】NexT 主题的配置使用记录
目录 简介 版本 安装 配置记录 风格/主题 网页图标 菜单栏 侧边栏 本地搜索 代码块 动画效果 阅读进度 书签 Mermaid lazyload fancybox pangu 捐赠 版权声明 不蒜 ...
- 解放双手!ChatGPT助力编写JAVA框架
亲爱的Javaer们,在平时编码的过程中,你是否曾想过编写一个Java框架去为开发提效?但是要么编写框架时感觉无从下手,不知道从哪开始.要么有思路了后对某个功能实现的技术细节不了解,空有想法而无法实现 ...
- Programming abstractions in C阅读笔记:p132-p137
<Programming Abstractions In C>学习第53天,p132-p137,3.2小节"strings"总结如下: 一.技术总结 3.2小节介绍了字 ...
- Mysql基础9-事务
一.事务简介 事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有操作作为一个整体一起向系统提交或者撤销操作请求,即这些操作要么同时成功,要么同时失败.mysql的事务默认是自动提交的,也就 ...
- 遥遥领先.NET 7, .NET 8 性能大幅提升
每个版本必有的性能提升汇总文章又来了.大家可以学习阅读了. 微软 .NET 开发团队的工程师 Stephen Toub 发表博客<Performance Improvements in .NET ...
- java正则表达式过滤工具类
正则表达式过滤工具类 import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @Description : * @D ...
- MySQL实战实战系列 06 全局锁和表锁 :给表加个字段怎么有这么多阻碍?
今天我要跟你聊聊 MySQL 的锁.数据库锁设计的初衷是处理并发问题.作为多用户共享的资源,当出现并发访问的时候,数据库需要合理地控制资源的访问规则.而锁就是用来实现这些访问规则的重要数据结构. 根据 ...