1 什么是包,为什么使用包?

到目前为止,我们看到的 Go 程序都只有一个文件,文件里包含一个 main 函数和几个其他的函数。在实际中,这种把所有源代码编写在一个文件的方法并不好用。以这种方式编写,代码的重用和维护都会很困难。而包(Package)解决了这样的问题。

包用于组织 Go 源代码(解耦合),提供了更好的可重用性与可读性。由于包提供了代码的封装,因此使得 Go 应用程序易于维护。

例如,假如我们正在开发一个 Go 图像处理程序,它提供了图像的裁剪、锐化、模糊和彩色增强等功能。一种组织程序的方式就是根据不同的特性,把代码放到不同的包中。比如裁剪可以是一个单独的包,而锐化是另一个包。这种方式的优点是,由于彩色增强可能需要一些锐化的功能,因此彩色增强的代码只需要简单地导入(我们会在随后讨论)锐化功能的包,就可以使用锐化的功能了。这样的方式使得代码易于重用。

我们会逐步构建一个计算矩形的面积和对角线的应用程序。

通过这个程序,我们会更好地理解包。

 

2 main 函数和 main 包

所有可执行的 Go 程序都必须包含一个 main 函数。这个函数是程序运行的入口。main 函数应该放置于 main 包中。

package packagename 这行代码指定了某一源文件属于一个包。它应该放在每一个源文件的第一行。

下面开始为我们的程序创建一个 main 函数和 main 包。在 Go 工作区内的 src 文件夹中创建一个文件夹,命名为 geometry。在 geometry 文件夹中创建一个 geometry.go 文件。

// geometry.go
package main 

import "fmt"

func main() {  
    fmt.Println("Geometrical shape properties")
}

package main 这一行指定该文件属于 main 包。import "packagename" 语句用于导入一个已存在的包。在这里我们导入了 fmt 包,包内含有 Println 方法。接下来是 main 函数,它会打印 Geometrical shape properties

键入 go install geometry,编译上述程序。该命令会在 geometry 文件夹内搜索拥有 main 函数的文件。在这里,它找到了 geometry.go。接下来,它编译并产生一个名为 geometry (在 windows 下是 geometry.exe)的二进制文件,该二进制文件放置于工作区的 bin 文件夹。现在,工作区的目录结构会是这样:

src
    geometry
        gemometry.go
bin
    geometry

键入 workspacepath/bin/geometry,运行该程序。请用你自己的 Go 工作区来替换 workspacepath。这个命令会执行 bin 文件夹里的 geometry 二进制文件。你应该会输出 Geometrical shape properties

 

3 创建并导入自定义的包

go语言的代码必须放在gopath的src路径下,包括下载的第三方包都要放在gopath的src路径下,包的导入是从gopath的src路径下开始检索。

属于某一个包的源文件都应该放置于一个单独命名的文件夹里除了main包以外,建议包名就叫文件夹名,一个文件夹下的包名必须一致。

同一个包下,变量,函数只能定义一次,同一个包下的变量和函数可以直接使用,即使在不同文件内,但属于同一个包,可以看成在同一个文件里(解耦合),包内的函数或变量,想让外部包使用,必须首字母大写。

在项目根目录下,再创建一个s1.go文件,声明为main包,并定义main函数

此时,mypackage包内的s1.go 和 s2.go 文件中定义的函数和变量应该以大写字母开头(推荐驼峰体)

//s1.go
package mypackage

import "fmt"

//大写开头表示导出,可以给其他包使用
func Test() {
    fmt.Println("我是mypackage包内的test函数")
    fmt.Println(Name)
    Name = "egon"
}
//s2.go
package mypackage

import "fmt"

var Name = "zell"

func Test1() {
    fmt.Println("我是mypackage包内的test1函数")
    fmt.Println(Name)
}

上例输出如下:因为是全局变量,s1.go中修改了变量,main函数最后打印的Name为egon

我是mypackage包内的test函数
zell
egon

 

4 init 函数

所有包都可以包含一个 init 函数,即初始化函数,不需要调用就会执行(在导包的时候就会自动执行)。init 函数不应该有任何返回值类型和参数,在我们的代码中也不能显式地调用它。init 函数的形式如下:

func init() {  
}

init 函数可用于执行初始化任务,也可用于在开始执行之前验证程序的正确性。

包的初始化顺序如下:

  1. 首先初始化包级别(Package Level)的变量
  2. 紧接着调用 init 函数。包可以有多个 init 函数(在一个文件或分布于多个文件中),它们按照编译器解析它们的顺序进行调用。

如果一个包导入了另一个包,会先初始化被导入的包。

尽管一个包可能会被导入多次,但是它只会被初始化一次。

在mypackage包的s1.go文件中定义多个init函数:

package mypackage

import "fmt"

func init() {
    fmt.Println("我是init")
}
func init() {
    fmt.Println("我是init666")
}
func init() {
    fmt.Println("我是init888")
}

func Test() {
    fmt.Println("我是mypackage包内的test函数")
    fmt.Println(Name)
    Name = "egon"
}

在main包的s1.go文件中定义一个init函数:

package main

import (
    "fmt"
    "second_go/mypackage"
)

func init() {
    fmt.Println("我是main包的init函数")
}

func main() {
    mypackage.Test()
    fmt.Println(mypackage.Name)
}

执行main函数,输出结果如下:

我是init
我是init666                
我是init888                
我是main包的init函数       
我是mypackage包内的test函数
zell                       
egon   

init函数的作用?

用来初始化包内的一些东西,比如这个包是数据库连接的包,一导入,建数据库连接池,每次从包里拿一个conn,就从连接池拿出一个连接;
main包下的init函数也会直接执行,也是用来做初始化的操作。

 

5 包导入的几种方式

直接导入

import (
    "fmt"
    "second_go/mypackage"
)

===> 等价于

import "fmt"
import "second_go/mypackage"

给包重命名,即不以文件夹的名字作为包名

如果mypackage文件夹下的s1.go文件,在首行定义包名时,重命名包名为 package test

在main包中导入test包
import test "second_go/mypackage"

在main函数中使用test包的变量或函数
func main() {
    test.Test()
    fmt.Println(test.Name)
}

空白符,包只导入,不使用。

导入了包,却不在代码中使用它,这在 Go 中是非法的。但有时候我们导入一个包,只是为了确保它进行了初始化,而无需使用包中的任何函数或变量。

package main 

import (
    _ "second_go/mypackage"  //只导入包,执行它的init函数,进行初始化操作
)
func main() {
    //这里如果使用mypackage包的函数或变量会报错
}

 

posted @ 2022-11-13 23:37  不会钓鱼的猫  阅读(269)  评论(0编辑  收藏  举报