定义函数
- 使用关键字:
func - 函数名要求:
- 使用小驼峰(go 语言中 大驼峰有特殊意义)
- 不能以数字开头
- 尽量见名知意
go
/*
定义函数的语法如下:
func 函数名(参数列表) 返回值类型列表 {
函数体
return 返回值列表
}
*/
package main
import (
"fmt"
)
// 1.定义函数
func sum(n1 int, n2 int) int {
return n1 + n2
}
// 2.调用函数
func main() {
result := sum(1, 2)
fmt.Println("1 + 2 =", result)
}参数
go
func print(n1 int, n2 int) {
fmt.Println(n1, n2)
}go
package main
import "fmt"
// 调用时可以传入任意多个 int 类型的参数
// 会自动收集到一个 int 类型的切片中
func printNumbers(nums ...int) {
fmt.Println(nums)
}
func main() {
printNumbers(1, 2, 3, 4)
// 输出 [1,2,3,4]
}返回值
go
func printAll(n1 int, n2 int) {
fmt.Println(n1, n2)
}go
func sum(n1 int, n2 int) int {
return n1 + n2
}go
// 注意返回多少个值, 都要在返回值类型列表中声明
func increment(n1 int, n2 int) (int, int) {
n1 += 1
n2 += 1
return n1, n2
}go
package main
import "fmt"
func increment(num int) (incInt int, incFloat float32) {
incInt = num + 1
incFloat = float32(num) + 0.1
// 0.return 关键字必须要有
// 1.return 会自动返回 incInt, decFloat 变量
// 2.要使用自动返回, 在声明返回值类型的时候必须同时声明变量名和类型
// 3.在接收函数返回值的时候, 必须按照声明的顺序接收
return
}
func main() {
incInt, incFlt := increment(5)
fmt.Println("incInt=", incInt) // 6
fmt.Println("incFloat=", incFlt) // 5.1
}值传递与引用传递
- 引用传递的本质就是
(内存)地址传递
go
package main
import "fmt"
func increment(num int) int {
num += 1
return num
}
func incrementPtr(numPtr *int) {
*numPtr += 1
}
func main() {
// 直接调用函数而不重新赋值是不行的, 因为 increment 是值传递
// increment(num)
// 将 num 的值修改为 increment 函数的返回值
num := 10
num = increment(num)
fmt.Println("num is", num)
// 直接传递 num2 的地址, incrementPtr 函数可以根据地址
// 直接修改num2 对应内存地址的值, 不需要重新赋值
num2 := 20
incrementPtr(&num2)
fmt.Println("num2 is", num2)
}go
package main
import "fmt"
func incrementNumbers(nums [5]int) {
// 在 GO 语言中, 即使是复杂数据类型(包括:数组/struct/channel)
// 默认情况下都是 "值" 传递
// 所以在函数内部输出的变量的内存地址和main函数中输出的不一样
fmt.Printf("[incrementNumbers]数据内容: %v\n", nums)
fmt.Printf("[incrementNumbers]变量类型: %T\n", nums)
fmt.Printf("[incrementNumbers]内存地址: %p\n", &nums)
for i := 0; i < len(nums); i++ {
nums[i] = nums[i] + 1
}
}
func incrementNumbersByPtr(nums *[5]int) {
// 只有在明确指定是使用指针类型的时候, 传递的才是指针
fmt.Printf("[incrementNumbersByPtr]数据内容: %v\n", nums)
fmt.Printf("[incrementNumbersByPtr]变量类型: %T\n", nums)
fmt.Printf("[incrementNumbersByPtr]内存地址: %p\n", &nums)
// 注: 如果是指针变量, 需要解应用才能获取到原内存存储的值
for i := 0; i < len(*nums); i++ {
(*nums)[i] += 1
}
}
func main() {
nums := [5]int{1, 2, 3, 4, 5}
fmt.Printf("[main]内存地址: %p\n", &nums)
// nums 中的值并没有改变, 因为是值传递
// 在函数内部修改的是 nums 的副本(内存地址都不一样)
incrementNumbers(nums)
fmt.Printf("[main]数据内容: %v\n", nums)
// nums 中的值被改变了, 因为是引用用传递
// 在函数内部修改的 nums 是通过内存地址找到的原件
incrementNumbersByPtr(&nums)
fmt.Printf("[main]数据内容: %v\n", nums)
}go
package main
import "fmt"
// 引用传递
func incrementNumbers(nums []int) {
for i := range nums {
nums[i] += 1
}
}
func main() {
nums := []int{1, 2, 3, 4, 5}
incrementNumbers(nums)
fmt.Printf("数据:%v 类型:%T\n", nums, nums)
// 为什么数组是值传递, 而切片却是引用传递?
// 0.数组的大小是固定的,它的数据存放在栈道
// 1.切片的大小是不确定的,所以数据一定存放在堆区
// 2.可以简单的理解为: 存在堆区的数据必须是引用传递的
// 3.映射表 map 同理可知
}函数的类型
go
package main
import "fmt"
func increment(num int) int {
num += 1
return num
}
func main() {
// 注意: 我并没有用()调用这个函数
inc := increment
fmt.Printf("inc 的类型是: %T, increment 的类型是 %T", inc, increment)
// 输出如下:
// inc 的类型是: func(int) int, increment 的类型是 func(int) int
// 通过变量调用函数
result := inc(10) // 就相当于执行了: increment(10)
fmt.Println("\nresult 的值是: ", result)
}将函数类型作为参数
既然函数可以赋值给一个变量, 那么函数就可以作为参数传递给另外一个函数
go
package main
import "fmt"
func each(count int, handler func(int)) {
for i := range count {
handler(i)
}
}
func printNumber(num int) {
fmt.Println("num =", num)
}
func main() {
// 传入 printNumber 函数作为参数
each(5, printNumber)
fmt.Println("---------------")
// 直接传入匿名函数作为参数
each(10, func(i int) {
fmt.Println("i =", i)
})
}匿名函数
就是没有名称的函数类型的值, 通常用于赋值给变量, 或者当作参数传递
go
package main
import "fmt"
func main() {
// 将一个匿名函数赋值给一个变量
printNumber := func(num int) {
fmt.Println("num =", num)
}
// 通过变量名调用函数
printNumber(10)
}内置函数
所谓的内置函数(也叫内建函数)其实就是 builtin 这个包中所有的函数, 可以直接使用, 不需要导入 它的源码放在 go 语言安装目录的 go/src/1.25.5/builtin/builtin.go 这个文件中
go
package main
func main() {
// 这个 println 就是内置函数
// 但是 ctrl+鼠标左键 点击进去查看源码会发现
// 这个文件中, 只有注释和类型定义, 并没有实现,
// 其实最后, 还是使用的 fmt.Println 函数
println("hello world")
}作用域
在 Go 中, 作用域分为:
全局作用域和局部作用域- 局部作用域也叫
函数作用域
go
package main
import "fmt"
// 全局作用域
var step int = 1
func increment(num int) int {
// 在这个函数中可以访问全局作用域的变量
res := num + step
// 局部作用域:
// 只有在 increment 函数中才能访问 res 变量
return res
}
func main() {
// 在这个函数中可以访问全局作用域的变量
fmt.Println("step:", step)
fmt.Println("increment: ", increment(1))
// 访问变量就近原则:
// 如果当前作用域能找到变量, 就使用当前作用域的变量
step := 10
fmt.Println("local step:", step) // 10
}defer 关键字
defer 是 Go 语言中一个非常独特的关键字, 用于延迟函数的执行, 将其推迟到包含它的函数返回之前执行, 这个特性使得资源清理和错误处 理变得更加简洁和可靠, 同时也会带来一些额外的开销
基本用法
go
package main
import "fmt"
func main() {
// 在函数调用前加 defer 即可
defer fmt.Println("1.defer fmt.Println executed")
// 不论是否是匿名函数都可以用
defer func() {
fmt.Println("2.defer func executed")
}()
}执行顺序 LIFO
多个 defer 语句按照后进先出的顺序执行
go
package main
import "fmt"
func main() {
defer fmt.Println("1.defer fmt.Println executed")
defer func() {
fmt.Println("2.defer func executed")
}()
// go run main.go 的输出结果:
// 2.defer func executed
// 1.defer fmt.Println executed
}获取参数值的时机
go
package main
import "fmt"
func main() {
num := 1
// 此时不会执行, 但是会获取参数的值
// 并不是等到执行那一刻才获取参数的值
defer fmt.Println("1.num = ", num)
num = 10
fmt.Println("2.num = ", num)
// 所以输出的结果是:
// 2.num = 10
// 1.num = 1
}go
package main
import "fmt"
func main() {
num := 1
// 如果想要延迟获取, 那么只需要给原语句
// 套一层匿名函数即可, 此时 defer 修饰的
// 是这个 func(){}() 匿名函数,
defer func() {
// 真正执行时才会获取 num 的值
fmt.Println("1.num = ", num)
}()
num = 10
fmt.Println("2.num = ", num)
// 所以输出结果是:
// 2.num = 10
// 1.num = 10
}错误处理
defer + recover 捕获错误
在go语言中, 没有 try catch 关键字, 那么它是如何管理 可预测的代码异常的
go
package main
import "fmt"
func test() {
num1 := 10
num2 := 0
result := num1 / num2 // 除 0 会报错
// panic: runtime error: integer divide by zero
// goroutine 1 [running]:
// main.main()
// /Users/secret/codes/golang-demo/main.go:8 +0x9
// exit status 2
// 这一行不会执行
fmt.Printf("%d / %d = %d \n", num1, num2, result)
}
func main() {
test()
// 后续的代码不会执行
fmt.Printf("后续的代码不会执行")
}go
package main
import "fmt"
func test() {
// defer 一个自执行匿名函数, 让他延迟执行
defer func() {
// 捕获错误
err := recover()
if err != nil {
fmt.Println("捕获到代码错误", err)
}
}()
num1 := 10
num2 := 0
result := num1 / num2
// 这一行不会执行
fmt.Printf("%d / %d = %d \n", num1, num2, result)
}
func main() {
test()
fmt.Println("后续代码执行了")
// 控制台输出如下:
// 捕获到代码错误 runtime error: integer divide by zero
// 后续代码执行了
}自定义错误与错误的类型
go
package main
import (
"errors"
"fmt"
)
// 1.能够这样设置返回值的类型说明:
// err 是 error 类型, 代表一个具体的错误
// nil 是 error 类型, 表示没有任何的错误
func test(x int) error {
if x == 0 {
// 2.自定义错误信息, 需要使用 errors 包中的 New 方法
err := errors.New("x 不能为0")
return err
}
return nil
}
func main() {
err := test(0)
if err != nil {
fmt.Printf("err 的类型是: %T \n", err) // err 的类型是: *errors.errorString
}
res := test(1)
fmt.Printf("res 的类型是: %T \n", res) // res 的类型是: <nil>
}手动终止程序执行
上面的例子, 我们发现, 即使出现错误, 也不会中断程序的执行, 只是正常的返回了一个 error 类型的数据
go
package main
import (
"errors"
"fmt"
)
func test(x int) error {
if x == 0 {
err := errors.New("x 不能为0")
return err
}
return nil
}
func main() {
err := test(0)
if err != nil {
// 如果程序出现错误, 手动让程序终止执行
// 相当于其他语言中的手动 throw 一个异常
panic(err)
}
// 后续的代码不会再执行了
res := test(1)
fmt.Printf("res 的类型是: %T \n", res)
}给自定义类型增加处理函数
go
package main
import "fmt"
type Pos [2]int
// GetX/GetY 是定义在 Pos 类型上的方法
// 必须通过 Pos 这个类型的数据来调用
func (p Pos) GetX() int {
return p[0]
}
func (p Pos) GetY() int {
return p[1]
}
// GetX/GetY 是普通方法
// 只要是 [2]int 类型就能调用
func GetX(p Pos) int {
return p[0]
}
func GetY(p Pos) int {
return p[1]
}
func main() {
var p Pos = [2]int{10, 20}
// 通过 Pos 类型调用类型上才有的方法
x1 := p.GetX()
y1 := p.GetY()
fmt.Println("x1, y1:", x1, y1)
// 通过传入 pos 数据, 调用普通方法
x2 := GetX(p)
y2 := GetY(p)
fmt.Println("x2, y2:", x2, y2)
fmt.Println("-------------------")
nums := [2]int{30, 50}
x3 := GetX(nums)
y3 := GetY(nums)
fmt.Println("x3, y3:", x3, y3)
// 这样是无法调用的:
// 因为变量 nums 的类型是 [2]int, 而变量 p 的类型是: Pos
// x4 := nums.GetX()
// y4 := nums.GetY()
// 要是想要像上面那样调用需要先转换类型
p2 := Pos(nums)
x4 := p2.GetX()
y4 := p2.GetY()
fmt.Println("x4, y4:", x4, y4)
}泛形函数
为什么要有泛形函数
如下3个函数, 除了类型不同, 实现逻辑是一模一样的, 就因为类型不同, 就要定义3个处理逻辑一样函数, 这么多重复代码显然是不太合理的, 是否有类似 TS/Java 的泛型来解决代码高度重复的问题
go
package main
import "fmt"
func filterInt(items []int, filterFunc func(int, int) bool) []int {
result := make([]int, 0)
for index, value := range items {
if filterFunc(value, index) {
result = append(result, value)
}
}
return result
}
func filterFloat(items []float64, filterFunc func(float64, int) bool) []float64 {
result := make([]float64, 0)
for index, value := range items {
if filterFunc(value, index) {
result = append(result, value)
}
}
return result
}
func filterString(items []string, filterFunc func(string, int) bool) []string {
result := make([]string, 0)
for index, value := range items {
if filterFunc(value, index) {
result = append(result, value)
}
}
return result
}
func main() {
items := []int{1, 2, 3, 4, 5}
filteredItems := filterInt(items, func(value int, index int) bool {
return value%2 == 1
})
fmt.Println(filteredItems)
floats := []float64{1.1, 2.2, 3.3, 4.4, 5.5}
filteredFloats := filterFloat(floats, func(value float64, index int) bool {
return value > 3
})
fmt.Println(filteredFloats)
langs := []string{"Java", "Rust", "JavaScript", "TypeScript", "python"}
filteredLangs := filterString(langs, func(value string, index int) bool {
return len(value) > 4
})
fmt.Println(filteredLangs)
}使用泛型函数
go
package main
import "fmt"
/*
泛型函数:
func 函数名[定义泛型名称 泛型允许的类型约束] (参数 使用泛型作为参数类型) 使用泛型作为返回值类型 {
// 在函数体中也可以使用泛型来约束变量的类型
}
*/
func filter[T int | float64 | string](items []T, filterFunc func(T, int) bool) []T {
result := make([]T, 0)
for index, value := range items {
if filterFunc(value, index) {
result = append(result, value)
}
}
return result
}
func main() {
items := []int{1, 2, 3, 4, 5}
filteredItems := filter(items, func(value int, index int) bool {
return value%2 == 1
})
fmt.Println(filteredItems)
floats := []float64{1.1, 2.2, 3.3, 4.4, 5.5}
filteredFloats := filter(floats, func(value float64, index int) bool {
return value > 3
})
fmt.Println(filteredFloats)
langs := []string{"Java", "Rust", "JavaScript", "TypeScript", "python"}
filteredLangs := filter(langs, func(value string, index int) bool {
return len(value) > 4
})
fmt.Println(filteredLangs)
}泛形类型
所谓的泛型类型其实就是带有泛型的类型定义
go
package main
import "fmt"
// 定一个 "数字数组类型", 具体是哪种数字用泛型
type NumberArray[T int | int8 | uint8 | int16 | uint16 | int32 | uint32 | int64 | uint64 | float32 | float64] []T
// 给类型绑定求和方法
func (nums NumberArray[T]) sum() T {
result := T(0) // 转类型
for _, num := range nums {
result += num
}
return result
}
func main() {
// 使用类型绑定的函数时, 指定泛型的具体类型为 int
integers := []int{1, 2, 3, 4, 5}
result1 := NumberArray[int](integers).sum()
fmt.Println(result1)
// 使用类型绑定的函数时, 指定泛型的具体类型为 float64
floats := []float64{1.1, 2.2, 3.3, 4.4, 5.5}
result2 := NumberArray[float64](floats).sum()
fmt.Println(result2)
}