Golang学习笔记

什么是包?为何使用包?

到目前为止咱们见到的 Go 程序都只有一个文件,文件中包含了一个main函数和几个其余函数。在实际中这种将全部代码都放在一个文件里的组织方式是不可行的。这样的组织方式使得代码变得没法重用和维护困难。包(package)用于解决这样的问题。git

包用于组织Go源代码,以得到更好的重用性和可读性。包提供了代码封装的机制从而使得Go应用程序易于维护。例如,假设咱们正在开发一个图像处理应用,它提供了诸如图像裁剪,锐化,模糊和增色等功能。一种组织代码的方式是将全部实现同一功能的代码放在一个独立的包中。例如裁剪功能能够放在一个单独包中,而锐化功能能够放在另外一个包中。这种作法的优势是:增色功能可能须要作一些锐化的处理,那么增色代码中能够简单地导入(咱们即将讨论导入)锐化包,使用其中提供的功能便可。这种方式使得代码变得更容易重用。github

咱们将逐步建立一个计算矩形面积和对角线的应用程序。golang

经过构建该程序,咱们将更好的理解包。less

main函数与main包

每一个可执行的Go程序都必须包含一个 main 函数。这个函数是执行程序的入口点。main 函数应该包含在 main 包中。函数

指定一个特定源文件属于一个包的语法为:package packagename,这条语句应该放在源文件的第一行。学习

下面让咱们开始建立 main 函数和 main 包。在 [工做空间目录]/src 目录下新建一个子目录,命名为 geometry在该目录下新建 geometry.gospa

编写 geometry.go 代码以下:.net

//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。找到后编译该文件并在 [工做空间目录]/bin 目录下生成二进制文件geometry(在Windows下是 geometry.exe)。 如今的目录结构以下所示:code

src/
    geometry/
        geometry.go
bin/
    geometry

执行 [工做空间目录]/bin/geometry 运行该程序,其中 [工做空间目录] 须要换成本身的实际目录。这条命令运行在 bin 目录下的 geometry 二进制文件。你应该能够看到以下输出:

Geometrical shape properties

建立自定义包

下面咱们将建立一个 rectangle 包,将与矩形相关的功能(计算矩形的面积和对角线)都放在这个包里。

属于同一个包的源文件应该放在独立的文件夹中,按照Go的惯例,该文件夹的名字应该与包名相同。

所以让咱们在 geometry 目录下建立一个 rectangle 子目录。全部放在该目录下的源文件都应该以 package rectangle 开头,用以表示这些源文件都属于 rectangle 包。

在 rectangle 目录下新建 rectangle.go ,编写以下代码:

//rectprops.go
package rectangle

import "math"

func Area(len, wid float64) float64 {  
    area := len * wid
    return area
}

func Diagonal(len, wid float64) float64 {  
    diagonal := math.Sqrt((len * len) + (wid * wid))
    return diagonal
}

在上面的代码中咱们实现了两个函数 Area 和 Diagonal 分别用于计算矩形的面积和对角线。矩形的面积为长与宽的积。矩形的对角线为长与宽的平方和再开根号。这里调用 math 包中的 Sqrt 函数来计算平方根。

注意上面实现的两个函数的函数名 Area 和 Diagonal 都是以大写字母开头的。这是必要的,咱们将很快解释为何须要这样作。

导入自定义包

为了使用自定义包咱们必须先导入它。用来导入自定义包的语法为:import path。咱们必须指定 path 为相对于 [工做空间目录]/src目录的相对路径。咱们当前的目录结构以下所示:

src/
    geometry/
        geometry.go
        rectangle/
            rectangle.go

语句 import "geometry/rectangle" 表示咱们要导入 rectangle 包。

在 geometry.go 中添加以下代码:

//geometry.go
package main 

import (  
    "fmt"
    "geometry/rectangle" //importing custom package
)

func main() {  
    var rectLen, rectWidth float64 = 6, 7
    fmt.Println("Geometrical shape properties")
        /*Area function of rectangle package used
        */
    fmt.Printf("area of rectangle %.2f\n", rectangle.Area(rectLen, rectWidth))
        /*Diagonal function of rectangle package used
        */
    fmt.Printf("diagonal of the rectangle %.2f ",rectangle.Diagonal(rectLen, rectWidth))
}

上面的代码导入了 rectangle 包而且使用 Area 和 Diagonal 函数计算矩形的面积和对角线。在 Printf 中的 %.2f 格式化指示符表示仅保留浮点数的两位小数。程序输出以下:

Geometrical shape properties  
area of rectangle 42.00  
diagonal of the rectangle 9.22

 

导出名字

咱们将 rectangle 包中的两个函数名称 Area 和 Diagonal 的首字母大写,这在 Go 中有特殊的意义。在 Go 中,任何以大写字母开头的变量名、函数名都是被导出的名字(exported name)。只有被导出的名字才能被其它包访问。在这里咱们须要在 main 包中访问 Area 和 Diagonal 函数,所以将它们的首字母大写。

若是将 rectprops.go 中的 Area(len, wid float64) 改为 area(len, wid float64),而且将 geometry.go 中的 rectangle.Area(rectLen, rectWidth) 改为 rectangle.area(rectLen, rectWidth),那么运行程序时编译器将会报错:geometry.go:11: cannot refer to unexported name rectangle.area。所以,若是想访问包外的函数,必须将其首字母大写。

init 函数

每个包均可以包含一个 init 函数。该函数不该该有任何参数和返回值,而且在咱们的代码中不能显式调用它。init 函数形式以下:

func init() {  
}

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

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

  1. 包级别的变量首先被初始化
  2. 接着 init 函数被调用。一个包能够有多个 init 函数(在一个或多个文件中),它们的调用顺序为编译器解析它们的顺序。

若是一个包导入了另外一个包,被导入的包先初始化。

尽管一个包可能被包含屡次,可是它只被初始化一次。

下面让咱们对咱们的程序作一些修改来理解 init 函数。

首先在 rectprops.go 中添加一个 init 函数:

//rectprops.go
package rectangle

import "math"  
import "fmt"

/*
 * init function added
 */
func init() {  
    fmt.Println("rectangle package initialized")
}
func Area(len, wid float64) float64 {  
    area := len * wid
    return area
}

func Diagonal(len, wid float64) float64 {  
    diagonal := math.Sqrt((len * len) + (wid * wid))
    return diagonal
}

咱们添加了一个简单的 init 函数,它仅打印:rectangle package initialized

如今咱们来修改 main 包。咱们知道矩形的 length 和 width 应该大于 0。咱们将在 geometry.go 中添加 init 函数和包级别的变量来作此检查。

修改 geometry.go 以下:

//geometry.go
package main 

import (  
    "fmt"
    "geometry/rectangle" //importing custom package
    "log"
)
/*
 * 1. package variables
*/
var rectLen, rectWidth float64 = 6, 7 

/*
*2. init function to check if length and width are greater than zero
*/
func init() {  
    println("main package initialized")
    if rectLen < 0 {
        log.Fatal("length is less than zero")
    }
    if rectWidth < 0 {
        log.Fatal("width is less than zero")
    }
}

func main() {  
    fmt.Println("Geometrical shape properties")
    fmt.Printf("area of rectangle %.2f\n", rectangle.Area(rectLen, rectWidth))
    fmt.Printf("diagonal of the rectangle %.2f ",rectangle.Diagonal(rectLen, rectWidth))
}

咱们对 geometry.go 作了以下修改:

  1. rectLen 和 rectWidth 变量从 main 函数中移到了外面,成为了包级别的变量。
  2. 添加 init 函数。当 rectLen 或 rectWidth 小于 0 时,该函数利用 log.Fatal 打印一条日志并终止程序。

main 包的初始化顺序为:

  1. 首先初始化被导入的包。所以 rectangle 包先被初始化。
  2. 而后初始化包级别的变量: rectLen 和 rectWidth 。
  3. init 函数被调用。
  4. 最后 main 函数被调用。

运行该程序,输出以下:

rectangle package initialized  
main package initialized  
Geometrical shape properties  
area of rectangle 42.00  
diagonal of the rectangle 9.22

正如预期的那样,rectangle 包的 init 函数首先被调用,接着是包级别的变量 rectLen 和 rectWidth 被初始化,接着是 main 包的 init 函数被调用,该函数检测 rectLen 和 rectWidth 是否小于 0,若是小于 0,则终止程序。咱们将会在单独的教程里介绍 if语句。如今你能够假定 if rectLen < 0 将会检测 rectLen 是否小于 0,若是是,则终止程序。rectWidth 也是一样的处理。也就是说两个条件都为假程序才继续执行。最后,main 函数被调用。

让咱们再次修改程序来学习 init 函数的使用。

将 geometry.go 中的 var rectLen, rectWidth float64 = 6, 7 这一行改成 var rectLen, rectWidth float64 = -6, 7。这里将 rectLen 改成负值。

如今运行程序,获得以下结果:

rectangle package initialized  
main package initialized  
2017/04/04 00:28:20 length is less than zero

像上面同样, rectangle 包首先被初始化,而后是 main 包中的包级别变量 rectLen 和 rectWidth 初始化。接着调用 main 包的 init 函数,由于 rectLen 是小于 0 的,所以程序打印 length is less than zero 后退出。

代码能够在 github 上下载。

使用空指示符

在 Go 中只导入包却不在代码中使用它是非法的。若是你这么作了,编译器会报错。这样作的缘由是为了不引入过多未使用的包而致使编译时间的显著增长。将 geometry.go 中的代码替换为以下代码:

//geometry.go
package main 

import (   

     "geometry/rectangle" //importing custom package

)
func main() {

}

上面的程序将会报错:geometry.go:6: imported and not used: "geometry/rectangle"

可是在开发过程当中,导入包却不当即使用它是很常见的。能够用空指示符(_)来处理这种状况。

下面的代码能够避免抛出上面的错误:

package main

import (  
    "geometry/rectangle" 
)

var _ = rectangle.Area //error silencer

func main() {

}

var _ = rectangle.Area 这一行屏蔽了错误。咱们应该跟踪这些“错误消音器”(error silencer), 在开发结束时,咱们应该去掉这些“错误消音器”,而且若是没有使用相应的包,这些包也应该被一并移除。所以,建议在 import 语句以后的包级别中写“错误消音器”。

有时咱们导入一个包只是为了确保该包初始化的发生,而咱们不须要使用包中的任何函数或变量。例如,咱们也许须要确保 rectangle 包的 init 函数被调用而不打算在代码中的任何地方使用这个包。空指示符仍然能够处理这种状况,像下面的代码同样:

package main 

import (   

     _ "geometry/rectangle" 

)
func main() {

}

运行上面的程序,将会获得输出:rectangle package initialized。咱们成功地初始化了这个包,即便在代码中的任何地方都没有使用它。

相关文章
相关标签/搜索