Go语言之包管理

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介:
什么是 Go 语言中的包


我们在使用其他语言,比如 Java ,是有包的概念的。它是 Java 语言中组织我们的 Java 文件的一个概念,比如java.lang这个包,它里面有很多我们常用的类,比如 String。在 Go 语言中,包也是类似的概念。它是把我们的 Go 文件组织起来,可以方便进行归类、复用等, 比如 Go 内置的 net 包。


net
├── http
├── internal
├── mail
├── rpc
├── smtp
├── testdata
├── textproto
└── url


以上是 net 包的一个目录结构,net 本身是一个包,net 目录下的 http 又是一个包。从这个大家可以看到,Go 语言的包其实就是我们计算机里的目录,或者叫文件夹,通过它们进行目录结构和文件组织。Go 只是对目录名字做了一个翻译,叫“包”而已。比如这里的 net 包其实就是 net 目录,http 包其实就是 http 目录,这也是 Go 语言中的一个命名习惯,包名和文件所在的目录名是一样的。


包的命名


Go 语言中包的命名,遵循简洁、小写以及与 Go 文件所在目录同名的原则,这样就便于我们引用、书写和快速定位查找。


比如 Go 自带的 http 这个包,这个 http 目录下的所有 Go 文件都属于这个 http 包,所以我们使用 http 包里的函数、接口的时候,导入这个 http 包就可以了。


package main

import "net/http"

func main() {
    http.ListenAndServe("127.0.0.1:80",handler);
}


从这个例子可以看到,我们导入的是net/http,这在 Go 语言里叫做全路径。因为 http 包在 net 里面,net 是最顶级的包,所以必须使用全路径导入,Go 编译程序才能找到 http 这个包,和我们文件系统的目录路径是一样的。


因为有了全路径,所以命名的包名可以和其他库的一样,只要它们的全路径不同就可以了。使用全路径的导入,也增加了包名命名的灵活性。


对于个人或者公司开发的程序而言,我们一般采用域名作为顶级包名的方式,这样就不用担心和其他开发者包名重复的问题了,比如我的个人域名是www.flysnow.org,那么我自己开发的 Go 程序都以flysnow.org作为全路径中的最顶层部分。例如,导入我开发的一个工具包:


package main

import "flysnow.org/tools"


如果你没有自己的域名,怎么办呢?这时候可以使用 Github.com 。干研发这一行的,在 Github 都会有个账号,如果没有赶紧申请一个。这时候我们就可以使用github.com/<username>作为你的顶级路径了,别人是不会和你重名的。


package main

import "github.com/rujews/tools"


这就是换成 Github.com 命名的方式。


main包


当把一个 Go 文件的包名声明为main时,就等于告诉 Go 编译程序,我这个是一个可执行的程序,那么 Go 编译程序就会尝试把它编译为一个二进制的可执行文件。


一个main的包,一定会包含一个main()函数,这种我们也不陌生,比如 C 和 Java 都有main()函数,它是一个程序的入口,没这个函数,程序就无法执行。


在 Go 语言里,同时要满足main包和包含main()函数,才会被编译成一个可执行文件。


我们看一个 Hello World 的 Go 语言版本,来说明main 包。


package main

import "fmt"

func main() {
    fmt.Println("Hello, 世界")
}


假设该 Go 文件叫 hello.go,放在$GOPATH/src/hello目录下,那么我们在这个目录下执行go build命令就会生成二进制的可执行文件,在 window 系统下生成的是hello.exe;在 UINX、MAC 和 Linux 下生成的是hello,我们在 CMD 或者终端里执行它,就可以看到控制台打印的:


Hello, 世界


二进制可执行文件的名字,就是该 main 包的 Go 文件所在目录的名字,因为 hello.go 在 hello 目录下,所以生成的可执行文件就是 hello 这个名字。


导入包


要想使用一个包,必须先导入它才可以使用。Go 语言提供了import关键字来导入一个包,这个关键字告诉 Go 编译器到磁盘的哪里去找要想导入的包,所以导入的包必须是一个全路径的包,也就是包所在的位置。


import "fmt"


这就表示我们导入了fmt包,也就等于告诉 Go 编译器,我们要使用这个包下面的代码。如果要导入多个包怎么办呢?Go 语言还为我们提供了导入块。


import (
    "net/http"
    "fmt"
)


使用一对括号包含导入块,每个包独占一行。


对于多于一个路径的包名,在代码中引用的时候,使用全路径最后一个包名作为引用的包名,比如net/http,我们在代码使用的是http,而不是net


现在我导入了包,那么编译的时候,Go 编译器去什么位置找它们呢?这里就要介绍一下 Go 的环境变量了。Go 有两个很重要的环境变GOROOTGOPATH,这是两个定义路径的环境变量,GOROOT是安装 Go 的路径,比如/usr/local/goGOPATH是我们自己定义的开发者个人的工作空间,比如/home/flysnow/go。 


编译器会使用我们设置的这两个路径,再加上import导入的相对全路径来查找磁盘上的包,比如我们导入的fmt包,编译器最终找到的是/usr/local/go/fmt这个位置。


值得了解的是:首先,对于包的查找是有优先级的,编译器会优先在GOROOT里搜索;其次是GOPATH,一旦找到,就会马上停止搜索。如果最终都没找到,那么就报编译异常了。


远程包导入


互联网的时代,现在大家使用类似于 Github 共享代码的越来越多,如果有的 Go 包共享在 Github 上,我们一样有办法使用它们,这就是远程导入包了,或者是网络导入,Go 天生就支持这种情况,所以我们可以很随意地使用 Github 上的 Go 库开发程序。


import "github.com/spf13/cobra"


这种导入,前提必须是该包托管在一个分布式的版本控制系统上,比如 Github、Bitbucket 等,并且是 Public 的权限,可以让我们直接访问它们。


编译在导入它们的时候,会先在GOPATH下搜索这个包,如果没有找到,就会使用go get工具从版本控制系统(GitHub)获取,并且会把获取到的源代码存储在GOPATH目录下对应 URL 的目录里,以供编译使用。


go get工具可以递归获取依赖包,如果github.com/spf13/cobra也引用了其他的远程包,该工具可以一并下载下来。


命名导入


我们知道,在使用import关键字导入包之后,我们就可以在代码中通过包名使用该包下相应的函数、接口等。如果我们导入的包名正好有重复的怎么办呢?针对这种情况,Go 语言可以让我们对导入的包重新命名,这就是命名导入。


package main

import (
    "fmt"
    myfmt "mylib/fmt"
)

func main() {
    fmt.Println()
    myfmt.Println()
}


如果没有重新命名,那么对于编译器来说,这两个fmt它是区分不清楚的。重命名也很简单,在我们导入的时候,在包名的左侧,起一个新的包名就可以了。


Go 语言规定,导入的包必须要使用,否则会包编译错误,这是一个非常好的规则,因为这样可以避免我们引用很多无用的代码而导致的代码臃肿和程序的庞大。很多时候,我们都不知道哪些包是否使用,这在 C 和 Java 上会经常遇到,因此我们不得不借助工具来查找我们没有使用的文件、类型、方法和变量等,把它们清理掉。


但是有时候,我们需要导入一个包,但是又不使用它,按照规则,这是不行的,为此 Go 语言给我们提供了一个空白标志符 _ ,只需要我们使用 _ 重命名我们导入的包就可以了。


package main

import (
    _ "mylib/fmt"
)


包的 init ()函数


每个包都可以有任意多个init()函数,这些init()函数都会在main()函数之前执行。init()函数通常用来做初始化变量、设置包或者其他需要在程序执行前的引导工作。比如上面我们讲的需要使用 _ 空标志符来导入一个包的目的,就是想执行这个包里的init()函数。


我们以数据库的驱动为例,Go 语言为了统一关于数据库的访问,使用databases/sql抽象了一层数据库的操作,可以满足我们操作 MYSQL、Postgre 等数据库。这样不管我们使用这些数据库的哪个驱动,编码操作都是一样的,想换驱动的时候,就可以直接换掉,而不用修改具体的代码。


这些数据库驱动的实现,就是具体的,可以由任何人实现的,它的原理就是定义了init()函数,在程序运行之前,把实现好的驱动注册到 sql 包里,这样我们就使用使用它操作数据库了。


package mysql

import (
    "database/sql"
)

func init() {
    sql.Register("mysql", &MySQLDriver{})
}


因为我们只是想执行这个 mysql 包的init()方法,并不想使用这个包,所以我们在导入这个包的时候,需要使用_重命名包名,避免编译错误。


import "database/sql"
import _ "github.com/go-sql-driver/mysql"

db, err := sql.Open("mysql", "user:password@/dbname")


看非常简洁,剩下针对的数据库的操作都是使用的database/sql标准接口。如果我们想换一个 mysql 的驱动的话,只需要换个导入就可以了,灵活方便,这也是面向接口编程的便利。



本文转自 baby神 51CTO博客,原文链接:http://blog.51cto.com/babyshen/1913254,如需转载请自行联系原作者

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
5天前
|
数据挖掘 API Go
《Go 简易速速上手小册》第7章:包管理与模块(2024 最新版)(下)
《Go 简易速速上手小册》第7章:包管理与模块(2024 最新版)
27 1
|
15天前
|
Go
go语言中的数据类型
go语言中的数据类型
13 0
|
21天前
|
Go 开发者
掌握Go语言:Go语言结构体,精准封装数据,高效管理实体对象(22)
掌握Go语言:Go语言结构体,精准封装数据,高效管理实体对象(22)
|
21天前
|
安全 Go
掌握Go语言:Go语言通道,并发编程的利器与应用实例(20)
掌握Go语言:Go语言通道,并发编程的利器与应用实例(20)
|
21天前
|
存储 缓存 安全
掌握Go语言:Go语言中的字典魔法,高效数据检索与应用实例解析(18)
掌握Go语言:Go语言中的字典魔法,高效数据检索与应用实例解析(18)
|
21天前
|
存储 安全 Go
掌握Go语言:Go语言类型转换,无缝处理数据类型、接口和自定义类型的转换细节解析(29)
掌握Go语言:Go语言类型转换,无缝处理数据类型、接口和自定义类型的转换细节解析(29)
|
1天前
|
程序员 Go API
【Go语言快速上手(二)】 分支与循环&函数讲解
【Go语言快速上手(二)】 分支与循环&函数讲解
|
1天前
|
Go
Golang深入浅出之-Go语言基础语法:变量声明与赋值
【4月更文挑战第20天】本文介绍了Go语言中变量声明与赋值的基础知识,包括使用`var`关键字和简短声明`:=`的方式,以及多变量声明与赋值。强调了变量作用域、遮蔽、初始化与零值的重要性,并提醒读者注意类型推断时的一致性。了解这些概念有助于避免常见错误,提高编程技能和面试表现。
12 0
|
1天前
|
编译器 Go 开发者
Go语言入门|包、关键字和标识符
Go语言入门|包、关键字和标识符
12 0
|
2天前
|
数据采集 存储 Go
使用Go语言和chromedp库下载Instagram图片:简易指南
Go语言爬虫示例使用chromedp库下载Instagram图片,关键步骤包括设置代理IP、创建带代理的浏览器上下文及执行任务,如导航至用户页面、截图并存储图片。代码中新增`analyzeAndStoreImage`函数对图片进行分析和分类后存储。注意Instagram的反爬策略可能需要代码适时调整。
使用Go语言和chromedp库下载Instagram图片:简易指南

热门文章

最新文章