Golangimport本地包和导⼊问题相关详解
1 本地包声明
包是Go程序的基本单位,所以每个Go程序源代码的开始都是⼀个包声明:
package pkgName
这就是包声明,pkgName 告诉编译器,当前⽂件属于哪个包。⼀个包可以对应多个*.go源⽂件,标记它们属于同⼀包的唯⼀依据就是这个package声明,也就是说:⽆论多少个源⽂件,只要它们开头的package包相同,那么它们就属于同⼀个包,在编译后就只会⽣成⼀个.a⽂件,并且存放在$GOPATH/pkg⽂件夹下。
⽰例:
(1) 我们在$GOPATH/⽬录下,创建如下结构的⽂件夹和⽂件:
分别写⼊如下的代码:
<
//
package hello
import (
"fmt"
)
func SayHello() {
fmt.Println("SayHello()-->Hello")
}
<
/
/
package hello
import (
"fmt"
)
func SayWorld() {
fmt.Println("SayWorld()-->World")
}
<
//
package main
import (
"hello"
)
func main() {
hello.SayHello()
hello.SayWorld()
}
分析:
根据/中的package声明可知,它们俩属于同⼀个包–hello,那么根据分析,编译后只会⽣成⼀个*.a⽂件。
执⾏命令:
go install hello
该命令的意思⼤概是:编译并安装hello包,这⾥安装的意思是将⽣成的*.a⽂件放到⼯作⽬录$GOPATH/pkg⽬录下去
运⾏后:
从结果看出,果然只⽣成了⼀个包,并且名为hello.a
那么我们提出第⼆个问题:⽣成的*.a⽂件名是否就是我们定义的包名+.a后缀?
为了验证这个问题,我们对源码做⼀些更改:
将/中的package声明改为如下:
package hello_a
在编译安装包之前,先清除上⼀次⽣成的包:
go clean -i hello
再次编译安装该包:
go install hello_a
按照“正常推理”,上⾯这句命令是没什么问题的,因为我们已经将包名改成hello_a了啊,但是实际的运⾏结果是这样的:
oh~No!!
那么,我们再试试⽤这条命令:
go install hello
卧槽!!居然成功了!!是不是??
那么我们尝试⽣成⼀下可执⾏程序,看看能不能正常运⾏呢?
go build main
⼜报错了
看这报错提⽰,好像应该改⼀下源码,那就改成如下吧:
//
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)中的路径名呢?
我觉得,可以这样理解:
这⾥指定的是该×××路径名就代表了此⽬录下唯⼀的包,编译器连接器默认就会去⽣成或者使⽤它,⽽不需要我们⼿动指明!
好吧,问题⼜来了,如果⼀个⽬录下有多个包可以吗?如果可以,那该怎么编译和使⽤??
那我们继续改改源代码:
⾸先,保持 不变,改动为如下代码:
//
package hello
import (
"fmt"
)
func SayHello() {
fmt.Println("SayHello()-->Hello")
}
并且更改的源码如下
//
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 ××× 这⾥对应的并不是包名,⽽是路径名!!
这⾥指定的是该×××路径名就代表了此⽬录下唯⼀的包,编译器连接器默认就会去⽣成或者使⽤它,⽽不需要我们⼿动指明!
好吧,证明了这个还是挺兴奋的!!那我们继续!!
import语句如果⼀个⽬录下,真的有两个或者更多个包,那该如何⽣成??
抱着试⼀试的态度,我尝试了许多可能,但⽆⼀正确,最后⼀个命令的结果是让我崩溃的:
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
⾸先,还是对上⾯的⽰例程序做⼀个更改,这次我们让它变得更加简单点,因为接下来讨论的东西,可能会稍微有点绕~~
⾸先,删除,清理掉编译⽣成的⽂件,其他⽂件内容如下:
<
//
package hello
import (
"fmt"
)
func SayHello() {
fmt.Println("SayHello()-->Hello")
}
<
//
package main
import (
"hello"
)
func main() {
hello.SayHello()
}
最后,让整体保持如下的样式:
我们先编译⼀次,让程序能够运⾏起来:
go install hello
go build main
./main
好吧,假如你能看到输出,那就没问题了!
此时,再来看看整体的结构:
按照C/C++的⽅式来说,此时⽣成了hello.a这个链接库,那么源⽂件那些应该就没有必要了吧,所以。。。。我们这样搞⼀下,我们来更改⼀下源码,但不编译它!
<
//
package hello
import (
"fmt"
)
func SayHello() {
fmt.Println("SayHello()-->")
}
然后,我们删除之前的可执⾏⽂件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 ./
#要编译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 ./
#最后它进⼊了⼀个“当前⽬录”,应该就是我们执⾏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 import包内容请搜索以前的⽂章或继续浏览下⾯的相关⽂章希望⼤家以后多多⽀持!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论