Golang 函数及相关特性
一、函数基础
1. 函数定义
(1)基本语法
Go 语言中函数定义的基本语法如下:
func function_name([parameter list]) [return_types] {
// 函数体
}
(2)三种函数类型
- 普通函数:有明确名称的函数,可直接调用。
- 匿名函数:无名称的函数,可在函数内部定义或作为值使用。
- 方法:定义在 struct 内的函数,与特定结构体关联。
2. 函数参数
(1)参数特点
- 可包含多个参数,每个参数需指定类型。
- 允许参数无名称(类似 C++ 占位参数)。
(2)传参方式
- 值传递:传递参数的拷贝副本,函数内修改不影响外部。
- 特殊类型:map、slice、interface、channel 本身为指针类型,拷贝后的指针仍指向底层数据,修改可能影响外部。
(3)变长参数
格式为 ARGS...TYPE,将参数保存到名为 ARGS 的 slice 中(元素类型均为 TYPE)。
3. 函数返回值
(1)返回值特点
- 支持多个返回值,需指定类型。
- return 可带参数或省略,省略时返回值必须有名称。
- 返回值有名称时需用括号包围(即使单个),且名称不能与参数重名。
- return 中可含表达式,但不能是赋值表达式(如
return c = a + b错误)。 - 命名返回值在函数内可直接使用,无需再次声明;return 中指定参数时,优先级更高。
(2)使用习惯
- 常用一个返回值表示执行结果(如
value, errvalue, ok)。 - 返回值超过 4 个时,建议放入容器(同类型用 slice,不同类型用 map)。
- 可用下划线
_丢弃不需要的返回值。
4. 函数类型与函数变量
通过类型别名定义函数类型,类似函数指针,可实现类似重载的效果:
type fun func(int, int) int // 定义函数类型 fun
5. 函数特性
- 不支持函数重载(overload)。
- 不能嵌套函数,但可嵌套匿名函数。
- 函数是值,可赋值给变量、作为参数传递或作为返回值。
- 调用时参数使用值传递(先拷贝副本再传递)。
6. 函数示例
package main
import "fmt"
// 无返回值函数
func test01() {
fmt.Println("not return a value")
}
// 函数类型别名
type fun func(int, int) int
// 求和函数(有返回值)
func sum(a int, b int) (ret int) {
ret = a + b
return ret
}
// 比较大小函数
func compare(a int, b int) (max int) {
if a > b {
max = a
} else {
max = b
}
return max
}
// 多返回值(可省略 return 参数)
func test02() (name string, age int) {
name = "tom"
age = 20
return // 等价于 return name, age
}
// return 覆盖命名返回值
func test03() (name string, age int) {
a := "jim"
b := 30
return a, b
}
// 值传递示例
func test04(a int) {
a = 200
fmt.Printf("a = %d\n", a)
}
// 引用类型修改示例(slice)
func test05(a []int) {
a[0] = 10
}
// 变长参数示例 1
func printArgs1(args ...int) {
for _, v := range args {
fmt.Printf("%v ", v)
}
fmt.Println()
}
// 变长参数示例 2(含固定参数)
func printArgs2(name string, age int, args ...int) {
fmt.Printf("name: %v, age: %v, args: ", name, age)
for _, v := range args {
fmt.Printf("%v ", v)
}
fmt.Println()
}
func main() {
ret := sum(1, 2)
fmt.Printf("ret = %d\n", ret) // 输出:ret = 3
max := compare(1, 3)
fmt.Printf("max = %d\n", max) // 输出:max = 3
fmt.Println("------------------------")
test01() // 输出:not return a value
name, age := test02()
fmt.Printf("name: %v, age: %v\n", name, age) // 输出:name: tom, age: 20
name, _ = test03() // 丢弃 age 返回值
fmt.Printf("name: %v, age: %v\n", name, age) // 输出:name: jim, age: 20
// 值传递演示
a := 100
test04(a) // 输出:a = 200
fmt.Printf("a = %d\n", a) // 输出:a = 100(外部值未变)
fmt.Println("------------------------")
// 引用类型修改演示
s := []int{1, 2, 3}
test05(s)
fmt.Printf("s: %v\n", s) // 输出:s: [10 2 3](内部修改影响外部)
fmt.Println("------------------------")
// 变长参数调用
printArgs1(1, 2, 3, 4, 5) // 输出:1 2 3 4 5
printArgs2("tom", 20, 1, 2, 3, 4, 5) // 输出:name: tom, age: 20, args: 1 2 3 4 5
fmt.Println("------------------------")
// 函数变量使用
var f fun
f = sum
result := f(1, 3)
fmt.Printf("sum = %v\n", result) // 输出:sum = 4
f = compare
result = f(1, 3)
fmt.Printf("max = %v\n", result) // 输出:max = 3
}
二、defer 语句
1. 作用与执行顺序
defer 用于延迟执行语句,在所属函数即将返回时,按 defer 定义的逆序(先进后出,类似栈)执行。
2. 特性
- 注册的延迟调用在 return 前执行。
- 多个 defer 按“先进后出”顺序执行。
- defer 语句中的变量值在声明时确定。
- 若遇 panic,会先执行所有 defer 函数再 panic。
3. 主要用途(资源释放)
- 关闭文件句柄。
- 释放锁资源。
- 释放数据库连接。
三、指针
1. 基本概念
指针变量指向值的内存地址,通过指针可修改函数外部变量,避免数据拷贝。
2. 指针类型与声明
-
指针类型格式:
*Type(如*int*string)。 -
声明语法:
var ip *int // 声明 int 类型指针 var fp *float32 // 声明 float32 类型指针
3. 指针数组
数组元素均为指针,声明示例:
const MAX int = 3
var ptr [MAX]*int // 包含 3 个 int 指针的数组
4. 指针使用限制
Go 指针仅支持取地址(&)和根据地址取值(*),不支持偏移和运算。
5. 指针示例
package main
import "fmt"
const MAX int = 3
func main() {
// 指针基本使用(& 取地址,* 取值)
var a int = 20
var ip *int
ip = &a
fmt.Printf("%v\n", &a) // 输出:a 的内存地址(如 0xc000016098)
fmt.Printf("%v\n", ip) // 输出:同 &a(指针存储的地址)
fmt.Printf("ip = %v\n", *ip) // 输出:ip = 20(指针指向的值)
fmt.Println()
// 指针数组示例
arr1 := []int{1, 2, 3}
var arr2 [MAX]*int
fmt.Println(arr1) // 输出:[1 2 3]
fmt.Println(arr2) // 输出:[<nil> <nil> <nil>]
for i := 0; i < MAX; i++ {
arr2[i] = &arr1[i]
fmt.Printf("%v ", *arr2[i]) // 输出:1 2 3
}
fmt.Println()
fmt.Println(arr2) // 输出:arr2 中存储的 arr1 元素地址
}
四、高阶函数
1. 匿名函数
(1)定义与特点
- 无名称的函数,格式:
func (参数列表) (返回值)。 - 可在函数内部定义(Go 不允许嵌套普通函数,但允许嵌套匿名函数)。
- 可直接赋值给变量或立即执行。
(2)示例
package main
import "fmt"
// 函数作为参数(类似回调)
func sayHello(name string) {
fmt.Printf("hello, %s\n", name)
}
func f1(name string, f func(string)) {
f(name)
}
// 函数作为返回值
func add(x, y int) int {
return x + y
}
func sub(x, y int) int {
return x - y
}
func cal(s string) func(int, int) int {
switch s {
case "+":
return add
case "-":
return sub
default:
return nil
}
}
func main() {
f1("tom", sayHello) // 输出:hello, tom
addFunc := cal("+")
ret := addFunc(1, 3)
fmt.Printf("ret = %v\n", ret) // 输出:ret = 4
subFunc := cal("-")
ret = subFunc(3, 1)
fmt.Printf("ret = %v\n", ret) // 输出:ret = 2
fmt.Println("---------------------")
// 匿名函数赋值给变量
max := func(a int, b int) int {
if a > b {
return a
} else {
return b
}
}
i := max(3, 1)
fmt.Printf("max = %v\n", i) // 输出:max = 3
// 匿名函数立即执行
result := func(a int, b int) int {
if a > b {
return a
} else {
return b
}
}(3, 1)
fmt.Printf("立即执行结果:%v\n", result) // 输出:立即执行结果:3
}
2. 闭包
(1)概念
闭包 = 函数 + 引用环境,即外层函数中定义的内层函数,操作外层函数的局部变量(参数或内部变量),且外层函数返回该内层函数。
(2)特性
外层函数的局部变量生命周期延长,不会随外层函数结束而销毁(因内层函数仍需使用)。
(3)示例
package main
import (
"fmt"
"strings"
)
// 闭包基础示例
func add1() func(int) int {
var x int // 外层函数局部变量
return func(y int) int {
x += y // 内层函数操作外层变量
return x
}
}
// 带参数的闭包
func add2(x int) func(int) int {
return func(y int) int {
x += y
return x
}
}
// 为文件名添加后缀的闭包
func makeSuffix(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}
// 返回多个闭包
func cal(base int) (func(int) int, func(int) int) {
add := func(x int) int {
base += x
return base
}
sub := func(x int) int {
base -= x
return base
}
return add, sub
}
func main() {
// 基础闭包演示
f1 := add1()
fmt.Println(f1(10)) // 输出:10(x=10)
fmt.Println(f1(20)) // 输出:30(x=10+20)
fmt.Println(f1(30)) // 输出:60(x=30+30)
f2 := add1() // 新闭包,独立 x
fmt.Println(f2(40)) // 输出:40
fmt.Println(f2(50)) // 输出:90
fmt.Println("--------------")
// 带参数闭包演示
f3 := add2(10) // 初始 x=10
fmt.Println(f3(10)) // 输出:20(10+10)
fmt.Println(f3(20)) // 输出:40(20+20)
fmt.Println(f3(30)) // 输出:70(40+30)
f4 := add2(20) // 初始 x=20
fmt.Println(f4(40)) // 输出:60(20+40)
fmt.Println(f4(50)) // 输出:110(60+50)
fmt.Println("--------------")
// 文件名后缀闭包
jpgFunc := makeSuffix(".jpg")
txtFunc := makeSuffix(".txt")
fmt.Println(jpgFunc("test")) // 输出:test.jpg
fmt.Println(txtFunc("test")) // 输出:test.txt
fmt.Println("--------------")
// 多闭包共享基础值
f5, f6 := cal(10) // 初始 base=10
fmt.Println(f5(2), f6(3)) // 输出:12(10+2) 9(12-3)
fmt.Println(f5(3), f6(4)) // 输出:12(9+3) 8(12-4)
fmt.Println(f5(4), f6(5)) // 输出:12(8+4) 7(12-5)
}
3. 递归函数
(1)注意事项
- 必须定义退出条件,避免死循环。
- 可能产生大量 goroutine 或导致栈内存溢出。
(2)示例
package main
import "fmt"
// 阶乘(n! = n × (n-1) × ... × 1)
func fac(n int) int {
if n == 1 { // 退出条件
return 1
}
result := n * fac(n-1)
fmt.Println(n, result)
return result
}
// 斐波那契数列(第 n 项 = 第 n-1 项 + 第 n-2 项,前两项为 1)
func fib(n int) int {
if n == 1 || n == 2 { // 退出条件
return 1
}
return fib(n-1) + fib(n-2)
}
func main() {
fac(5) // 输出:2 2;3 6;4 24;5 120
for i := 1; i < 10; i++ {
fmt.Printf("%d ", fib(i)) // 输出:1 1 2 3 5 8 13 21 34
}
fmt.Println()
}
五、init 函数
1. 作用与特点
- 特有的初始化函数,先于 main 执行,用于包级初始化(类似 C++ 构造函数)。
- 无参数、无返回值,自动执行,不能被调用。
- 每个包、每个源文件可包含多个 init 函数。
2. 执行顺序
- 初始化顺序:变量初始化 → init() → main()。
- 同一文件:按 init 定义顺序从上到下执行。
- 同一包不同文件:按文件名字符串升序执行各文件的 init。
- 不同包:若无依赖,按 main 包 import 顺序执行;若有依赖,最后被依赖的包先初始化(如 import 顺序 main-A-B-C,初始化顺序 C-B-A-main)。
3. 与 main 函数的对比
| 相同点 | 不同点 |
|---|---|
| 均为 Go 保留函数;无参数、无返回值;仅由 Go 自动调用 | init 可在任意包中定义,可多个;main 仅在 main 包中,仅一个;init 先于 main 执行 |
4. 注意事项
- 避免循环 import(如 A-B-C-A)。
- 一个包被多次 import 仅初始化一次。
- 建议使用主动初始化,避免依赖 init 执行顺序。
5. 示例
package main
import "fmt"
// 一个包中可以多个 init 函数,但是 init 的执行顺序没有明确定义
// 变量初始化(先于 init)
var a int = initVar()
// 第一个 init 函数
func init() {
fmt.Println("init1")
}
// 第二个 init 函数
func init() {
fmt.Println("init2")
}
// 变量初始化函数
func initVar() int {
fmt.Println("init var")
return 10
}
func main() {
fmt.Println("main...")
}
/* 输出:
init var
init1
init2
main...
*/
六、builtin 包(内置标识符与函数)
builtin 并非实际包,包含 Go 预定义的标识符和函数。官方文档:https://pkg.go.dev/builtin@go1.19.1
1. nil 变量
nil 是指针、通道、函数、接口、map、切片的零值,声明格式:
var nil Type // Type 必须是指针、通道、函数、接口、map 或切片类型
2. 常用内置函数
(1)append
-
作用:向切片追加元素。
-
原型:
func append(slice []Type, elems ...Type) []Type
(2)cap
-
作用:返回容量(切片、数组、通道的最大存储能力)。
-
原型:
func cap(v Type) int
(3)close
-
作用:关闭通道(仅用于发送通道
chan<- Type)。 -
原型:
func close(c chan<- Type)
(4)copy
-
作用:拷贝切片内容(将 src 拷贝到 dst)。
-
原型:
func copy(dst, src []Type) int // 返回实际拷贝的元素数
(5)delete
-
作用:删除 map 中的键值对。
-
原型:
func delete(m map[Type]Type1, key Type)
(6)len
-
作用:返回长度(字符串、数组、切片、map、通道的当前元素数)。
-
原型:
func len(v Type) int
(7)make
-
作用:为 slice、map、chan 分配空间并初始化。
-
原型:
func make(t Type, size ...IntegerType) Type
(8)new
-
作用:分配内存(初始化为零值),返回指向该内存的指针。
-
原型:
func new(Type) *Type
(9)print 与 println
-
作用:打印内容(简单输出,不支持格式化)。
-
原型:
func print(args ...Type) func println(args ...Type) // 打印后换行
(10)panic 与 recover
-
panic:引发恐慌,立即停止当前调用并抛出异常。
func panic(v any) -
recover:在延迟函数中执行,用于恢复恐慌,返回 panic 传递的值;若在延迟函数外调用或无恐慌,返回 nil。
func recover() any