包的使用

Go语言使用包(package)这种语法元素来组织源码,所有语法可见性均定义在package这个级别,与Java 、python等语言相比,这算不上什么创新,但与C传统的include相比,则是显得“先进”了许多。

Go 语言的源码复用建立在包(package)基础之上。包通过 package, import, GOPATH 操作完成。

main包

Go 语言的入口 main() 函数所在的包(package)叫 main,main 包想要引用别的代码,需要import导入!

package

src 目录是以代码包的形式组织并保存 Go 源码文件的。每个代码包都和 src 目录下的文件夹一一对应。每个子目录都是一个代码包。

代码包包名和文件目录名,不要求一致。比如文件目录叫 hello,但是代码包包名可以声明为 “main”,但是同一个目录下的源码文件第一行声明的所属包,必须一致!

同一个目录下的所有.go文件的第一行添加 包定义,以标记该文件归属的包,演示语法:

1
package 包名

包需要满足:

  • 一个目录下的同级文件归属一个包。也就是说,在同一个包下面的所有文件的package名,都是一样的。
  • 在同一个包下面的文件package名都建议设为是该目录名,但也可以不是。也就是说,包名可以与其目录不同名。
  • 包名为 main 的包为应用程序的入口包,其他包不能使用。

在同一个包下面的文件属于同一个工程文件,不用import包,可以直接使用

包可以嵌套定义,对应的就是嵌套目录,但包名应该与所在的目录一致。

包中,通过标识符首字母是否大写,来确定是否可以被导出。首字母大写才可以被导出,视为 public 公共的资源。

import

要引用其他包,可以使用 import 关键字,可以单个导入或者批量导入,语法演示:

A:通常导入

1
2
3
4
5
6
7
// 单个导入
import "package"
// 批量导入
import (
"package1"
"package2"
)

B:点操作
我们有时候会看到如下的方式导入包

1
2
3
import(
. "fmt"
)

这个点操作的含义就是这个包导入之后在你调用这个包的函数时,你可以省略前缀的包名。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
. "fmt"
)

type S struct{}

func (s S) p(n int) S {
Print(n)
return s
}
func main() {
var s S
defer s.p(1).p(2)
Print(3)
}

C:起别名

别名操作顾名思义我们可以把包命名成另一个我们用起来容易记忆的名字。导入时,可以为包定义别名,语法演示:

1
2
3
4
5
6
import (
p1 "package1"
p2 "package2"
)
// 使用时:别名操作,调用包函数时前缀变成了我们的前缀
p1.Method()

D:_操作
如果仅仅需要导入包时执行初始化操作,并不需要使用包内的其他函数,常量等资源。则可以在导入包时,匿名导入。

这个操作经常是让很多人费解的一个操作符,请看下面这个import:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"database/sql"
"fmt"

_ "github.com/go-sql-driver/mysql"
)

func main() {
db, err := sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/flask_meeting?charset=utf8mb4")
if err != nil {
fmt.Println("错误信息", err)
return
}
fmt.Println("连接成功", db)
}

_操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数。也就是说,使用下划线作为包的别名,会仅仅执行init()。

导入的包的路径名,可以是相对路径也可以是绝对路径,推荐使用绝对路径(起始于工程根目录)。

GOPATH环境变量

import导入时,会从GO的安装目录(也就是GOROOT环境变量设置的目录)和GOPATH环境变量设置的目录中,检索 src/package 来导入包。如果不存在,则导入失败。
GOROOT,就是GO内置的包所在的位置。
GOPATH,就是我们自己定义的包的位置。

通常我们在开发Go项目时,调试或者编译构建时,需要设置GOPATH指向我们的项目目录,目录中的src目录中的包就可以被导入了。

init() 包初始化

下面我们详细的来介绍一下这两个函数:init()、main() 是 go 语言中的保留函数。我们可以在源码中,定义 init() 函数。此函数会在包被导入时执行,例如如果是在 main 中导入包,包中存在 init(),那么 init() 中的代码会在 main() 函数执行前执行,用于初始化包所需要的特定资料。例如:
包源码:

1
2
3
4
5
6
7
//src/userPackage/tool.go

package userPackage
import "fmt"
func init() {
fmt.Println("tool init")
}

主函数源码:

1
2
3
4
5
6
7
8
9
10
11
//src/main.go

package main
import (
"userPackage"
)
func main() {
fmt.Println("main run")
// 使用userPackage
userPackage.SomeFunc()
}

执行时,会先输出 “tool init”,再输出 “main run”。

下面我们详细的来介绍一下init()、main() 这两个函数。在 go 语言中的区别如下:
相同点:

两个函数在定义时不能有任何的参数和返回值。
该函数只能由 go 程序自动调用,不可以被引用。

不同点:

init 可以应用于任意包中,且可以重复定义多个。
main 函数只能用于 main 包中,且只能定义一个。

两个函数的执行顺序:

在 main 包中的 go 文件默认总是会被执行。

  • 对同一个 go 文件的 init( ) 调用顺序是从上到下的。

  • 对同一个 package 中的不同文件,将文件名按字符串进行“从小到大”排序,之后顺序调用各文件中的init()函数。

  • 对于不同的 package,如果不相互依赖的话,按照 main 包中 import 的顺序调用其包中的 init() 函数。

  • 如果 package 存在依赖,调用顺序为最后被依赖的最先被初始化,例如:导入顺序 main –> A –> B –> C,则初始化顺序为 C –> B –> A –> main,一次执行对应的 init 方法。main 包总是被最后一个初始化,因为它总是依赖别的包

避免出现循环 import,例如:A –> B –> C –> A。

一个包被其它多个包 import,但只能被初始化一次

管理外部包

go允许import不同代码库的代码。对于import要导入的外部的包,可以使用 go get 命令取下来放到GOPATH对应的目录中去。

举个例子,比如说我们想通过go语言连接mysql数据库,那么需要先下载mysql的数据包,打开终端并输入以下命令:

1
$ go get github.com/go-sql-driver/mysql

也就是说,对于go语言来讲,其实并不关心你的代码是内部还是外部的,总之都在GOPATH里,任何import包的路径都是从GOPATH开始的;唯一的区别,就是内部依赖的包是开发者自己写的,外部依赖的包是go get下来的。

最后

我们可以通过go install 来编译包文件。

我们知道一个非main包在编译后会生成一个.a文件(在临时目录下生成,除非使用go install安装到$GOROOT或$GOPATH下,否则你看不到.a),用于后续可执行程序链接使用。

比如Go标准库中的包对应的源码部分路径在:$GOROOT/src,而标准库中包编译后的.a文件路径在$GOROOT/pkg/darwin_amd64下。

  • pkg 目录是用来存放通过 go install 命令安装后的代码包的归档文件(.a 文件)。归档文件的名字就是代码包的名字。所有归档文件都会被存放到该目录下的平台相关目录中,即在 $GOPATH/pkg/$GOOS_$GOARCH 中,同样以代码包为组织形式。

time包

time包:
1年=365天,day
1天=24小时,hour
1小时=60分钟,minute
1分钟=60秒,second
1秒钟=1000毫秒,millisecond
1毫秒=1000微秒,microsecond–>μs
1微秒=1000纳秒,nanosecond–>ns
1纳秒=1000皮秒,picosecond–>ps

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
package main

import (
"fmt"
"math/rand"
"time"
)

func main() {

//1.获取当前的时间
t1 := time.Now()
fmt.Printf("%T\n", t1) //time.Time
fmt.Println(t1) //2023-06-10 11:16:15.340273 +0800 CST m=+0.003384001

//2.获取指定的时间
t2 := time.Date(2008, 7, 15, 16, 30, 28, 0, time.Local)
fmt.Println(t2) //2008-07-15 16:30:28 +0800 CST

//3.time-->string之间的转换
/*
t1.Format("格式模板")-->string
模板的日期必须是固定:06-1-2-3-4-5
*/
s1 := t1.Format("2006-1-2 15:04:05")
fmt.Println(s1) //2023-6-10 11:16:15

s2 := t1.Format("2006/01/02")
fmt.Println(s2) //2023/06/10

//string-->time
/*
time.Parse("模板",str)-->time,err
*/
s3 := "1999年10月10日" //string
t3, err := time.Parse("2006年01月02日", s3)
if err != nil {
fmt.Println("err:", err)
}
fmt.Println(t3)
fmt.Printf("%T\n", t3)

fmt.Println(t1.String())

//4.根据当前时间,获取指定的内容
year, month, day := t1.Date() //年,月,日
fmt.Println(year, month, day) //2023 June 10

hour, min, sec := t1.Clock()
fmt.Println(hour, min, sec) //时,分,秒

year2 := t1.Year()
fmt.Println("年:", year2)
fmt.Println(t1.YearDay())

month2 := t1.Month()
fmt.Println("月:", month2)
fmt.Println("日:", t1.Day())
fmt.Println("时:", t1.Hour())
fmt.Println("分钟:", t1.Minute())
fmt.Println("秒:", t1.Second())
fmt.Println("纳秒:", t1.Nanosecond())

fmt.Println(t1.Weekday()) //Wednesday

//5.时间戳:指定的日期,距离1970年1月1日0点0时0分0秒的时间差值:秒,纳秒

t4 := time.Date(1970, 1, 1, 1, 0, 0, 0, time.UTC)
timeStamp1 := t4.Unix() //秒的差值
fmt.Println(timeStamp1)
timeStamp2 := t1.Unix()
fmt.Println(timeStamp2)

timeStamp3 := t4.UnixNano()
fmt.Println(timeStamp3) //3600 000 000 000
timeStamp4 := t1.UnixNano()
fmt.Println(timeStamp4)

//6.时间间隔
t5 := t1.Add(time.Minute)
fmt.Println(t1)
fmt.Println(t5)
fmt.Println(t1.Add(24 * time.Hour))

t6 := t1.AddDate(1, 0, 0)
fmt.Println(t6)

d1 := t5.Sub(t1)
fmt.Println(d1)

//7.睡眠
time.Sleep(3 * time.Second) //让当前的程序进入睡眠状态
fmt.Println("main。。。over。。。。。")

//睡眠[1-10]的随机秒数
rand.Seed(time.Now().UnixNano())
randNum := rand.Intn(10) + 1 //int
fmt.Println(randNum)
time.Sleep(time.Duration(randNum) * time.Second)
fmt.Println("睡醒了。。")
}

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
time.Time
2023-06-10 11:24:17.1424817 +0800 CST m=+0.003008901
2008-07-15 16:30:28 +0800 CST
2023-6-10 11:24:17
2023/06/10
1999-10-10 00:00:00 +0000 UTC
time.Time
2023-06-10 11:24:17.1424817 +0800 CST m=+0.003008901
2023 June 10
11 24 17
年: 2023
161
月: June
日: 10
时: 11
分钟: 24
秒: 17
纳秒: 142481700
Saturday
3600
1686367457
3600000000000
1686367457142481700
2023-06-10 11:24:17.1424817 +0800 CST m=+0.003008901
2023-06-10 11:25:17.1424817 +0800 CST m=+60.003008901
2023-06-11 11:24:17.1424817 +0800 CST m=+86400.003008901
2024-06-10 11:24:17.1424817 +0800 CST
1m0s
main。。。over。。。。。
7
睡醒了。。

json

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
"encoding/json"
"fmt"
)

type userInfo struct {
Name string
Age int `json:"age"`
Hobby []string
}

func main() {
a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
buf, err := json.Marshal(a)
if err != nil {
panic(err)
}
fmt.Println(buf) // [123 34 78 97...]
fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}

buf, err = json.MarshalIndent(a, "", "\t")
if err != nil {
panic(err)
}
fmt.Println(string(buf))

var b userInfo
err = json.Unmarshal(buf, &b)
if err != nil {
panic(err)
}
fmt.Printf("%v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
[123 34 78 97 109 101 34 58 34 119 97 110 103 34 44 34 97 103 101 34 58 49 56 44 34 72 111 98 98 121 34 58 91 34 71 111 108 97 110 103 34 44 34 84 121 112 101 83 99 114 105 112 116 34 93 125]
{"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
{
"Name": "wang",
"age": 18,
"Hobby": [
"Golang",
"TypeScript"
]
}
main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}

解释:

代码定义了一个 userInfo 结构体,该结构体具有三个字段:Name、Age 和 Hobby,分别表示用户名、年龄和兴趣爱好。Age 字段使用 json:"age" 的标签,指示在 JSON 序列化和反序列化过程中,将其映射到名为 age 的字段
在 main 函数中,首先创建了一个 userInfo 结构体实例 a,赋予了相应的字段值。然后使用 json.Marshal 将结构体实例 a 序列化为 JSON 字符串,得到一个字节数组 buf。fmt.Println(buf) 打印了字节数组的内容,由于是字节的 ASCII 表示,所以会打印一系列数字。接着使用 fmt.Println(string(buf)) 将字节数组转换为字符串并打印,得到 JSON 格式的字符串表示。接下来,使用 json.MarshalIndent 进行格式化序列化,生成带缩进的 JSON 字符串,并将其打印出来。然后,定义了一个空的 userInfo 结构体变量 b,使用 json.Unmarshal 将 JSON 字符串 buf 反序列化为结构体实例 b。反序列化的结果存储在 b 中。%#v 用于打印变量 b 的详细信息。它会打印出结构体类型的名称以及字段的名称和对应的值。这种格式化输出对于调试和理解变量的结构非常有用。

env

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"fmt"
"os"
"os/exec"
)

func main() {

fmt.Println(os.Args)
fmt.Println(os.Getenv("PATH"))
fmt.Println(os.Setenv("AA", "BB"))

buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
if err != nil {
panic(err)
}
fmt.Println(string(buf))
}

解释:

在 main 函数中,首先使用 fmt.Println(os.Args) 打印出程序接收到的命令行参数。os.Args 是一个字符串切片,包含了程序运行时传递的命令行参数。
接下来,使用 os.Getenv(“PATH”) 获取名为 “PATH” 的环境变量的值,并使用 fmt.Println 打印出来。os.Getenv 函数用于获取环境变量的值。
然后,使用 os.Setenv(“AA”, “BB”) 将名为 “AA” 的环境变量的值设置为 “BB”。os.Setenv 函数用于设置环境变量的值。最后,使用 exec.Command 函数创建一个命令对象,并指定要执行的命令为 “grep”,参数为 “127.0.0.1” 和 “/etc/hosts”。然后,使用 CombinedOutput 方法执行命令并捕获命令的输出结果(包括标准输出和标准错误)。捕获的输出结果存储在 buf 变量中。如果执行命令过程中发生错误,代码会使用 panic 函数抛出异常。最后,使用 fmt.Println(string(buf)) 打印出命令的输出结果。