Golang学习笔记(四) —— 函数
函数
函数定义
Go语言中定义函数使用 func 关键字,具体格式如下:
func (接收者)函数名(参数)(返回值){ 函数体 }
其中:
- 接收者:只有在定义方法时,才需要设置接收者。(可选项)
- 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也称不能重名。
- 参数:参数由参数变量和参数变量的类型组成,多个参数之间使用
,
分隔。(可选项) - 返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用
()
包裹,并用,
分隔。(可选项) - 函数体:实现指定功能的代码块。
有返回值的函数,必须有明确的终止语句,否则会引发编译错误。
当两个或多个连续的参数是同一类型,则除了最后一个类型之外,其他都可以省略。例如:func add(x,y int) int
参数
值传递
Go 语言中,函数的参数传递只有值传递,没有引用传递。
-
值传递:指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
-
引用传递:指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
为什么没有引用传递?
- Go 语言中不存在引用变量,自然没有引用传递。
什么是引用变量?
- 在 C++ 中,引用变量相当于原变量的一个别名
#include <stdio.h> int main() { int a = 10; int &b = a; int &c = b; printf("&a = %p\n &b = %p\n &c = %p\n", &a, &b, &c); // &a = 0x7ffe114f0b14 // &b = 0x7ffe114f0b14 // &c = 0x7ffe114f0b14 return 0; }
- 从上面的 c++ 程序可以看到,a、b、c 这三个变量的地址相同,共享一个内存地址(值自然也是相同的)。而在 Go 中,不同变量可以有相同的值,但地址是不会相同的,因此Go 语言中不存在引用变量。
没有引用传递,那我们想在函数内修改函数外的变量,该怎么做?
- 传递指针,通过指针修改实际参数。
引用和指针的区别?
- 指针变量是指向目的内存的变量,而引用变量是目的内存上的变量的一个别名
可变参数
可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数名后加 ...type 来标识。(type是可变参数的类型)
注意:
- 可变参数通常要作为函数的最后一个参数。
- 可变参数底层是由切片实现的
举个例子:
func intAdd(args ...int) int { sum := 0 for _, arg := range args { sum = sum + arg } return sum }
若想传递任意类型的可变参数,可以将可变参数的类型设置为 interface{}
函数返回值
Go语言中通过 return 关键字向外输出返回值。
- 支持多返回值,函数如果有多个返回值时必须用 ( ) 将所有返回值包裹起来。
- 支持返回值命名
返回值命名
注意:
- 当命名时,它们在函数开始时被初始化为其类型的零值
- 命名后,如果 return 没有指定变量,则会默认返回声明的返回变量
- 返回值命名,不能只命名一个,必须为该函数的所有返回值命名(可以用占位符 _ 命名,相当于没有命名)
举个例子:
func intAdd(args ...int) (sum int, str string, _ bool, _ error) { sum = 0 str = "good" for _, arg := range args { sum = sum + arg } return } func main() { sum, str, right, err := intAdd(100, 150, 200, 1, 20) fmt.Println(sum, str, right, err) //471 good false <nil> }
命名后,即便 return 其他变量(x),也会将 其他变量的值(x) 复制给 返回值命名的返回变量(y),再返回 返回值命名的返回变量(y)
package main import "fmt" func re_test() (y int) { x := 5 defer func() { //使用 defer 在调试时可以看的更明显 y++ // y = y + 1 = 6 }() return x // y = x = 5 } func main(){ fmt.Println(re_test()) //6 }
具体原理 并不清楚,姑且先这么认为(也许有误),待之后有机会再详细分析。 在下一篇的函数进阶中会详细阐述。
函数变量
函数类型
在 Go 中,函数类型也是一种数据类型,我们可以使用 type 关键字来定义一个函数类型,具体格式如下:
type int_calculation func(int, int) int
上面语句定义了一个 int_calculation 类型,它是一种函数类型,这种函数接收两个 int 类型的参数并且返回一个 int 类型的返回值,简单来说,凡是满足这个条件的函数都是 int_calculation 类型的函数。
举个例子:
func add(x, y int) int { return x + y } func sub(x, y int) int { return x - y }
上面两个函数都是 int_calculation 类型
函数类型变量
定义了一个函数类型后,我们可以声明该函数类型的变量,并赋值。
func main() { var c int_calculation // 声明一个int_calculation类型的变量c c = add // 把add赋值给c fmt.Printf("type of c:%T\n", c) // type of c:main.int_calculation fmt.Println(c(1, 2)) // 像调用add一样调用c f := add // 将函数add赋值给变量f fmt.Printf("type of f:%T\n", f) // type of f:func(int, int) int fmt.Println(f(10, 20)) // 像调用add一样调用f var a struct { fn int_calculation //结构体内封装函数类型的变量 } a.fn = add //为函数变量赋值 fmt.Println(a.fn(1, 2)) }
匿名函数
匿名函数是指不需要定义函数名的一种函数实现方式。其定义格式如下:
func(参数)(返回值){
函数体
}
匿名函数因为没有函数名,所以需要赋值给某个变量或者作为立即执行函数。
func main() { // 将匿名函数保存到变量 add := func(x, y int) { fmt.Println(x + y) } add(10, 20) // 通过变量调用匿名函数 //自执行函数:匿名函数定义完加()直接执行 func(x, y int) { fmt.Println(x + y) }(10, 20) }
匿名函数能够直接作为函数类型,来声明变量
func add(x, y int) int { return x + y } func main() { var a func(int, int) int= add fmt.Println(a(1, 2)) d := struct { fn func(int, int) int }{ fn: func(x int, y int) int { return x + y }, //fn: add 也行 } fmt.Println(d.fn(1, 2)) }
高阶函数
函数作为参数
函数既然能作为变量,自然也能够作为参数
func calc1(x, y int, op func(int, int) int) int { //用匿名函数作为参数类型 return op(x, y) } func calc2(x, y int, op int_calculation) int { //用定义的函数类型作为参数类型 return op(x, y) } func main() { ret2 := calc1(10, 20, add) fmt.Println(ret2) //30 }
函数作为返回值
func operator(s string) (int_calculation, error) { //此处的 int_calculation 可以用 func(int, int) int 代替 switch s { case "+": return add, nil case "-": return sub, nil default: err := errors.New("无法识别的运算符") return nil, err } }
闭包
闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,当匿名函数引用了外部作用域的变量(自由变量)时,就成了闭包函数。有了闭包后函数就成为了一个变量的值,只要变量没被释放,函数就会一直处于存活并独享的状态。
举个例子:
func adder2(x int) func(int) int { return func(y int) int { x += y return x } } func main() { var f = adder2(10) fmt.Println(f(10)) //20 fmt.Println(f(20)) //40 fmt.Println(f(30)) //70 f1 := adder2(20) fmt.Println(f1(40)) //60 fmt.Println(f1(50)) //110 }
这里只是简单了解闭包的概念,更具体的内容留到下一篇(函数进阶),再进一步了解。
内置函数
Go 语言提供了15个内置函数内置函数 | 介绍 |
close | 主要用来关闭channel |
len | 用来求长度,比如string、array、slice、map、channel |
cap | 返回值的容量,值的类型不同,值的容量含义也不同 |
new | 用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针 |
make | 用来分配内存,主要用来分配引用类型,比如chan、map、slice |
append | 用来追加元素到数组、slice中 |
panic和recover | 用来做错误处理 |
copy | 可以将源切片中的元素拷贝到目标切片 |
delete |
通过指定键 m[key] 删除 map 中的元素,如果 map 是 nil 或没有元素,delete 不做任何操作 |
print和printIn | 输入到标准错误流中并打印,官方推荐使用 fmt 包 |
complex | 将两个浮点型的值构造为一个复合类型的值,需要注意的是,实部和虚部必须是相同类型,即都是 float32 或 float64 |
real | 返回复合类型的值的实部,返回值是对应的浮点数类型 |
imag | 返回复合类型的值的虚部,返回值是对应的浮点数类型 |
defer
defer特性
1. 关键字 defer 用于注册延迟调用。
2. 这些调用直到 return 前才被执行。因此可以用来做资源清理。
3. 多个defer语句,按先进后出的方式执行。
4. defer语句中的变量,在defer声明时就决定了,只是延后执行而已。
defer用途
1. 关闭文件句柄
2. 锁资源释放
3. 数据库连接释放
defer执行时机
Go 语言的函数中 return 语句在底层并不是原子操作,它分为给 返回值赋值 和 RET指令 两步。而 defer 语句执行的时机就在返回值赋值操作后,RET指令执行前。具体如下图所示:
defer例子
func calc(index string, a, b int) int { ret := a + b fmt.Println(index, a, b, ret) return ret } func main() { x := 1 y := 2 defer calc("AA", x, calc("A", x, y)) x = 10 defer calc("BB", x, calc("B", x, y)) y = 20 }

A 1 2 3 B 10 2 12 BB 10 12 22 AA 1 3 4 //前两个是因为:defer 函数在 运行到时 就确定了参数的值,于是就直接执行了参数里的函数 //后两个是因为:defer 延迟执行,在 return 后按 后进先出 的顺序执行
函数的例子
本篇的最后以一个例子结尾,依此深化对函数的理解:
package main import "fmt" func f1() int { x := 5 defer func() { x++ }() return x } func f2() (x int) { defer func() { x++ }() return 5 } func f3() (y int) { x := 5 defer func() { x++ }() return x } func f4() (x int) { defer func(x int) { x++ }(x) return 5 } func main() { fmt.Println(f1()) fmt.Println(f2()) fmt.Println(f3()) fmt.Println(f4()) }

5 6 5 5

func f1() int { //用户没有为返回值命名,但内部还是有对返回值的命名,这里是 ~r0 x := 5 //① x =5 defer func() { x++ //③ x = x + 1 = 6 }() return x //② ~r0 = x = 5 }// 返回 ~r0 = 5

func f2() (x int) { //返回值命名为 x,最后返回变量 x defer func() { x++ //② x = x + 1 = 6 }() return 5 //① x = 5 }// 返回 x = 6

func f3() (y int) { //返回变量 y x := 5 //① x = 5 defer func() { x++ //③ x = x + 1 = 6 }() return x //② y = x = 5 }//返回 y = 5

func f4() (x int) { //返回变量 x defer func(x int) { //① 运行到 defer,发现有传参,先传参 x++ //④ x = x + 1 = 1,这里的 x 是局部变量,不会改变外面的返回变量 x }(x) //② 传参,x = 0 return 5 //③ x = 5 }// 返回 x = 5
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南