Q7nl1s admin

什么是包,为什么要使用它们?

到目前为止,我们看到的 Go 程序只有一个文件,其中包含一个 main函数和几个其他函数。在现实世界的场景中,这种将所有源代码写入单个文件的方法是不可扩展的。重用和维护以这种方式编写的代码变得不可能。这是包有用的地方。

包用于组织 Go 源代码以获得更好的可重用性和可读性。包是位于同一目录中的 Go 源文件的集合。包提供了代码划分,因此很容易维护 Go 项目。

例如,假设我们正在用 Go 编写一个金融应用程序,其中一些功能是单利计算、复利计算和贷款计算。组织此应用程序的一种简单方法是按功能。我们可以创建包simpleinterestcompoundinterestloan。如果loan包需要计算单利,只需导入simpleinterest包即可。这样代码就被重用了。

我们将通过创建一个简单的应用程序来学习包,以确定给定本金、利率和持续时间(以年为单位)的单利。

main函数和main包

每个可执行的 Go 应用程序都必须包含 main 函数。该函数是执行的入口点。主要功能应该驻留在 main 包中。

package packagename指定特定源文件属于 package packagename。这应该是每个 go 源文件的第一行。

让我们开始为我们的应用程序创建主函数和 main 包。

运行以下命令以在预定的目录中创建一个名为learnpackage的目录。

1
mkdir E:\backend\Go_backend\learnpackage

在我们的learnpackage目录中创建一个名为main.go的文件,其中包含以下内容。

1
2
3
4
5
6
7
package main 

import "fmt"

func main() {
fmt.Println("Simple interest calculation")
}

该行代码package main指定该文件属于 main 包。import "packagename"语句用于导入现有包。packagename.FunctionName()是调用包中函数的语法。

在第3行,我们导入fmt包以使用Println功能。fmt是一个标准包,作为 Go 标准库的一部分内置。主要功能是打印Simple interest calculation

learnpackage

通过使用移动到目录来 编译上述程序

1
cd E:\backend\Go_backend\learnpackage\

并输入以下命令

1
go build

如果一切顺利,我们的二进制文件将被编译并准备好执行。在终端中键入命令learnpackage,您将看到以下输出。

1
Simple interest calculation  

Go Module

我们将以这样一种方式构建代码,即所有与简单兴趣相关的功能都在simpleinterest包中。为此,我们需要创建一个自定义包simpleinterest,其中包含计算单利的函数。在创建自定义包之前,我们需要先了解Go Modules,因为创建自定义包需要Go Modules

Go Module 只不过是 Go 包的集合。现在你可能会想到这个问题。为什么我们需要 Go 模块来创建自定义包?答案是我们创建的自定义包的导入路径来源于 go 模块的名称。除此之外,我们的应用程序使用的所有其他第三方包(例如来自 github 的源代码)将与版本信息一起出现在go.mod文件中。该go.mod文件是在我们创建新模块时创建的。您将在下一节中更好地理解这一点。

另一个问题可能会出现在我们的脑海中。为什么我们到现在还没有创建Go 模块就成功了?答案是:到目前为止,在本教程系列中,我们从未创建过自己的自定义包,因此不需要 Go 模块。

有了足够的理论基础:)。让我们开始行动并创建我们的 go 模块和自定义包。

创建一个Go module

learnpackage通过键入确保您在目录中cd E:\backend\Go_backend\learnpackage\。在这个目录中运行以下命令来创建一个名为 learnpackage 的 go 模块。

1
go mod init learnpackage  

上面的命令将创建一个名为go.mod. 以下将是该文件的内容。

1
2
3
module learnpackage

go 1.19

该行module learnpackage指定模块的名称是learnpackage。正如我们之前提到的,learnpackage将是导入此模块内创建的任何包的基本路径。该行go 1.19指定此模块中的文件使用 go version 1.19

创建简单的自定义interest package

属于一个包的源文件应该放在它们自己的单独文件夹中。Go 中的约定是将此文件夹命名为与包相同的名称。

让我们在文件夹learnpackage内创建一个名为simpleinterest的文件夹。 mkdir simpleinterest将为我们创建此文件夹。

simpleinterest 文件夹中的所有文件都应以package simpleinterest开头,因为它们都属于simpleinterest包。

simpleinterest 文件夹中创建一个simpleinterest.go文件。

下面将是我们应用程序的目录结构。

1
2
3
4
5
├── learnpackage
│ ├── go.mod
│ ├── main.go
│ └── simpleinterest
│ └── simpleinterest.go

将以下代码添加到simpleinterest.go文件中。

1
2
3
4
5
6
7
package simpleinterest

//Calculate calculates and returns the simple interest for a principal p, rate of interest r for time duration t years
func Calculate(p float64, r float64, t float64) float64 {
interest := p * (r / 100) * t
return interest
}

在上面的代码中,我们创建了一个Calculate计算并返回单利的函数。这个函数是不言自明的。它计算并返回单利。

请注意,函数名称Calculate以大写字母开头。这是必不可少的,我们将很快解释为什么需要这样做。

导入自定义包

要使用自定义包,我们必须先导入它。导入路径是模块名加上包的子目录和包名。在我们的例子中,模块名称是learnpackage,包 simpleinterestlearnpackage的直接子文件夹simpleinterest

1
2
├── learnpackage
│ └── simpleinterest

所以该行import "learnpackage/simpleinterest"将导入simpleinterest包。

如果我们有这样的目录结构

1
2
3
learnpackage  
│ └── finance
│ └── simpleinterest

那么导入语句将是import "learnpackage/finance/simpleinterest"

将以下代码添加到main.go

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

import (
"fmt"
"learnpackage/simpleinterest"
)

func main() {
fmt.Println("Simple interest calculation")
p := 5000.0
r := 10.0
t := 1.0
si := simpleinterest.Calculate(p, r, t)
fmt.Println("Simple interest is", si)
}

上面的代码导入simpleinterest包并使用Calculate函数找到简单的兴趣。标准库中的包不需要模块名称前缀,因此“fmt”无需模块前缀即可工作。当应用程序运行时,输出将是

1
2
Simple interest calculation  
Simple interest is 500

更多关于 go install

现在我们了解了包的工作原理,是时候多谈谈go install. Go 工具,例如go install就在当前目录的上下文中工作。让我们了解这意味着什么。到目前为止,我们一直在从目录E:\backend\Go_backend\learnpackage\运行go install。如果我们尝试从任何其他目录运行它,它将失败。

尝试进入cd E:\backend\Go_backend\然后运行go install learnpackage. 它将失败并出现以下错误。

1
go: go.mod file not found in current directory or any parent directory; see 'go help modules'

让我们了解此错误背后的原因。go install将可选的包名作为参数(在我们的例子中包名是learnpackage),如果包存在于运行它的当前目录或父目录或父目录等中,它会尝试编译 main 函数.

Go_backend目录中,那里没有go.mod文件,因此go install抱怨它找不到包learnpackage

当我们移动到E:\backend\Go_backend\learnpackage\时,那里有一个go.mod文件,我们的模块名称learnpackage在该go.mod文件中。

所以go install learnpackage将从E:\backend\Go_backend\learnpackage\目录内部工作。

但是到目前为止我们只是在使用go install并且没有指定包名。如果未指定包名,go install则默认为当前工作目录中的模块名。这就是为什么当go install在没有任何包名的情况下能够运行的原因。因此,以下 3 个命令在运行时是等效的。

1
2
3
4
5
go install

go install .

go install learnpackage

我还提到它go install能够递归地在父目录中搜索 go.mod 文件。让我们检查一下这是否有效。

1
cd ~/Documents/learnpackage/simpleinterest/  

上面的命令会将我们带到simpleinterest目录。从那个目录运行

1
go install learnpackage  

Go install 将成功地在定义了learnpackage模块的父目录中找到一个go.mod file,因此它可以工作:)。

Exported Names

Calculate我们将 Simple Interest 包中的函数大写。这在 Go 中有特殊的含义。任何以大写字母开头的变量或函数都是 go 中的导出名称(Exported Names)。只能从其他包访问导出的函数和变量。在我们的例子中,我们想Calculate从 main 包中访问函数。因此这是大写的。

如果在simpleinterest.go中将函数名由 Calculate 改为 calculate,如果我们尝试在 main.go 中调用该函数simpleinterest.calculate(p, r, t),编译器会报错

1
2
3
# learnpackage
./main.go:13:8: cannot refer to unexported name simpleinterest.calculate
./main.go:13:8: undefined: simpleinterest.calculate

因此,如果您想访问包外的函数,则应将其大写。

init function

Go 中的每个包都可以包含一个init函数。该init函数不能有任何返回类型,也不能有任何参数。init 函数不能在我们的源代码中显式调用。包初始化时会自动调用。init 函数具有以下语法:

1
2
func init() {  
}

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

一个包的初始化顺序如下:

  1. 包级变量首先被初始化
  2. 接下来调用 init 函数。一个包可以有多个 init 函数(在单个文件中或分布在多个文件中),它们按照它们呈现给编译器的顺序被调用。

如果一个包导入其他包,则首先初始化导入的包。

一个包即使是从多个包导入的,也只会被初始化一次。

让我们对我们的应用程序进行一些修改以了解init功能。

首先,让我们将init函数添加到simpleinterest.go文件中。

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

import "fmt"

/*
* init function added
*/
func init() {
fmt.Println("Simple interest package initialized")
}
//Calculate calculates and returns the simple interest for principal p, rate of interest r for time duration t years
func Calculate(p float64, r float64, t float64) float64 {
interest := p * (r / 100) * t
return interest
}

我们添加了一个简单的 init 函数,它只打印Simple interest package initialised

现在让我们修改 main 包。我们知道在计算单利时,本金、利率和期限应该大于零。我们将使用main.go文件中的 init 函数和包级变量来定义此检查。

修改main.go如下,

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
package main 

import (
"fmt"
"learnpackage/simpleinterest" //importing custom package
"log"
)
var p, r, t = 5000.0, 10.0, 1.0

/*
* init function to check if p, r and t are greater than zero
*/
func init() {
println("Main package initialized")
if p < 0 {
log.Fatal("Principal is less than zero")
}
if r < 0 {
log.Fatal("Rate of interest is less than zero")
}
if t < 0 {
log.Fatal("Duration is less than zero")
}
}

func main() {
fmt.Println("Simple interest calculation")
si := simpleinterest.Calculate(p, r, t)
fmt.Println("Simple interest is", si)
}

以下是对main.go更改的描述

  1. prt 变量从主函数级别移动到包级别。
  2. 添加了一个初始化函数。如果使用log.Fatal函数的本金、利率或持续时间小于零,init 函数会打印日志并终止程序执行

的初始化顺序如下,

  1. 导入的包首先被初始化。因此simpleinterest包首先被初始化并且它的 init 方法被调用。
  2. 包级变量p、r和t接下来被初始化。
  3. init函数在 main 中调用。
  4. 最后调用main函数。

如果您运行该程序,您将获得以下输出。

1
2
3
4
Simple interest package initialized  
Main package initialized
Simple interest calculation
Simple interest is 500

正如预期的那样,simpleinterest首先调用包的 init 函数,然后是包级变量的初始化pr然后t。接下来调用 main 包的 init 函数。它检查和是否小于零,如果条件为真则终止p。现在你可以假设它会检查是否小于 0,如果是,程序将被终止。我们为和写了一个类似的条件。在这种情况下,所有这些条件都为假,程序继续执行。最后调用main函数。

让我们稍微修改一下这个程序来学习init函数的使用。

main.go中改变下行代码

1
var p, r, t = 5000.0, 10.0, 1.0  

1
var p, r, t = -5000.0, 10.0, 1.0  

我们已经初始化p为负数。

现在,如果您运行应用程序,您将看到

1
2
3
Simple interest package initialized  
Main package initialized
2020/02/15 21:25:12 Principal is less than zero

p为负数。因此,当 init 函数运行时,程序在打印后终止Principal is less than zero

使用空白标识符

在 Go 中导入一个包并且不在代码中的任何地方使用它是非法的。如果您这样做,编译器会抱怨。这样做的原因是为了避免未使用的包膨胀(bloating),这将显着增加编译时间。将代码替换为main.go以下内容,

1
2
3
4
5
6
7
8
9
package main

import (
"learnpackage/simpleinterest"
)

func main() {

}

上面的程序会报错

1
2
# learnpackage
./main.go:4:2: imported and not used: "learnpackage/simpleinterest"

但是,当应用程序正在积极开发时导入包并在以后(如果不是现在)在代码中的某个地方使用它们是很常见的。在这些情况下,_空白标识符可以帮助我们。

上述程序中的错误可以通过以下代码静音,

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"learnpackage/simpleinterest"
)

var _ = simpleinterest.Calculate

func main() {

}

var _ = simpleinterest.Calculate行使错误静音。我们应该跟踪这些类型的错误消音器(error silencers),并在应用程序开发结束时删除它们,包括导入的包,在导入的包没有使用的时候。因此,建议在 import 语句之后的包级别中编写错误消音器。

有时我们需要导入一个包只是为了确保初始化发生,即使我们不需要使用包中的任何函数或变量。例如,我们可能需要确保调用simpleinterest包的init函数,即使我们计划不在代码中的任何地方使用该包。在这种情况下也可以使用 _ 空白标识符,如下所示。

1
2
3
4
5
6
7
8
9
package main

import (
_ "learnpackage/simpleinterest"
)

func main() {

}

运行上述程序将输出Simple interest package initialized. 我们已经成功地初始化了这个simpleinterest包,即使它没有在代码中的任何地方使用。

 Comments
Comment plugin failed to load
Loading comment plugin
Powered by Hexo & Theme Keep
Unique Visitor Page View