如何在golang使用golang 自定义排序的包

理解Golang包导入 - Bill Yuan - 博客园
转自://understanding-import-packages/
使用包(package)这种语法元素来组织源码,所有语法可见性均定义在package这个级别,与Java 、python等语言相比,这算不上什么创新,但与C传统的include相比,则是显得&先进&了许多。
中包的定义和使用看起来十分简单:
通过package关键字定义包:
& &package xxx
使用import关键字,导入要使用的标准库包或第三方依赖包。
import "a/b/c"
import "fmt"
fmt.Println("Hello, World")
很多Golang初学者看到上面代码,都会想当然的将import后面的"c"、"fmt"当成包名,将其与c.Func1()和 fmt.Println()中的c和fmt认作为同一个语法元素:包名。但在深入Golang后,很多人便会发现事实上并非如此。比如在使用实时分布式消 息平台nsq提供的go client api时:
我们导入的路径如下:
&& import &/bitly/go-nsq&
但在使用其提供的export functions时,却用做前缀包名:
&& q, _ := nsq.NewConsumer("write_test", "ch", config)
人们不禁要问:import后面路径中的最后一个元素到底代表的是啥? 是包名还是仅仅是一个路径?我们一起通过试验来理解一下。& 实验环境:darwin_amd64 , go 1.4。
初始试验环境目录结果如下:
GOPATH = /Users/tony/Test/Go/pkgtest/pkgtest/&&& pkg/&&& src/& &&&& libproj1/&&& &&&&&& foo/&&& & & & &&& foo1.go&&& && app1/& &&& &&&& main.go&&&一、编译时使用的是包源码还是.a
我们知道一个非main包在编译后会生成一个.a文件(在临时目录下生成,除非使用go install安装到$GOROOT或$GOPATH下,否则你看不到.a),用于后续可执行程序链接使用。
比如Go标准库中的包对应的源码部分路径在:$GOROOT/src,而标准库中包编译后的.a文件路径在$GOROOT/pkg/darwin_amd64下。一个奇怪的问题在我脑袋中升腾起来,编译时,编译器到底用的是.a还是源码?
我们先以用户自定义的package为例做个小实验。
$GOPATH/src/&&& libproj1/foo/&&& &&& &&& & foo1.go&&& app1&&& &&& &&& & main.go
package foo
import "fmt"
func Foo1() {
fmt.Println("Foo1")
// main.go
package main
"libproj1/foo"
func main() {
foo.Foo1()
执行go install libproj1/foo,Go编译器编译foo包,并将foo.a安装到$GOPATH/pkg/darwin_amd64/libproj1下。编译app1:go build app1,在app1目录下生成app1*可执行文件,执行app1,我们得到一个初始预期结果:
$./app1Foo1
现在我们无法看出使用的到底是foo的源码还是foo.a,因为目前它们的输出都是一致的。我们修改一下foo1.go的代码:
package foo
import "fmt"
func Foo1() {
fmt.Println("Foo1 & modified")
重新编译执行app1,我们得到结果如下:
$./app1Foo1 & modified
实际测试结果告诉我们:(1)在使用第三方包的时候,当源码和.a均已安装的情况下,编译器链接的是源码。
那么是否可以只链接.a,不用第三方包源码呢?我们临时删除掉libproj1目录,但保留之前install的libproj1/foo.a文件。
我们再次尝试编译app1,得到如下错误:
$go build app1main.go:5:2: cannot find package "libproj1/foo" in any of:&&& /Users/tony/.Bin/go14/src/libproj1/foo (from $GOROOT)&&& /Users/tony/Test/Go/pkgtest/src/libproj1/foo (from $GOPATH)
编译器还是去找源码,而不是.a,因此我们要依赖第三方包,就必须搞到第三方包的源码,这也是Golang包管理的一个特点。
其实通过编译器的详细输出我们也可得出上面结论。我们在编译app1时给编译器传入-x -v选项:
$go build -x -v app1WORK=/var/folders/2h/xr2tmnxx6qxc4w4w13m01fsh0000gn/T/go-buildlibproj1/foomkdir -p $WORK/libproj1/foo/_obj/mkdir -p $WORK/libproj1/cd /Users/tony/Test/Go/pkgtest/src/libproj1/foo/Users/tony/.Bin/go14/pkg/tool/darwin_amd64/6g -o $WORK/libproj1/foo.a -trimpath $WORK -p libproj1/foo -complete -D _/Users/tony/Test/Go/pkgtest/src/libproj1/foo -I $WORK -pack ./foo1.go ./foo2.goapp1mkdir -p $WORK/app1/_obj/mkdir -p $WORK/app1/_obj/exe/cd /Users/tony/Test/Go/pkgtest/src/app1/Users/tony/.Bin/go14/pkg/tool/darwin_amd64/6g -o $WORK/app1.a -trimpath $WORK -p app1 -complete -D _/Users/tony/Test/Go/pkgtest/src/app1 -I $WORK -I /Users/tony/Test/Go/pkgtest/pkg/darwin_amd64 -pack ./main.gocd ./Users/tony/.Bin/go14/pkg/tool/darwin_amd64/6l -o $WORK/app1/_obj/exe/a.out -L $WORK -L /Users/tony/Test/Go/pkgtest/pkg/darwin_amd64 -extld=clang $WORK/app1.amv $WORK/app1/_obj/exe/a.out app1
可以看到编译器6g首先在临时路径下编译出依赖包foo.a,放在$WORK/libproj1下。但我们在最后6l链接器的执行语句中并未显式看到app1链接的是$WORK/libproj1下的foo.a。但是从6l链接器的-L参数来看:-L $WORK -L /Users/tony/Test/Go/pkgtest/pkg/darwin_amd64,我们发现$WORK目录放在了前面,我们猜测6l首先搜索到的时$WORK下面的libproj1/foo.a。
为了验证我们的推论,我们按照编译器输出,按顺序手动执行了一遍如上命令,但在最后执行6l命令时,去掉了-L $WORK:
/Users/tony/.Bin/go14/pkg/tool/darwin_amd64/6l -o $WORK/app1/_obj/exe/a.out -L /Users/tony/Test/Go/pkgtest/pkg/darwin_amd64 -extld=clang $WORK/app1.a
这样做的结果是:
$./app1Foo1
编译器链接了$GOPATH/pkg下的foo.a。(2)到这里我们明白了所谓的使用第三方包源码,实际上是链接了以该最新源码编译的临时目录下的.a文件而已。
Go标准库中的包也是这样么?对于标准库,比如fmt而言,编译时,到底使用的时$GOROOT/src下源码还是$GOROOT/pkg下已经编译好的.a呢?
我们不妨也来试试,一个最简单的hello world例子:
import "fmt"
func main() {
fmt.Println("Hello, World")
我们先将$GOROOT/src/fmt目录rename 为fmtbak,看看go compiler有何反应?go build -x -v ./
$go build -x -v ./WORK=/var/folders/2h/xr2tmnxx6qxc4w4w13m01fsh0000gn/T/go-buildmain.go:4:8: cannot find package "fmt" in any of:&&& /Users/tony/.Bin/go14/src/fmt (from $GOROOT)&&& /Users/tony/Test/Go/pkgtest/src/fmt (from $GOPATH)&找不到fmt包了。显然标准库在编译时也是必须要源码的。不过与自定义包不同的是,即便你修改了fmt包的源码(未重新编译GO安装包),用户源码编译时,也不会尝试重新编译fmt包的,依旧只是在链接时链接已经编译好的fmt.a。通过下面的gc输出可以验证这点:
$go build -x -v ./WORK=/var/folders/2h/xr2tmnxx6qxc4w4w13m01fsh0000gn/T/go-buildapp1mkdir -p $WORK/app1/_obj/mkdir -p $WORK/app1/_obj/exe/cd /Users/tony/Test/Go/pkgtest/src/app1/Users/tony/.Bin/go14/pkg/tool/darwin_amd64/6g -o $WORK/app1.a -trimpath $WORK -p app1 -complete -D _/Users/tony/Test/Go/pkgtest/src/app1 -I $WORK -pack ./main.gocd ./Users/tony/.Bin/go14/pkg/tool/darwin_amd64/6l -o $WORK/app1/_obj/exe/a.out -L $WORK -extld=clang $WORK/app1.amv $WORK/app1/_obj/exe/a.out app1
可以看出,编译器的确并未尝试编译标准库中的fmt源码。
二、目录名还是包名?
从第一节的实验中,我们得知了编译器在编译过程中依赖的是包源码的路径,这为后续的实验打下了基础。下面我们再来看看,Go语言中import后面路径中最后的一个元素到底是包名还是路径名?
本次实验目录结构:$GOPATH&&& src/&&& && libproj2/&&&& &&& &&& foo/&&& && &&& &&& foo1.go&&&&&& app2/&&&&&&& &&&& main.go
按照Golang语言习惯,一个go package的所有源文件放在同一个目录下,且该目录名与该包名相同,比如libproj1/foo目录下的package为foo,foo1.go、 foo2.go&共同组成foo package的源文件。但目录名与包名也可以不同,我们就来试试不同的。
我们建立libproj2/foo目录,其中的foo1.go代码如下:
package bar
import "fmt"
func Bar1() {
fmt.Println("Bar1")
注意:这里package名为bar,与目录名foo完全不同。
接下来就给app2带来了难题:该如何import bar包呢?
我们假设import路径中的最后一个元素是包名,而非路径名。
//app2/main.go
package main
"libproj2/bar"
func main() {
bar.Bar1()
编译app2:
$go build -x -v app2WORK=/var/folders/2h/xr2tmnxx6qxc4w4w13m01fsh0000gn/T/go-buildmain.go:5:2: cannot find package "libproj2/bar" in any of:&&& /Users/tony/.Bin/go14/src/libproj2/bar (from $GOROOT)&&& /Users/tony/Test/Go/pkgtest/src/libproj2/bar (from $GOPATH)
编译失败,在两个路径下无法找到对应libproj2/bar包。
我们的假设错了,我们把它改为路径:
//app2/main.go
package main
"libproj2/foo"
func main() {
bar.Bar1()
再编译执行:
$go build app2$app2Bar1
这回编译顺利通过,执行结果也是OK的。这样我们得到了结论:(3)import后面的最后一个元素应该是路径,就是目录,并非包名。
go编译器在这些路径(libproj2/foo)下找bar包。这样看来,go语言的惯例只是一个特例,即恰好目录名与包名一致罢了。也就是说下面例子中的两个foo含义不同:
import "libproj1/foo"
func main() {
import中的foo只是一个文件系统的路径罢了。而下面foo.Foo()中的foo则是包名。而这个包是在libproj1/foo目录下的源码中找到的。
再类比一下标准库包fmt。
import "fmt"fmt.Println("xxx")
这里上下两行中虽然都是&fmt",但同样含义不同,一个是路径 ,对于标准库来说,是$GOROOT/src/fmt这个路径。而第二行中的fmt则是包名。gc会在$GOROOT/src/fmt路径下找到fmt包的源文件。
三、import m "lib/math"
Go language specification中关于import package时列举的一个例子如下:
Import declaration&&&&&&&&& Local name of Sin
import&& "lib/math"&&&&&&&& math.Sinimport m "lib/math"&&&&&&&& m.Sinimport . "lib/math"&&&&&&&& Sin
我们看到import m "lib/math"& m.Sin一行。我们说过lib/math是路径,import语句用m替代lib/math,并在代码中通过m访问math包中的导出函数Sin。
那m到底是包名还是路径呢?既然能通过m访问Sin,那m肯定是包名了,Right!那import m "lib/math"该如何理解呢?&
根据上面一、二两节中得出的结论,我们尝试理解一下m:(4)m指代的是lib/math路径下唯一的那个包。
一个目录下是否可以存在两个包呢?我们来试试。
我们在libproj1/foo下新增一个go源文件,bar1.go:
package bar
import "fmt"
func Bar1() {
fmt.Println("Bar1")
我们重新构建一下这个目录下的包:
$go build libproj1/foocan't load package: package libproj1/foo: found packages bar1.go (bar) and foo1.go (foo) in /Users/tony/Test/Go/pkgtest/src/libproj1/foo
我们收到了错误提示,编译器在这个路径下发现了两个包,这是不允许的。
我们再作个实验,来验证我们对m含义的解释。
我们建立app3目录,其main.go的源码如下:
package main
import m "libproj2/foo"
func main() {
libproj2/foo路径下的包的包名为bar,按照我们的推论,m指代的就是bar这个包,通过m我们可以访问bar的Bar1导出函数。
编译并执行上面main.go:
$go build app3$app3Bar1
执行结果与我们推论完全一致。
附录:6g, 6l文档位置:
6g & $GOROOT/src/cmd/gc/doc.go6l & $GOROOT/src/cmd/ld/doc.go
随笔 - 752下次自动登录
现在的位置:
& 综合 & 正文
用Golang自己构造ICMP数据包
ICMP是用来对网络状况进行反馈的协议,可以用来侦测网络状态或检测网路错误。
限于当前Golang在网络编程方面的稀缺,资料甚少,所以分享一个用Golang来构造ICMP数据包并发送ping的echo消息的实例。
定义的echo数据包结构:
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Identifier
Sequence Number
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+-+-+-+-+-
package main
"encoding/binary"
type ICMP struct {
Identifier
SequenceNum uint16
func CheckSum(data []byte) uint16 {
length int = len(data)
for length & 1 {
sum += uint32(data[index])&&8 + uint32(data[index+1])
index += 2
length -= 2
if length & 0 {
sum += uint32(data[index])
sum += (sum && 16)
return uint16(^sum)
func main() {
laddr net.IPAddr = net.IPAddr{IP: net.ParseIP("192.168.137.111")}
//***IP地址改成你自己的网段***
raddr net.IPAddr = net.IPAddr{IP: net.ParseIP("192.168.137.1")}
//如果你要使用网络层的其他协议还可以设置成 ip:ospf、ip:arp 等
conn, err := net.DialIP("ip4:icmp", &laddr, &raddr)
if err != nil {
fmt.Println(err.Error())
defer conn.Close()
//开始填充数据包
icmp.Type = 8 //8-&echo message
0-&reply message
icmp.Code = 0
icmp.Checksum = 0
icmp.Identifier = 0
icmp.SequenceNum = 0
buffer bytes.Buffer
//先在buffer中写入icmp数据报求去校验和
binary.Write(&buffer, binary.BigEndian, icmp)
icmp.Checksum = CheckSum(buffer.Bytes())
//然后清空buffer并把求完校验和的icmp数据报写入其中准备发送
buffer.Reset()
binary.Write(&buffer, binary.BigEndian, icmp)
if _, err := conn.Write(buffer.Bytes()); err != nil {
fmt.Println(err.Error())
fmt.Printf("send icmp packet success!")
执行后可以用wireshark抓下包看看,可以看到远方网关传来了reply响应:
看看我们构造的ICMP是否正确:
如果转载请注明出处:http://blog.csdn.net/gophers/article/details/
&&&&推荐文章:
【上篇】【下篇】当前位置:
微信扫一扫分享到朋友圈
# 结构体和包中的类型或基础类型定义方法## 1、对结构体定义方法首先看下面这个例子:```golangpackage mainimport (& & &fmt&& & &math&)type Vertex struct {& & X, Y float64}func (v *Vertex) Abs() float64 {& & return math.Sqrt(v.X*v.X + v.Y*v.Y)}func main() {& & v := &Vertex{3, 4}& & fmt.Println(v.Abs())& & fmt.Println(v,*v)& &&& & x := Vertex{3, 4}& & fmt.Println(x.Abs())& & fmt.Println(x) &&}```Go 没有类。然而,仍然可以在结构体类型上定义方法。方法接收者 出现在 func 关键字和方法名之间的参数中--- & &(v *Vertex)```golangfunc (v *Vertex) FunName() float64 {& & & //&}```## 2、对包中的任意类型定义任意方法 &对包中的 任意 类型定义任意方法,而不仅仅是针对结构体。不能对来自其他包的类型或基础类型定义方法。----- &(f MyFloat)如下示例:```golangpackage mainimport (& & &fmt&& & &math&)type MyFloat float64func (f MyFloat) Abs() float64 {& & if f & 0 {& & & & return float64(-f)& & }& & return float64(f)}func main() {& & f := MyFloat(-math.Sqrt2)& & fmt.Println(f.Abs())}```下面用如下这个例子来区二者区别以及用法:```golangpackage mainimport (& & &fmt&& & &math&)// 普通类型type MyFloat float64func (f MyFloat) Abs() float64 {& & if f & 0 {& & & & return float64(-f)& & }& & return float64(f)}func (f *MyFloat) P() float64{& & if *f & 0 {& & & & return float64(-*f)& & }& & return float64(*f)}// 结构体type Vertex struct {& & X, Y float64}func (v *Vertex) Abs() float64 {& & return math.Sqrt(v.X*v.X + v.Y*v.Y)}func (v Vertex) Scale(f float64) {& & v.X = v.X * f& & v.Y = v.Y * f}func main() {& & // 基础类型的初始化和调用方式,注意用()而不是{}& & f := MyFloat(-math.Sqrt2)& & fmt.Println(f.Abs(),f.P())& &&& & /*& 错误调用方式1:& & f1 := &MyFloat(-math.Sqrt2)& & fmt.Println(*f.Abs(),*f.P()) 错误调用方式2: f1 := &MyFloat{-math.Sqrt2}& & fmt.Println(*f.Abs(),*f.P()) 错误调用方式3: f1 := MyFloat{-math.Sqrt2}& & fmt.Println(f.Abs(),f.P())& & */& & v := &Vertex{3, 4}& & fmt.Println(v, *v,&v,v.Abs())& & v.Scale(5)& & fmt.Println(v, *v,&v,v.Abs())& &&& & v1 := Vertex{6, 7}& & fmt.Println(v1,&v1, v1.Abs())& & v1.Scale(8)& & fmt.Println(v1, v1.Abs())}// 输出:/*1.1&{3 4} {3 4} 0x&{3 4} {3 4} 0x{6 7} &{6 7} 9.887{6 7} 9.887*/```**注意区别:**```golangfunc (v *Vertex) Abs() float64&func (v Vertex) Abs() float64&```## 3、注意两者区别:& * func (v *Vertex) Abs() float64&& & 调用方法时传递的是对象的实例的指针,即可改变对象的值;& * func (v Vertex) Abs() float64&调用方法时传递的是对象的实例的一个副本,即不可改变对象的值;总结:对象的实例针对数据操作时必须定义为指针的类型,然后才能传递正确的地址,否则v参数只是对象的一个副本,下面这个实例则可佐证此观点:```golangpackage mainimport (& & &fmt&& & &math&)// 普通类型type MyFloat float64func (f MyFloat) Abs() &{& & if f & 0 {& & & & f = MyFloat(float64(-f))& & }
f = MyFloat(float64(f))}func (f *MyFloat) Abs_1() {& & if *f & 0 {& & & & *f = MyFloat(float64(-*f))& & } *f = MyFloat(float64(*f))}// 结构体type Vertex struct {& & X, Y float64}func (v Vertex) Scale(f float64) {& & v.X = v.X * f& & v.Y = v.Y * f}func (v *Vertex) Scale_1(f float64) {& & v.X = v.X * f& & v.Y = v.Y * f}func main() {& & // 基础类型的初始化和调用方式,注意用()而不是{}& & f := MyFloat(-math.Sqrt2)& & fmt.Println(f, &f)& & f.Abs()& & fmt.Println(f, &f)& & f.Abs_1()& & fmt.Println(f, &f)& &&& &&& & v := &Vertex{3, 4}& & fmt.Println(v, *v,&v)& & v.Scale(5)& & fmt.Println(v, *v,&v)& & v.Scale_1(5)& & fmt.Println(v, *v,&v)& &&& & v1 := Vertex{6, 7}& & fmt.Println(v1,&v1)& & v1.Scale(8)& & fmt.Println(v1, &v1)& & v1.Scale_1(8)& & fmt.Println(v1, &v1)}// 输出结果:/*-1.00168-1.001681.00168&{3 4} {3 4} 0x&{3 4} {3 4} 0x&{15 20} {15 20} 0x{6 7} &{6 7}{6 7} &{6 7}{48 56} &{48 56}*/```总结:golang隐式传递指针,但是不隐式定义指针,此坑需同学们注意。----------------------------------------#==== golang-python学习心得 ====微信公众号:golang-python &私人微信ID:fuhao1121网址:http://fuhao715.github.ioQQ:&编程学习心得轻松学编程&回复:『 p 』查看python课程回复回复:『 g 』查看golang课程回复回复:『 m 』查看项目管理回复:『 w 』查看其他文章&点击&阅读全文&进入http://fuhao715.github.io
分享给好友
分享到微信朋友圈:
第一步 打开微信底部扫一扫
第二步 扫下面的文章二维码
第三步 右上角点击转发
相关文章Relevant
■ 点击上面蓝字一键关注 ▲QIBU生活微刊建议在WIFI下观看,土豪请随意~~1、每一次接吻 会消耗体内至少12个卡路里科学家指出:...
我是主播 贝妮~(微信号:Voaoao)每天提供最热门、最火爆、最精彩的视频!口味有点儿重喔~笑死!笑死!笑死!如果觉得这些还...
【最费脑力的14部电影】《盗梦空间》、《记忆裂痕》、《生死停留》、《死亡幻觉》、《禁闭岛》、《穆赫兰道》、《蝴蝶效应》、...
现如今,飞机以舒适、方便与节省时间等原因成为出行首选的交通方式之一.可你是否知道,为何不能喝飞机上的冲泡茶饮,又为何在...
感知CG,感触创意,感受艺术,感悟心灵 在CG世界的一期中我们展示了 Vince Low的一部分作品,今天再次翻看CG网站时发现他的...
因女儿未出世便患肿瘤,柴静离职后首发雾霾调查.雾霾是什么?它从哪儿来?我们怎么办?看完这些,才知道雾霾的真相.震撼!震...Go语言使用sort包对任意类型元素的集合进行排序的方法
作者:books1958
字体:[ ] 类型:转载 时间:
这篇文章主要介绍了Go语言使用sort包对任意类型元素的集合进行排序的方法,实例分析了sort排序所涉及的方法与相关的使用技巧,需要的朋友可以参考下
本文实例讲述了Go语言使用sort包对任意类型元素的集合进行排序的方法。分享给大家供大家参考。具体如下:
使用sort包的函数进行排序时,集合需要实现sort.Inteface接口,该接口中有三个方法:
代码如下:// Len is the number of elements in the collection.&
Len() int&
// Less reports whether the element with&
// index i should sort before the element with index j.&
Less(i, j int) bool&
// Swap swaps the elements with indexes i and j.&
Swap(i, j int)
以下为简单示例:
代码如下://对任意对象进行排序&
type Person struct {&
&&& name string&
&&& age& int&
//为*Person添加String()方法,便于输出&
func (p *Person) String() string {&
&&& return fmt.Sprintf("( %s,%d )", p.name, p.age)&
type PersonList []*Person&
//排序规则:首先按年龄排序(由小到大),年龄相同时按姓名进行排序(按字符串的自然顺序)&
func (list PersonList) Len() int {&
&&& return len(list)&
func (list PersonList) Less(i, j int) bool {&
&&& if list[i].age & list[j].age {&
&&&&&&& return true&
&&& } else if list[i].age & list[j].age {&
&&&&&&& return false&
&&& } else {&
&&&&&&& return list[i].name & list[j].name&
func (list PersonList) Swap(i, j int) {&
&&& var temp *Person = list[i]&
&&& list[i] = list[j]&
&&& list[j] = temp&
func interfaceTest0203() {&
&&& fmt.Println("------")&
&&& p1 := &Person{"Tom", 19}&
&&& p2 := &Person{"Hanks", 19}&
&&& p3 := &Person{"Amy", 19}&
&&& p4 := &Person{"Tom", 20}&
&&& p5 := &Person{"Jogn", 21}&
&&& p6 := &Person{"Mike", 23}&
&&& pList := PersonList([]*Person{p1, p2, p3, p4, p5, p6})&
&&& sort.Sort(pList)&
&&& fmt.Println(pList)&
&&& /*output:&
&&& [( Amy,19 ) ( Hanks,19 ) ( Tom,19 ) ( Tom,20 ) ( Jogn,21 ) ( Mike,23 )] */&
希望本文所述对大家的Go语言程序设计有所帮助。
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具

我要回帖

更多关于 golang 自定义包 的文章

 

随机推荐