当前位置: 首页 > news >正文

【Golang】:函数和包

目录

1. 函数

1.1 函数定义

1.2 函数传参

1.3 函数返回值

1.4 可变参数

1.5 函数类型

1.6 匿名函数

1.7 defer 延迟调用

1.8 闭包(Closure)

2. 包

2.1. 基本概念

2.2 使用方式

2.3 init 函数


1. 函数

1.1 函数定义

func 函数名(参数列表) 返回值类型 {// 函数体return 返回值
}

注意: Go中的函数不支持函数重载

package mainimport "fmt"func add(a int, b int) int {return a + b
}
func main() {fmt.Println(add(1, 2))}

1.2 函数传参

参数传递的方式有两种:

  • 值传递:传参时传递的是值的拷贝,函数内部对参数的修改不会影响到原始数据,值类型参数默认采用的就是值传递,包括基本数据类型、数组和结构体。
  • 引用传递:传参时传递的是地址的拷贝,在函数内部对参数的修改会影响到原始数据,引用类型参数默认采用引用传递,包括指针、切片、管道、接口等。
package mainimport "fmt"// 值传递,无法改变内部的数据
func swap(a int, b int) {temp := aa = bb = temp
}
func main() {var a int = 100var b int = 200fmt.Println("a: ", a, "b: ", b)     // a:  100 b:  200swap(a, b)fmt.Println("a: ", a, "b: ", b)     // a:  100 b:  200}

引用传参:

package mainimport "fmt"func swap(a *int, b *int) {temp := *a*a = *b*b = temp
}
func main() {var a int = 100var b int = 200fmt.Println("a: ", a, "b: ", b) // a:  100 b:  200swap(&a, &b)fmt.Println("a: ", a, "b: ", b) // a:  200 b:  100}

1.3 函数返回值

1. 返回多个值

Go中函数支持返回多个值,通过返回值列表指明各个返回值的类型即可。如下:

package mainimport "fmt"func GetSumAndSub(a int, b int) (int, int) { // 返回多个值add := a + bsub := a - breturn add, sub
}func main() {add, sub := GetSumAndSub(10, 20)fmt.Println(add) // 30fmt.Println(sub) // -10
}

2. 忽略返回值

如果函数返回多个值,在接收时,可以通过_(占位符)忽略不需要的返回值。如下:

package mainimport "fmt"func GetSumAndSub(a int, b int) (int, int) { // 返回多个值add := a + bsub := a - breturn add, sub
}func main() {_, sub := GetSumAndSub(10, 20)fmt.Println(sub) // -10
}

3. 返回值命名

Go中函数支持在返回值列表给返回值命名,这时函数在返回时无需在return后指明需要返回的值,可以避免返回顺序出错。如下:

package mainimport "fmt"func GetSumAndSub(a int, b int) (add int, sub int) { // 返回多个值add = a + bsub = a - breturn
}func main() {add, sub := GetSumAndSub(10, 20)fmt.Println(add) // 30fmt.Println(sub) // -10
}

1.4 可变参数

参数数量不确定时使用:

  • 如果一个函数的形参列表中有可变参数,则可变参数需要放在形参列表的最后。
package mainimport "fmt"func sum(nums ...int) int {total := 0for _, v := range nums {total += v}return total
}func main() {fmt.Println(sum(1))               // 1fmt.Println(sum(1, 2))            // 3fmt.Println(sum(1, 2, 3))         // 6fmt.Println(sum(1, 2, 3, 4))      // 10
}

1.5 函数类型

在Go中函数也是一种数据类型,可以将其赋值给一个变量,然后通过该变量即可对函数进行调用。如下:

package mainimport "fmt"func add(num1 int, num2 int) int {return num1 + num2
}func main() {sumFunc := addfmt.Printf("类型:%T, 值:%d", sumFunc, sumFunc(1, 2))    // 类型:func(int, int) int, 值:3
}

类型定义和类型别名

1. 类型定义

  • 这里 MyInt 是一个新类型,它的底层类型是 int,但是编译器认为 intMyInt 是两个不同的类型。

  • 所以直接赋值会报错,必须 显式类型转换

package mainimport "fmt"func main() {type myInt intvar a myInt = 100var b int = 200fmt.Printf("类型:%T, 值:%d\n", a, a) // 类型:main.myInt, 值:100fmt.Printf("类型:%T, 值:%d\n", b, b) // 类型:int, 值:200// a = b     // 错误,a = myInt(b)fmt.Printf("类型:%T, 值:%d\n", a, a) // 类型:main.myInt, 值:200fmt.Printf("类型:%T, 值:%d\n", b, b) // 类型:int, 值:200
}

2. 类型别名

  • 这里 MyInt 只是 int别名,两者完全等价,编译器认为它们就是同一个类型。

  • 因此可以直接赋值,不需要转换

package mainimport "fmt"func main() {type myInt = intvar a myInt = 100var b int = 200fmt.Printf("类型:%T, 值:%d\n", a, a) // 类型:int, 值:100fmt.Printf("类型:%T, 值:%d\n", b, b) // 类型:int, 值:200a = b                             // 可以直接赋值fmt.Printf("类型:%T, 值:%d\n", a, a) // 类型:int, 值:200fmt.Printf("类型:%T, 值:%d\n", b, b) // 类型:int, 值:200
}

    在Go中将函数作为形参也是常见的用法,这时结合自定义数据类型给函数类型取别名,能有效提高代码的可读性。如下:

    package mainimport "fmt"func Sum(a int, b int) int {return a + b
    }type SumType func(int, int) int // 自定义数据类型func MyFunc(f SumType, num1 int, num2 int) int {return f(num1, num2)
    }func main() {result := MyFunc(Sum, 10, 20)fmt.Printf("值:%d\n", result) // 值:30
    }
    

    1.6 匿名函数

    如果希望某个函数只使用一次,可以考虑使用匿名函数,定义时直接使用,不必命名:

        func(a int, b int) int{return a + b}()

    可以赋值给变量,进行使用:

    package mainimport "fmt"func main() {// 定义匿名函数并调用sum1 := func(a int, b int) int {return a + b}(1, 2)fmt.Printf("类型:%T, 值:%d\n", sum1, sum1) // 类型:int, 值:3// 定义匿名函数sum2 := func(a int, b int) int {return a + b}fmt.Printf("类型:%T, 值:%d\n", sum2, sum2(100, 200)) // 类型:func(int, int) int, 值:300
    }
    

    1.7 defer 延迟调用

    1. defer 的作用:用来延迟执行某些操作,通常是释放资源

    • 文件关闭(file.Close()
    • 解锁(mu.Unlock()
    • 数据库连接释放
    • 网络连接关闭

    这样写可以避免忘记释放资源,即使函数发生 panicdefer 语句依然会执行。

    2. 执行顺序

    • 当程序执行到 defer 时,不会立刻执行,而是把该语句 压入 defer 栈

    • 当函数返回时,Go 会按照 后进先出(LIFO) 的顺序执行这些语句。

    • defer 语句的参数会在声明时就被求值,而不是在函数结束时。

    package mainimport "fmt"func demo() {defer fmt.Println("A")defer fmt.Println("B")fmt.Println("C")
    }func test() {x := 10defer fmt.Println("defer x =", x) // 保存的是 x=10x = 20fmt.Println("x =", x)
    }func main() {demo()         // C B Atest()         // x = 20  defer x = 10
    }
    

    常见应用场景

    • 在Go中通常会在创建资源后,通过defer语句将资源关闭,由于defer语句会在当前函数执行完毕后再执行,因此在defer语句之后仍然可以使用创建的资源。
    • 在其他语言中,资源释放时机是一个常见的问题,而Go中的defer机制就使得资源的创建和释放可以成对存在,程序员再也不用担心资源释放时机的问题了。
    func FileOperation(filename string) (err error) {file, err := os.Open(filename) // 打开文件if err != nil {fmt.Printf("open file error, err = %v\n", err)return}defer file.Close() // 关闭文件// 进行文件操作...return
    }

    注意事项(常见坑)

    • 多个 defer 的执行顺序是反向的(栈结构)

    • defer 的参数在声明时就确定了,不会随着变量变化而改变

    • defer 与 return 结合时,如果 defer 修改了返回值(需要命名返回值),可能导致结果不同。

    package mainimport "fmt"func f() (x int) {defer func() { x++ }()return 3
    }func main() {x := f()fmt.Println("值:%d\n", x)      // 4
    }
    

      1.8 闭包(Closure)

      1. 什么是闭包?

      • 闭包 = 函数 + 外部变量引用环境

      • 它允许函数“记住”并操作其外部作用域中的变量,即使该作用域已经结束。

      package mainimport "fmt"func add(a int) func(int) int {var sum = areturn func(x int) int {sum += xreturn sum}
      }func main() {var posSum = add(10)fmt.Printf("类型:%T\n", posSum)    // 类型:func(int) intfmt.Printf("值:%d\n", posSum(10))  // 值:20fmt.Printf("值:%d\n", posSum(20))  // 值:40fmt.Printf("值:%d\n", posSum(30))  // 值:70}
      
      • sum 是外部变量,func(x int) int 是内部函数。

      • 即使 add() 执行完毕,sum 依然被闭包函数捕获并存储在内存中。

      注意:闭包记住环境,变量延长寿命;引用而非拷贝,循环要格外小心。

      2. 包

      2.1. 基本概念

      1. 包 = 代码的最小组织单元

      2. 每个 Go 源文件都必须声明一个 package

      3. 同一个目录下的 .go 文件必须属于同一个包(除非是 main 包)。

      4. 首字母大写:对外可见(公有)。

      5. 首字母小写:仅包内可见(私有)。

      6. 可管理性:大项目通常会按功能划分多个包,例如:modelscontrollersservices

      2.2 使用方式

      包的使用方式可以分为四部:打包导入包给包取别名使用包

      1. 打包

      // 在 .go 文件的第一行写上
      package 包名// 注意:同一目录下的 .go 文件必须属于同一个包。

      2. 导入包

      // 在需要使用的地方通过 import 引入
      import "test_go/hello"

      3. 给包取别名

      注意:取了别名就只能用别名

      package mainimport ("fmt"h "test_go/hello"     // 可以避免包名冲突,或缩短使用
      )func main() {fmt.Println(h.Add(1, 2))
      }
      

      4. 使用包

      // 通过 包名.标识符 调用
      // 注意:只有 首字母大写 的函数/变量/类型才能被外部包访问。
      package mainimport ("fmt"h "test_go/hello"
      )func main() {fmt.Println(h.Add(1, 2))
      }
      

      2.3 init 函数

      1. 基本概念

      • 每个 Go 源文件都可以包含 一个或多个 init 函数。

      • init 函数在 程序运行前自动调用,且 main() 之前执行

      • 不能被显式调用,即你不能在代码里写 init()

      2. 执行顺序

      1. 包级变量初始化:先初始化包里的全局变量。

      2. 执行 init 函数:包中可能有多个 init,会按照它们在源码中出现的顺序执行,但这并不会产生重定义报错,因为init函数在编译阶段会被编译器处理为特殊的符号,确保所有init函数被正确执行而不会发生冲突。

      3. 导入包的 init

        • 如果 main 包依赖其它包,会先初始化依赖包(递归执行)。

        • 即:先初始化被导入的包,再初始化当前包

      4. 最后才执行 main()

      依赖包变量初始化 → 依赖包 init() → 当前包变量初始化 → 当前包 init() → main()
      
      package mainimport ("fmt"
      )var num = initNum()func initNum() int {fmt.Println("初始化全局变量 num")return 100
      }
      func init() {fmt.Println("init1() 被调用")
      }
      func init() {fmt.Println("init2() 被调用")
      }func main() {fmt.Println("main() 被调用")fmt.Println("num =", num)
      }// 初始化全局变量 num
      // init1() 被调用
      // init2() 被调用
      // main() 被调用
      // num = 100
      

      如果 main 包导入了其他包,main包初始化之前,会先对其导入的包进行初始化。

      package mainimport ("fmt"h "test_go/hello"
      )var num = initNum()func initNum() int {fmt.Println("初始化全局变量 num")return 100
      }
      func init() {fmt.Println("init() 被调用")
      }func main() {fmt.Println("main() 被调用")fmt.Println("num =", num)fmt.Println(h.Add(1, 2))
      }
      /*
      hello.go中init() 被调用
      初始化全局变量 num
      init() 被调用
      main() 被调用
      num = 100
      3
      */

      http://www.dtcms.com/a/336618.html

      相关文章:

    • 因果语义知识图谱如何革新文本预处理
    • os详解,从上面是‘os‘模块?到核心组成和常用函数
    • 智能合约里的 “拒绝服务“ 攻击:让你的合约变成 “死机的手机“
    • 什么是AI Agent(智能体)
    • nature子刊:MCNN基于电池故障诊断的模型约束的深度学习方法
    • [Oracle数据库] Oracle 多表查询
    • 网络常识-我的电脑啥时安装了证书
    • 生成模型实战 | InfoGAN详解与实现
    • java如何使用正则提取字符串中的内容
    • 谈谈对面向对象OOP的理解
    • 深入分析 Linux PCI Express 子系统
    • Highcharts 官方文档与 API 查询技巧解析
    • android aidl相关学习
    • 【昇腾】单张48G Atlas 300I Duo推理卡MindIE+WebUI方式跑14B大语言模型_20250817
    • 在鸿蒙中实现深色/浅色模式切换:从原理到可运行 Demo
    • 母猪姿态转换行为识别:计算机视觉与行为识别模型调优指南
    • redis和cdn的相似性和区别
    • 编程算法实例-最小公倍数
    • Python自学09-常用数据结构之元组
    • 黑马商城day08-Elasticsearch作业(个人记录、仅供参考、详细图解)
    • 嵌入式系统中的签名验证:设计与原理解析(C/C++代码实现)
    • Java基础Object中常见问题解析
    • Redis面试精讲 Day 24:Redis实现限流、计数与排行榜
    • 数字货币的法律属性与监管完善路径探析
    • SCAI采用公平发射机制成功登陆LetsBonk,60%代币供应量已锁仓
    • SpringBoot中,接口加解密
    • C语言课程开发
    • 【前端基础】flex布局中使用`justify-content`后,最后一行的布局问题
    • Java 基础 -- Java 基础知识
    • 2025-08-17 李沐深度学习18——循环神经网络基础