=====================================================================
- 我们不可能把所有的函数放在同一个源文件中,可以分门别类的把函数放在不同的源文件中
- 解決同名问题:两个人都想定义一个同名的函数,在同一个文件中是不可以定义相同名字的函数的,此时可以用包来区分。
包
包的定义
- 目录结构:
code/
│ └── demo6/
│ └── utils/
│ └── util.go
│ └── main/
│ └── mian.go
└── go.mod
- 包的定义:
package utils //包的声明,规范上与文件夹名一致
import ( //倒入所需函数
"fmt"
)
//定义包中的函数 utils1
func Utils1(a int) {
fmt.Println("程序调用了utils包中的utils1函数。")
}
主函数:
package main
import (
//"chapter5/demo6/untils" // package chapter5/demo6/untils is not in std (/usr/local/go/src/chapter5/demo6/untils)
//上面的包是在GOROOT下src中寻找的
"code/chapter5/demo6/untils" //gomod管理包,从项目开始导路径
)
func main() {
//特别注意。包内函数要被外界函数访问,函数首字母必须大写!否则就算有定义,外部也是访问不到的(说为定义)
//utils.utils1(10) ./main.go:9:8: undefined: utils.utils1
utils.Utils1(10)
}
程序调用了utils包中的utils1函数。
- 详细说明:
- 文件夹名,go文件名,包名没有必然联系。文件夹——用于导入包时候路径;go文件名——可任意;包名——调用包内函数。
- 在package声明的时候,尽量使得包名和所在的文件夹同名(更加易读,但可不同)
- mian包是入口包,一般放在main文件夹下
- 在自定义包的时候,首先需要注意的是,需要包外调用的函数名一定要大写,否则在外部调用的时候,会显示没有此函数。
./main.go:9:8: undefined: utils.utils1
- 一个目录下的同级文件归属一个包,但可在该文件夹下的子文件夹定义另一个包,如:file1,file2,file3各属于一个包。(如果和文件有同级文件夹,即使子文件夹放的还是同一个包,也无法搜索到,会被认为未定义)——相当于在搜索包的时候,只会搜索导入文件夹下的go文件。
myproject/
└── mypackage/
├── file1.go
├── folder1/
│ └── file2.go
└── folder2/
└── file3.go
- 同一个目录下不能有重复的函数(名称),因为在
main
函数中调用的时候,定位函数为包所在文件夹+包+函数
,格式如下:
package main
import "包所在路径"
func main(){
包.函数()
}
PS:基于后面两点限制,同一文件夹下,不能定义不同的包,所以不存在“同一文件夹下有不同的包,不同包下有同名函数”。
包的导入
GOPATH
方式进行管理包:
- 使用这种方式,首先要配置环境变量GOPATH,项目文件必须放在
GOPATH/src
下 - 使用这种方式进行包管理,在导入自定义包的时候,包放在
GOPATH/src
目录之后进行导入。 - PS:使用这种方式,所有的包程序都糊去GOPATH/src路径下进行寻找,使用go get 下载的资源 也会放在gopath/src下
- 使用
go mod
进行包管理
标准库包:
标准库包是Go语言官方提供的包,它们通常位于Go语言安装的根目录下的 src
文件夹中。例如,标准库包 fmt
的路径可能是 GOROOT/src/fmt
,其中 GOROOT
是你的Go语言安装路径。
直接使用包的名称导入,无需指定完整路径。例如:
import "fmt"
第三方包:
第三方包通常存储在版本控制系统(如GitHub)上。当你使用 go get
或Go Modules下载这些包时,它们会被存放在你的工程目录下的 go/pkg/mod
目录中,每个包都有一个对应的子目录,用于存放不同版本的包。
使用包的完整路径导入,通常是版本控制系统的路径。例如:
import "github.com/gin-gonic/gin"
在代码中引用了名为 gin
的第三方包,该包位于GitHub上的 github.com/gin-gonic
仓库中。导入路径应该准确反映包的位置,以便Go工具能够正确下载和管理依赖。
自定义包:
自定义包是你自己创建的包,通常存储在你的项目中。你可以将自定义包放在项目的 pkg
、internal
或其他自定义目录中,具体位置取决于你的项目结构。
在项目文件下的自定义包的导入方式取决于自定义包的相对位置。假设你有以下项目结构:
myproject/
├── cmd/
│ └── main.go
├── pkg/
│ └── mypackage/
│ └── mypackage.go
└── go.mod
如果你的 main.go
文件位于 cmd
目录下,而你的自定义包 mypackage
位于 pkg
目录下,你可以使用相对路径导入:
// 在 main.go 中导入自定义包
import "../pkg/mypackage"
或者,如果使用 Go Modules 进行包管理,可以使用模块路径:
// 在 main.go 中导入自定义包
import "myproject/pkg/mypackage"
确保你的导入路径相对或绝对地反映了自定义包的位置,这样可以确保Go工具正确地解析和导入你的自定义包。
重命名包
在导入包的时候可以重命名包,并且在后续使用的时候,可以使用重命名代替包名。
package main
import (
test "code/chapter5/demo6/utils"
)
func main() {
test.Utils1(10)
}
初始化函数init()
- 初始化函数
可以用来进行一些初始化的操作,每一个源文件都可以包含—个init函数,该函数会在main函数执行前被Go运行框架调用。
package main
import "fmt"
func init() {
fmt.Println("Init函数被执行")
}
func main() {
fmt.Println("main函数被执行")
}
Init函数被执行
main函数被执行
- 全局变量定义,init函数,main函数的执行流程?
package main
import "fmt"
//全局变量接受函数返回值——如果该变量那个被执行,则输出
var a int= test()
func test () int {
fmt.Println("全局变量被执行")
return 1
}
func init() {
fmt.Println("Init函数被执行")
}
func main() {
fmt.Println("main函数被执行")
}
全局变量被执行
Init函数被执行
main函数被执行
- 多个源文件都有init函数的时候,如何执行:
文件结构:
code/
├── chapter5/
│ └── funcinit/
│ └── main/
│ └── mypackage.go
│ └── utils
│ └──u1.go
│ └──u2.go
└──
main.go
package main
import (
"fmt"
"code/chapter5/demo7/funinit/utils"
)
//全局变量接受函数返回值——如果该变量那个被执行,则输出
var a int= test()
func test () int {
fmt.Println("全局变量被执行")
return 1
}
func init() {
fmt.Println("Init函数被执行")
}
func main() {
//u1.F1()
u1.F2()
fmt.Println("main函数被执行")
}
u1.go
package u1
import "fmt"
var a int= test()
func test () int {
fmt.Println("u1.go全局变量被执行")
return 1
}
func init() {
fmt.Println("u1.go.init函数被执行")
}
func F1() {
fmt.Println("f1函数被执行")
}
u2.go
package u1
import "fmt"
func init() {
fmt.Println("u2.go.init函数被执行")
}
func F2() {
fmt.Println("f2函数被执行")
}
输出:
u1.go全局变量被执行
u1.go.init函数被执行
u2.go.init函数被执行
全局变量被执行
Init函数被执行
f2函数被执行
main函数被执行
从上面的输出可以看到:
- 包中不同的go文件都包含一个
init
函数,所以init函数特殊,可以同名。 - 主函数中,即使不调用u2.go中的函数,其中的init函数同样会执行。
- 包中的全局变量最先执行,即使不调用该源文件中的函数
- 执行顺序:导入包中所有源文件的init函数(不同文件中的init函数执行顺序和go文件名有关),全局变量,主函数init函数、main函数(函数执行按顺序)。
从代码运行角度看:
程序从main函数开始执行,在导入包的时候,会加载包中所有的源文件(但不执行其中的函数)(但和main.go一样,只是不执行main函数,会先执行全局变量和init函数),当加载完成,则会继续执行main.go中语句,依次往下,在main函数中,包内函数被调用时执行。
匿名函数
- Go支持匿名函数,匿名函数只会执行一次
- 匿名函数使用方式(mian函数内部定义):
- 定义匿名函数时就直接调用,这种方式匿名函数只能调用一次(常用)
package main
import "fmt"
func main() {
//变量a直接收匿名函数传回的返回值
//定义的同时就调用
a := func (num1 int, num2 int) int { //此处a为匿名函数返回的值
return num1+num2
}(1,2) //1,2为匿名函数传入的形参
fmt.Println(a)
}
- 将匿名函数赋给一个变量(该变量就是函数变量了),再通过该变量来调用匿名函数。这种方式实际上是给了匿名函数一个函数名,通过该函数名也可以多次调用。 重复调用可以直接定义成普通函数
package main
import "fmt"
func main() {
//将匿名赋给变量,使用函数变量进行调用
a := func (num1 int, num2 int) int { //此处a为匿名函数类型
return num1+num2
} //此处不传入形参
fmt.Printf("%T\n",a)
fmt.Println(a(1,2)) //调用匿名函数
fmt.Println(a(2,2)) //调用匿名函数
}
func(int, int) int
3
4
- 以上的匿名函数使用都是在main函数内部进行调用,如何让一个匿名函数,可以在整个程序中有效呢?将匿名函数给一个全局变量就可以了。 其实还是上文中第二部分多次使用的形式
package main
import "fmt"
//定义函数类型的全局变量,以便整个程序搜能使用(包括其他包)
var A = func () { //注意事项::=不能第一全局变量
fmt.Println("匿名函数被调用")
}
func test() {
fmt.Println("调用test函数")
A()
}
func main() {
test()
A()
}
调用test函数
匿名函数被调用
匿名函数被调用
PS::=
定义变脸的形式只能使用在函数内部,不能用这种方式定义全局变量!!!!全局变量只能使用var
定义
闭包
- 闭包就是一个函数和与其相关的引用环境组合的一个整体
package main
import "fmt"
//getSum函数,传入参数为空,返回值为一个函数
func getSum() func (int) int{ //此处返回变量类型
var n int = 10
return func (num int) int{ //传入参数为int类型,返回为int类型 //只能定义为匿名函数
n = n + num //此处是可以使用外部的参数的 //可以将n理解为这个函数的全局便利那个
return n
}
}
func main() {
test := getSum() //调用了getSum函数,则test为一个函数类型便利那个
fmt.Println(test(1))
fmt.Println(test(2))
fmt.Println(test(3))
}
11
13
16
可以发现,函数会存储值,所以可以将需要存储(引用)的值放入外层函数里面,外面的参数可以理解为返回函数的全局变量
- 闭包的本质:
闭包本质一个匿名函数,只是这个函数引入外界的变量/参数
匿名函数+引用的变量/参数=闭包==(引用的变量/参数外层)== - 特点:
(1)返回的是一个匿名函数,但是这个匿名函数引用到函数外的变量/参数,因此这个匿名函数就和变量/参数形成一个整体,构成闭包
(2)闭包中使用的变量/参数会一直保存在内存中,所以会一直使用。闭包不可滥用 - 如果不使用闭包,也可以使用全局变量进行存储值,可以在函数内部改变全局变量
package main
import "fmt"
//定义全局便利那个
var sum int = 0 //全局变量存储和
//定义累加函数
func getSum(num int) int{
sum += num //改变全局变量的值
return sum
}
func main() {
test := getSum //重命名函数
fmt.Println(test(1))
fmt.Println(test(2))
fmt.Println(test(3))
}
1
3
6
defer
关键字
在函数中,程序员经常需要创建资源,为了在函数执行完毕后,及时的释放资源,Go的设计者提供defer
关键字
package main
import "fmt"
func main() {
defer fmt.Println("defer1")
defer fmt.Println("defer2")
fmt.Println("1")
fmt.Println("2")
}
1
2
defer2
defer1
从上面的运行结果我们可以观察到,defer
的执行类似于栈的操作:遇到defer不执行,入栈,当函数中其他语句执行完成,则开始出栈。
所以当需要关闭某个资源的时候,可以不用思考在何处关闭,或者在程序结束前忘记关闭,defer会在程序结束前执行