当前位置:首页 > 文章列表 > Golang > Go教程 > Golang import本地包和导入问题相关详解

Golang import本地包和导入问题相关详解

来源:脚本之家 2022-12-30 14:31:25 0浏览 收藏

知识点掌握了,还需要不断练习才能熟练运用。下面golang学习网给大家带来一个Golang开发实战,手把手教大家学习《Golang import本地包和导入问题相关详解》,在实现功能的过程中也带大家重新温习相关知识点,温故而知新,回头看看说不定又有不一样的感悟!

1 本地包声明

包是Go程序的基本单位,所以每个Go程序源代码的开始都是一个包声明:

package pkgName

这就是包声明,pkgName 告诉编译器,当前文件属于哪个包。一个包可以对应多个*.go源文件,标记它们属于同一包的唯一依据就是这个package声明,也就是说:无论多少个源文件,只要它们开头的package包相同,那么它们就属于同一个包,在编译后就只会生成一个.a文件,并且存放在$GOPATH/pkg文件夹下。

示例:

(1) 我们在$GOPATH/目录下,创建如下结构的文件夹和文件:

分别写入如下的代码:

hello.go

//hello.go
package hello

import (
  "fmt"
)

func SayHello() {
  fmt.Println("SayHello()-->Hello")
}

hello2.go

//hello2.go
package hello

import (
  "fmt"
)

func SayWorld() {
  fmt.Println("SayWorld()-->World")
}

main.go

//main.go
package main

import (
  "hello"
)

func main() {
  hello.SayHello()
  hello.SayWorld()
}

分析:

根据hello.go/hello2.go中的package声明可知,它们俩属于同一个包–hello,那么根据分析,编译后只会生成一个*.a文件。

执行命令:

go install hello

该命令的意思大概是:编译并安装hello包,这里安装的意思是将生成的*.a文件放到工作目录$GOPATH/pkg目录下去

运行后:

从结果看出,果然只生成了一个包,并且名为hello.a

那么我们提出第二个问题:生成的*.a文件名是否就是我们定义的包名+.a后缀?

为了验证这个问题,我们对源码做一些更改:
将hello.go/hello2.go中的package声明改为如下:

package hello_a

在编译安装包之前,先清除上一次生成的包:

go clean -i hello

再次编译安装该包:

go install hello_a

按照“正常推理”,上面这句命令是没什么问题的,因为我们已经将包名改成hello_a了啊,但是实际的运行结果是这样的:

oh~No!!
那么,我们再试试用这条命令:

go install hello

卧槽!!居然成功了!!是不是??

那么我们尝试生成一下可执行程序,看看能不能正常运行呢?

go build main

又报错了!!!

看这报错提示,好像应该改一下main.go源码,那就改成如下吧:

//main.go
package main

import (
  "hello_a"
)

func main() {
  hello_a.SayHello()
  hello_a.SayWorld()
}

改成上面这样也合情合理哈?毕竟我们把包名定义成了hello_a了!
那就再来编译一次吧:

go build main

继续报错!

等等!!有新的发现,对比上两次的报错信息,可见第一次还能能找到hello_a包的,更改源码后居然还TM找不到hello_a包了??
好吧,那咱再改回去,不过这回只改包的导入语句,改成:

import (
  "hello"
)

再次编译:

go build main

卧槽!!居然没报错了!!再运行一下可执行程序:

好吧,终于得到了想要的结果!

那进行到这里能说明什么呢?

(1) 一个包确实可以由多个源文件组成,只要它们开头的包声明一样
(2)一个包对应生成一个*.a文件,生成的文件名并不是包名+.a
(3) go install ××× 这里对应的并不是包名,而是路径名!!
(4) import ××× 这里使用的也不是包名,也是路径名!
(5) ×××××.SayHello() 这里使用的才是包名!

那么问题又来了,我们该如何理解(3)、(4)中的路径名呢?
我觉得,可以这样理解:
这里指定的是该×××路径名就代表了此目录下唯一的包,编译器连接器默认就会去生成或者使用它,而不需要我们手动指明!

好吧,问题又来了,如果一个目录下有多个包可以吗?如果可以,那该怎么编译和使用??

那我们继续改改源代码:

首先,保持hello2.go 不变,改动hello.go为如下代码:

//hello.go
package hello

import (
  "fmt"
)

func SayHello() {
  fmt.Println("SayHello()-->Hello")
}

并且更改main.go的源码如下

//main.go
package main

import (
  "hello"
)

func main() {
  hello.SayHello()
  hello_a.SayWorld()
}

再次清理掉上次生成的可执行程序与包:

go clean -i hello
go clean -x main

你可以试着执行如上的命令,如果不能清除,那就手动删除吧!
反正,还原成如下样子:

那么再次尝试编译并安装包,不过注意了,此时hello目录下有两个包了,不管是否正确,我们先尝试一下:

go install hello

oh~~果然出错了!!

 
看到了吗?它说它找到了两个包了啊!!!

那这能说明什么呢??

其实这就更加确定的说明了,我们上面的推测是正确的!

(3) go install ××× 这里对应的并不是包名,而是路径名!!

这里指定的是该×××路径名就代表了此目录下唯一的包,编译器连接器默认就会去生成或者使用它,而不需要我们手动指明!

好吧,证明了这个还是挺兴奋的!!那我们继续!!

如果一个目录下,真的有两个或者更多个包,那该如何生成??
抱着试一试的态度,我尝试了许多可能,但无一正确,最后一个命令的结果是让我崩溃的:

go help install

恩!对!你没有看错:installs the packages named by the import paths
What the fuck!! 以后还是决定要先看文档再自己做测试!!

好吧,综上所述,一个目录下就只能有一个包吧,因为都是指定路径,没有办法指定路径下的某个具体的包,这样的做法其实也挺好,让源代码结构更清晰!

2 包的导入问题

导入包:

  • 标准包使用的是给定的短路径,如"fmt"、"net/http"
  • 自己的包,需要在工作目录(GOPATH)下指定一个目录,improt 导入包,实际上就是基于工作目录的文件夹目录

导入包的多种方式:

  • 直接根据$GOPATH/src目录导入import "test/lib"(路径其实是$GOPATH/src/test/lib)
  • 别名导入:import alias_name "test/lib" ,这样使用的时候,可以直接使用别名
  • 使用点号导入:import . "test/lib",作用是使用的时候直接省略包名
  • 使用下划线导入:improt _ "test/lib",该操作其实只是引入该包。当导入一个包时,它所有的init()函数就会被执行,但有些时候并非真的需要使用这些包,仅仅是希望它的init()函数被执行而已。这个时候就可以使用_操作引用该包。即使用_操作引用包是无法通过包名来调用包中的导出函数,而是只是为了简单的调用其init函数()。往往这些init函数里面是注册自己包里面的引擎,让外部可以方便的使用,例如实现database/sql的包,在init函数里面都是调用了sql.Register(name string, driver driver.Driver)注册自己,然后外部就可以使用了。
  • 相对路径导入     import   "./model"  //当前文件同一目录的model目录,但是不建议这种方式import

首先,还是对上面的示例程序做一个更改,这次我们让它变得更加简单点,因为接下来讨论的东西,可能会稍微有点绕~~

首先,删除hello2.go,清理掉编译生成的文件,其他文件内容如下:

hello.go

//hello.go
package hello

import (
  "fmt"
)

func SayHello() {
  fmt.Println("SayHello()-->Hello")
}

main.go

//main.go
package main

import (
  "hello"
)

func main() {
  hello.SayHello()
}

最后,让整体保持如下的样式:

我们先编译一次,让程序能够运行起来:

go install hello
go build main
./main

好吧,假如你能看到输出,那就没问题了!
此时,再来看看整体的结构:

按照C/C++的方式来说,此时生成了hello.a这个链接库,那么源文件那些应该就没有必要了吧,所以。。。。我们这样搞一下,我们来更改一下hello.go源码,但不编译它!
hello.go

//hello.go
package hello

import (
  "fmt"
)

func SayHello() {
  fmt.Println("SayHello()-->Hello_modifi...")
}

然后,我们删除之前的可执行文件main,再重新生成它:

rm main
go build main

恩~~等等,我看一下运行结果:

What the fuck!!!为什么出来的是这货???

好吧,为了一探究竟,我们再次删除main文件,并再次重新编译,不过命令上得做点手脚,我们要看看编译器连接器这两个小婊砸到底都干了些什么,为啥是隔壁老王的儿子出来了??!!

rm main
go build -x -v main

结果:

那么我们一步一步对这个结果做一个分析:

#首先,它好像指定了一个临时工作目录
WORK=/tmp/go-build658882358 

#看着样子,它好像是要准备编译hello目录下的包
hello
#然后创建了一系列临时文件夹
mkdir -p $WORK/hello/_obj/  
mkdir -p $WORK/

#进入包的源文件目录
cd /home/yuxuan/GoProjects/import/src/hello 

#调用6g这个编译器编译生成hello.a,存放在$WORK/临时目录下
/opt/go/pkg/tool/linux_amd64/6g -o $WORK/hello.a -trimpath $WORK -p hello -complete -D _/home/yuxuan/GoProjects/import/src/hello -I $WORK -pack ./hello.go

#要编译main目录下的包了
main
#还是创建一系列的临时文件夹
mkdir -p $WORK/main/_obj/  
mkdir -p $WORK/main/_obj/exe/

#进入main文件夹
cd /home/yuxuan/GoProjects/import/src/main

#调用6g编译器,编译生成main.a,存放于$WORK/临时目录下
/opt/go/pkg/tool/linux_amd64/6g -o $WORK/main.a -trimpath $WORK -p main -complete -D _/home/yuxuan/GoProjects/import/src/main -I $WORK -I /home/yuxuan/GoProjects/import/pkg/linux_amd64 -pack ./main.go

#最后它进入了一个“当前目录”,应该就是我们执行go build命令的目录
cd .

#调用连接器6l 然后它链接生成a.out,存放与临时目录下的$WORK/main/_obj/exe/文件夹中,但是在链接选项中并未直接发现hello.a
#从链接选项:-L $WORK -L /home/yuxuan/GoProjects/import/pkg/linux_amd64中可以看出,连接器首先搜索了$WORK临时目录下的所有*.a文件,然后再去搜索/home/yuxuan/GoProjects/import/pkg/linux_amd64目录下的*.a文件,可见原因
/opt/go/pkg/tool/linux_amd64/6l -o $WORK/main/_obj/exe/a.out -L $WORK -L /home/yuxuan/GoProjects/import/pkg/linux_amd64 -extld=gcc $WORK/main.a

#最后,移动可执行文件并重命名
mv $WORK/main/_obj/exe/a.out main

到这里,其实差不多也就得出结论了,连接器在连接时,其实使用的并不是我们工作目录下的hello.a文件,而是以该最新源码编译出的临时文件夹中的hello.a文件。

当然,如果你对这个结论有所怀疑,可以试试手动执行上述命令,在最后链接时,去掉-L $WORK的选项,再看看运行结果!

那么,这是对于有源代码的第三方库,如果没有源代码呢?

其实,结果显而易见,没有源代码,上面的临时编译不可能成功,那么临时目录下就不可能有.a文件,所以最后链接时就只能链接到工作目录下的.a文件!

但是,如果是自带的Go标准库呢?

其实也可以用上述的方法验证一下,验证过程就不写了吧?
最后得到的结果是:对于标准库,即便是修改了源代码,只要不重新编译Go源码,那么链接时使用的就还是已经编译好的*.a文件!

3 导入包的三种模式

包导入有三种模式:正常模式、别名模式、简便模式

Go language specification中关于import package时列举的一个例子如下:

Import declaration Local name of Sin

import “lib/math” math.Sin 
import m “lib/math” m.Sin 
import . “lib/math” Sin

我们看到import m “lib/math” m.Sin一行,在上面的结论中说过lib/math是路径,import语句用m替代lib/math,并在代码中通过m访问math包中导出的函数Sin。
那m到底是包名还是路径呢?
答案显而易见,能通过m访问Sin,那m肯定是包名了!
那问题又来了,import m “lib/math”该如何理解呢?

根据上面得出的结论,我们尝试这样理解m:m指代的是lib/math路径下唯一的那个包!

4 总结

经过上面这一长篇大论,是时候该总结一下成果了:

多个源文件可同属于一个包,只要声明时package指定的包名一样;一个包对应生成一个*.a文件,生成的文件名并不是包名+.a组成,应该是目录名+.a组成go install ××× 这里对应的并不是包名,而是路径名!!import ××× 这里使用的也不是包名,也是路径名×××××.SayHello() 这里使用的才是包名!指定×××路径名就代表了此目录下唯一的包,编译器连接器默认就会去生成或者使用它,而不需要我们手动指明!一个目录下就只能有一个包存在对于调用有源码的第三方包,连接器在连接时,其实使用的并不是我们工作目录下的.a文件,而是以该最新源码编译出的临时文件夹中的.a文件对于调用没有源码的第三方包,上面的临时编译不可能成功,那么临时目录下就不可能有.a文件,所以最后链接时就只能链接到工作目录下的.a文件对于标准库,即便是修改了源代码,只要不重新编译Go源码,那么链接时使用的就还是已经编译好的*.a文件包导入有三种模式:正常模式、别名模式、简便模式

今天关于《Golang import本地包和导入问题相关详解》的内容就介绍到这里了,是不是学起来一目了然!想要了解更多关于golang的内容请关注golang学习网公众号!

版本声明
本文转载于:脚本之家 如有侵犯,请联系study_golang@163.com删除
Golang中的Slice与数组及区别详解Golang中的Slice与数组及区别详解
上一篇
Golang中的Slice与数组及区别详解
Golang常用环境变量说明与设置详解
下一篇
Golang常用环境变量说明与设置详解
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    542次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    508次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    497次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    484次学习
查看更多
AI推荐
  • PPTFake答辩PPT生成器:一键生成高效专业的答辩PPT
    PPTFake答辩PPT生成器
    PPTFake答辩PPT生成器,专为答辩准备设计,极致高效生成PPT与自述稿。智能解析内容,提供多样模板,数据可视化,贴心配套服务,灵活自主编辑,降低制作门槛,适用于各类答辩场景。
    21次使用
  • SEO标题Lovart AI:全球首个设计领域AI智能体,实现全链路设计自动化
    Lovart
    SEO摘要探索Lovart AI,这款专注于设计领域的AI智能体,通过多模态模型集成和智能任务拆解,实现全链路设计自动化。无论是品牌全案设计、广告与视频制作,还是文创内容创作,Lovart AI都能满足您的需求,提升设计效率,降低成本。
    20次使用
  • 美图AI抠图:行业领先的智能图像处理技术,3秒出图,精准无误
    美图AI抠图
    美图AI抠图,依托CVPR 2024竞赛亚军技术,提供顶尖的图像处理解决方案。适用于证件照、商品、毛发等多场景,支持批量处理,3秒出图,零PS基础也能轻松操作,满足个人与商业需求。
    33次使用
  • SEO标题PetGPT:智能桌面宠物程序,结合AI对话的个性化陪伴工具
    PetGPT
    SEO摘要PetGPT 是一款基于 Python 和 PyQt 开发的智能桌面宠物程序,集成了 OpenAI 的 GPT 模型,提供上下文感知对话和主动聊天功能。用户可高度自定义宠物的外观和行为,支持插件热更新和二次开发。适用于需要陪伴和效率辅助的办公族、学生及 AI 技术爱好者。
    34次使用
  • 可图AI图片生成:快手可灵AI2.0引领图像创作新时代
    可图AI图片生成
    探索快手旗下可灵AI2.0发布的可图AI2.0图像生成大模型,体验从文本生成图像、图像编辑到风格转绘的全链路创作。了解其技术突破、功能创新及在广告、影视、非遗等领域的应用,领先于Midjourney、DALL-E等竞品。
    56次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码