golang函数特点:
• 无需声明原型。
• 支持不定 变参。
• 支持多返回值。
• 支持命名返回参数。
• 支持匿名函数和闭包。
• 函数也是一种类型,一个函数可以赋值给变量。
• 不支持 嵌套 (nested) 一个包不能有两个名字一样的函数。
• 不支持 重载 (overload)
• 不支持 默认参数 (default parameter)。
1 | //函数声明告诉了编译器函数的名称,参数,和返回类型 |
func
:函数由 func 开始声明function_name
:函数名称,参数列表和返回值类型构成了函数签名。parameter list
:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数是可选的,也就是说函数也可以不包含参数。可以定义多个参数,多个参数之间用逗号分隔。return_types
:返回类型,函数返回一列值。return_types
是该列值的数据类型。有些功能不需要返回值,这种情况下return_types
不是必须的。可以有返回值,也可以没有函数体:函数定义的代码集合。
1 | //函数可以返回多个值 |
1 | //具名返回值允许我们不显式地使用 return 语句来返回结果,Go 会自动返回最后的变量值 |
函数值传递
在Go语言中,函数可以像任何其他变量一样被赋值给变量或作为参数传递给其他函数。
值类型:在Go语言中,基本数据类型(如
int
、float
、bool
、string
)和复合数据类型(如数组、结构体)都是值类型。存储位置:值类型通常存储在栈(stack)上。
参数传递:当值类型作为函数参数传递时,实际上是传递了该值的拷贝。
修改影响:在函数内部对参数值的修改不会影响到函数外部的原始值。
1 | //基本数据类型(int) |
1 | //数组 |
1 | //结构体 |
函数引用传递
引用类型:在Go语言中,指针、切片(slice)、映射(map)和通道(channel)是引用类型。
存储位置:引用类型通常存储在堆(heap)上,并通过指针来访问。
参数传递:当引用类型作为函数参数传递时,实际上是传递了指向该值的指针,即引用传递。
修改影响:在函数内部对参数的修改会影响到函数外部的原始值。
1 | //指针 |
1 | //切片 |
1 | //映射 |
init
函数
在Go语言里,init
函数就像是一个特殊的“准备”按钮。它是一个不需要你手动去按的按钮,程序在开始运行的时候会自动帮你按这个按钮。这个按钮的作用就是做一些准备工作,比如设置好环境,让你的程序能够顺利地开始工作。
定义:
init
函数是一个特殊的函数,用于初始化操作。它没有参数,没有返回值,且不能被显式调用。自动调用:程序在启动时会自动调用
init
函数,且在main
函数执行之前。
1 | func init(){ |
执行顺序:在同一个包中,
init
函数的执行顺序是按照它们在代码中出现的顺序。如果一个包中有多个文件,那么init
函数的执行顺序是按照文件名的字典顺序。全局变量初始化:全局变量的初始化是在
init
函数执行之前进行的。如果全局变量依赖于函数调用的结果,那么这个函数会在相关的init
函数执行之前被调用。无参数和返回值:
init
函数没有参数,也不返回任何值。可重复定义:一个包中可以定义多个
init
函数,它们会按照它们在代码中出现的顺序执行。用途:
init
函数通常用于那些不能通过简单声明或全局变量赋值来完成的初始化工作,比如配置文件的加载、环境变量的设置、日志系统的初始化等。
1 | package mypackage |
作用与调用时机:
init()
函数用于在包被导入时执行一次性的初始化操作。每个包可以包含多个init()
函数,它们会在包被导入时自动执行。当包被导入时,
init()
函数会按照导入的顺序自动执行。同一个包中的多个init()
函数按照编写的顺序执行。
使用方式:
init()
函数的定义和普通函数类似,只是函数名为init
。它没有参数和返回值,不需要手动调用,而是在包被导入时自动执行。
应用场景:
- 常用于初始化包的配置信息,例如读取配置文件,进行初始化设置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package config
import (
"log"
"os"
)
var Config = struct {
Database string
Port int
}{}
func init() {
// 假设配置文件名为 config.ini
configFile, err := os.Open("config.ini")
if err != nil {
log.Fatal("Failed to open config file: ", err)
}
defer configFile.Close()
// 读取配置文件并设置到Config变量中
// 这里省略了具体的解析代码
// ...
}- 用于数据库初始化,比如建立数据库连接,进行必要的数据表创建等操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package db
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"log"
)
var DB *sql.DB
func init() {
var err error
// 连接数据库
DB, err = sql.Open("mysql", "user:password@/dbname")
if err != nil {
log.Fatal("Error connecting to the database: ", err)
}
// 测试数据库连接
err = DB.Ping()
if err != nil {
log.Fatal("Error pinging the database: ", err)
}
}- 注册功能插件,当包中存在多个功能插件需要在包被导入时注册到主程序中。
1
2
3
4
5
6
7
8
9
10
11
12
13package plugin
var Plugins = []func(){}
func init() {
// 注册插件
Plugins = append(Plugins, func() {
// 插件1的代码
})
Plugins = append(Plugins, func() {
// 插件2的代码
})
}
与
main
函数的异同:相同点:两个函数在定义时不能有任何的参数和返回值,且Go程序自动调用(一般函数都要先声明,再调用。而 init 函数和 main 函数只声明就行,Go 程序会自己进入)。
不同点:
init()
可以应用于任意包中,且可以重复定义多个;main
函数只能用于main
包中,且只能定义一个。
包
在Go语言开发中,我们经常需要在不同的文件之间共享代码,比如在一个文件中定义函数,在另一个文件中使用这个函数。这就是包(package)的作用之一。此外,如果两个程序员都想在同一个项目中定义同名的函数,使用包可以避免命名冲突。
在Go语言中,包(package)是代码组织的基本单位。一个包可以包含多个.go源文件,它们共享相同的包名。包的使用包括定义包、编译包以及在包中定义函数、变量和类型。
1 | // math.go |
1 | // utils.go |
1 | package main |
在main.go
文件中,我们导入了math
和utils
包,并使用它们各自的Add
函数。由于每个函数都通过其包名进行了限定,因此即使函数名称相同,也不会发生冲突。我们可以通过math.Add
来调用math
包中的Add
函数,通过utils.Add
来调用utils
包中的Add
函数。
作用
区分标识符:包的使用可以区分相同名字的函数、变量等标识符,即使在不同的包中。
项目管理:当程序文件很多时,包可以帮助我们更好地管理和组织项目。
控制访问:包可以控制函数、变量等的访问权限,即它们的作用域。
1 | package 包名 |
1 | import "包的路径" |
main包
main
包是Go程序的入口点。每个可执行的Go程序都必须包含一个main
包,其中包含main
函数。
1 | package main |
匿名函数
匿名函数是Go语言中没有函数名的函数。它们通常用于需要函数的地方,但又不想为函数命名的场景。匿名函数可以被赋值给一个变量,或者直接作为参数传递给另一个函数。
匿名函数使用方法
直接调用
就像是你直接用工具做了一件事。
1 | package main |
定义了一个匿名函数来输出一条日志信息,然后立即调用它。这个函数只在程序启动时使用一次,之后就不再需要了。
1 | package main |
如果doSomething
函数返回一个错误,我们定义并立即调用一个匿名函数来处理这个错误,比如输出错误信息并执行清理工作。
保存到变量
就像是你把这个工具保存起来,以后还可以再用。
1 | package main |
作为参数和返回值
就像是你把这个工具带到了别的地方(作为参数),或者你得到了一个新的工具(作为返回值)。
作为参数:
1 | package main |
handleTask
是一个匿名函数,我们把它作为参数传递给了 processTask
函数
作为返回值:
1 | package main |
getTool
函数返回一个匿名函数,我们得到了这个新工具,并在 main
函数中使用它
一次性匿名函数
在定义匿名函数的时候就调用,此时匿名函数就只能使用一次
1 | fun main(){ |
🚀 编译结果如下:
1 | 30 |
赋值给变量调用 可重复使用的匿名函数
这种方式的匿名函数可以多次调用,之前我们说过函数也是一种数据类型,那么将这个函数直接定义一个变量然后赋值。
这种方式就像是你有一本食谱,你可以按照食谱上的指示多次制作同一种蛋糕。每次你想要做蛋糕时,都会按照食谱上的步骤来操作。
1 | myFunc := func() { |
1 | package main |
🚀 编译结果如下:
1 | res= 10 |
全局匿名函数
这些是没有名字的函数,但是它们可以在程序的任何地方被调用,因为它们被定义在全局范围内。就像你家里的公共工具,比如剪刀,你可以在任何需要的时候找到并使用它们。
1 | package main |
匿名函数就像是你生活中的“即用即抛”工具,它们不需要长期维护,也不需要占用你太多的资源。你可以在需要的时候快速使用它们,然后继续你的工作和生活。它们特别适合那些不需要重复使用或者只在特定情况下需要的功能。通过使用匿名函数,你的代码可以变得更加灵活和高效,就像你的生活因为有了这些即用即抛的工具而变得更加方便一样。
闭包
在一个厨房里做蛋糕,你有一个秘密配方(闭包),这个配方不仅告诉你需要哪些步骤(函数体),还告诉你需要用到哪些特别的调料(外部变量)。即使你离开了厨房,这个配方还是能记住你需要哪些调料,并且每次你按照这个配方做蛋糕时,都能用到这些调料。
闭包就像是一个“记忆盒子”,它是一个函数,但是这个函数能够记住它在哪里被创建,以及那时候周围的情况。即使创建它的那个环境(比如一个函数)已经结束了,闭包仍然能够记住那些信息。
闭包的特点
记住变量:闭包可以记住它被创建时周围的变量,即使那些变量在外部函数中已经不可见。
可以修改记忆:闭包不仅能记住变量,还能改变它们,就像那些变量一直存在一样。
延迟执行:闭包可以在需要的时候才执行,即使创建它的外部函数已经执行完毕。
闭包怎么工作的?
闭包通过在函数内部定义另一个函数来实现。内部函数能够访问外部函数的变量,即使外部函数已经执行完毕。
闭包是一个特殊的匿名函数, 它是匿名函数和相关引用环境组成的一个整体,
- 也就是说只要匿名函数中用到了外界的变量, 那么这个匿名函数就是一个闭包。
1 | //Go 支持匿名函数,这意味着可以在函数内部定义并调用函数,匿名函数也可以作为变量传递。 |
- 闭包中使用的变量和外界的变量是同一个变量, 所以可以闭包中可以修改外界变量
1 | package main |
闭包a
“记住了”变量num
,并且能够修改它。当你调用a()
时,闭包不仅打印出了修改后的num
值,而且这个修改也影响到了main
函数中的num
变量。
- 只要闭包还在使用外界的变量, 那么外界的变量就会一直存在
1 | package main |
addUpper
函数返回了一个闭包,这个闭包“记住了”变量x
。每次调用res()
时,闭包都会增加x
的值,并返回新的x
值。因为闭包一直在使用x
,所以x
会一直存在,直到程序结束。
闭包的应用场景
封装状态:就像你的秘密配方不让别人看到,闭包可以封装状态,不让外部直接访问。
生成唯一资源:每次按照配方做蛋糕,都能保证蛋糕是独一无二的,闭包可以生成唯一的资源。
延迟初始化:就像你可以根据需要再决定是否做蛋糕,闭包可以实现延迟初始化。
闭包的注意事项
内存泄漏:如果你的秘密配方一直记得那些调料,即使你不再做蛋糕,那些调料也不会被清理掉,可能会导致资源浪费。
变量捕获:闭包会记住变量的最新状态,如果这些变量在外部被修改,闭包内部看到的也会是最新的状态。
函数异常处理
在Go语言中,处理函数出错的情况有点像我们平时处理生活中的意外。想象你在一个餐厅工作,你的任务是确保顾客的订单正确无误。如果一切都顺利,你会把食物直接送到顾客那里。但有时候,厨房可能会出点问题,比如订单做错了或者食物烧焦了。这时候,你不能直接把问题食物给顾客,你需要先处理这个问题,可能是退回去重做,或者给顾客换个菜。
异常处理方式和其他语言(如 Java 或 Python)不同。Go 不支持传统的 try-catch
异常机制,而是依赖错误处理模式,通过返回值和 defer
, panic
, recover
机制来管理和捕获异常。
在Go语言中,我们有两种主要的方式来处理这种“厨房错误”:
- 错误返回值:这是最常见的方式,就像你检查食物是否做好了,如果没有,你会告诉顾客需要等一下。在Go中,函数会返回一个额外的值,通常是最后一个返回值,用来告诉你有没有错误发生。
Go语言中的错误处理主要依赖于
error
接口。error
是一个接口,定义了Error()
方法,返回错误信息。函数可以通过返回
error
类型的值来指示是否发生错误。调用者需要检查这个错误值是否为nil
,以确定函数是否成功执行。
1 | package main |
1 | 出错了: 不能除以零 |
- defer:当你在函数中使用
defer
时,它会在函数返回之前执行。这常用于清理资源,比如关闭文件、网络连接等。即使函数中途遇到错误,defer
指定的操作仍会在函数结束前执行。后进先出(LIFO)的顺序执行。
1 | package main |
1 | Performing some work... |
- panic和recover:这种方式有点像处理厨房火灾这种紧急情况。
panic
就是当错误严重到无法处理时,你按下紧急按钮,让整个餐厅知道出事了。recover
就像是紧急响应,可以在错误发生后采取措施,比如清理现场,防止餐厅完全停止营业。
panic
是Go语言中用于触发异常的内置函数。当调用panic
时,程序会立即停止当前函数的执行,并向上一层函数传递错误信息,这个过程会一直持续到程序终止或者被recover
捕获。panic
可以带一个参数,通常是error
类型的值,表示错误信息。panic
通常用于不可恢复的错误情况,比如程序逻辑中出现了严重的错误。
1 | package main |
1 | 函数开始 |
recover
是Go语言中用于捕获和处理panic
的内置函数。只有在defer
函数中调用recover
才能成功捕获panic
。如果
recover
成功捕获panic
,它会返回panic
的值,否则返回nil
。使用
recover
可以恢复程序的执行,避免程序异常终止。
1 | package main |
1 | safeFunction 开始 |