android 运行 go_和我一起学Go系列:Go 包/模块及其隐藏特性

3f9988a1ee2b53fb98ad5973be6a27d2.png

在 Java 中,我们将每个 .java 文件称之为类。引入某个类到别的类中,称之为类对象引用。

在 Go 中并没有类的概念,每个 .go 文件被称之为模块,引入某个模块到别的模块中称之为模块引用。

但是与 Java 不同的是:Go 中不存在引入某个模块到另一个模块中,只有包的引用。

我们可以看最简单的 fmt 的使用:

a73902c1e590455114f0fda4ad2b0eb5.png

fmt 是包名而不是模块名。

引入了 fmt 这个包之后,该包下所有公开的变量和方法都会被引用到。所谓的公开的,即变量或者方法名开头是大写字母形式,基本语法中有介绍,不再赘述。

这一篇主要介绍模块内各个对象的初始化方式以及隐藏特性,如何引入包下的各个模块。

包的概念

如同所有的语言,pkg 没有什么高大上,就是 Go 文件的管理目录。只是每个语言对包管理和引入的方式有所区别,所以值得我们拿出来单独说明。

每个 .go 源文件的第一行必然要声明该文件属于哪个包,比如:

package main

一个独立的 Go 程序必须要有一个 package main 的声明,main 包是该程序可执行的入口,所有的启动都将从 main 函数开始。

包的引用

下面我们来创建一些包以及 Go 文件,观察如何引用:

d16d6685ad5b570f34bb60c4d6fb8349.png

有如上目录结构,

UserInfo.go

package user



type User struct {
 Id int64
 Name string
 Sex int32
}

func GetUserInfo(uid int64) User {
 return User{1,"xiaoming", 1}
}

我们接下来在 UserDao.go 中引用 UserInfo.go:

package dao

import (
 "fmt"
 "mod-demo/user"
)

func GetUser(uid int64) user.User {
 fmt.Print()
 return user.GetUserInfo(uid)
}

可以看到导入了 'mod-demo/user' 包名,然后使用 user 就可以点出来该包下的公开变量/方法。

那我们接着在 UserInfo.go 中添加一个私有属性:

var default_sex int32

可以看到这时候是无法引入:

64f3d97f3c41bc6e92d9b162de3b9eca.png

接着我们继续在 user 包下新增 UserClientCache.go 模块,看看是否还是可以通过 user 包名来引用。

UserClientCache.go

package user

type UserClient struct {
 Id int64
 ClientId int64
 ClientType string

}

func GetUserClient(id int64) UserClient  {
 return UserClient{1, 2, "android"}
}

接着引用它:

9faabadeb9669efb3f09b0a5acc43f6c.png

可以看到是没问题的。

包别名

如果你引入不同的包模块,它们的子目录都有一样的名称,这时候你可以使用包别名来区分它们。

如果你的包目录名称非常的长,你也可以使用别名来代替。

上面的项目中我已经把包名改的如此之长:

1aa34f0537b4603d859b3f24013faff9.png

下面来看一下包别名的用法:

package dao

import (
 shortUser "mod-demo/user_test_long_long_name"
)

func GetUser(uid int64) shortUser.User {
 return shortUser.GetUserInfo(uid)
}


func GetUserClient(uid int64) shortUser.UserClient {
 return shortUser.GetUserClient(uid)
}

上面示例中我们使用别名也是一样能调用。

点操作

. 导入可以让包内的方法注册到当前包的上下文中,直接调用方法名即可,不需要再加包前缀。

package dao

import (
 . "mod-demo/user_test_long_long_name"
)

func GetUser(uid int64) User {
 return GetUserInfo(uid)
}


func GetUserClientInfo(uid int64) UserClient {
 return GetUserClient(uid)
}

下划线操作

_ 是包引用操作,只会执行包下各模块中的 init 方法,并不会真正的导入包,所以不可以调用包内的其他方法。

后文我们说到模块的时候会讲 init 方法。它会在该模块被编译的时候执行。所以你可以在 init 方法中去做一些初始化的事情。

当我们在执行 go build file.go的时候,该文件引用的模块中如果有定义 init 方法,那么会在构建的时候执行该 init 方法。

先上结论:

  1. 在同一个 package 中,可以多个文件中定义 init 方法
  2. 在同一个 Go 文件中,可以重复定义 init 方法
  3. 在同一个 package 中,不同文件中的 init 方法的执行按照文件名先后执行各个文件中的 init 方法
  4. 在同一个文件中的多个 init 方法,按照在代码中编写的顺序依次执行不同的 init 方法

首先目录结构如下:

mod-demo
 user
  UserClientCache.go
  UserInfo.go
  main.go

UserClientCache.go 如下:

package user

import "fmt"

type UserClient struct {
 Id int64
 ClientId int64
 ClientType string

}

func GetUserClient(id int64) UserClient  {
 return UserClient{1, 2, "android"}
}


func init() {
 fmt.Println(UserClient{1, 2, "android"})
}

func init() {
 fmt.Println(UserClient{2, 2, "ios"})
}

UserInfo.go 如下:

package user

import "fmt"

var default_sex int32

type User struct {
 Id int64
 Name string
 Sex int32
}

func GetUserInfo(uid int64) User {
 return User{1,"xiaoming", 1}
}

func init() {
 fmt.Println(User{1,"xiaoming", 1})
}

Main.go 如下:

package main

import (
 "fmt"
 _ "mod-demo/user"
)

func main() {

 fmt.Print("main start")

}

执行 main 函数可以看到:

{1 2 android}
{2 2 ios}
{1 xiaoming 1}
main start

是按照文件排列顺序先后输出的,并且 main 函数是最后执行。

上面说到了 init 函数和 main 函数的初始化顺序,一个完整的 Go 进程启动,各个模块以及变量的初始化顺序如下:

go run *.go
├── 执行 Main 包
├── 初始化所有引用的包
|  ├── 初始化所有引用的包 (recursive definition)
|  ├── 初始化全局变量
|  └── 以词法文件名的顺序调用 init 函数
└── 初始化 Main 包
   ├── 初始化全局变量
   └── 以词法文件名的顺序调用 init 函数

程序的初始化和执行都起始于 main 包。

如果 main 包还导入了其它的包,那么就会在编译时将它们依次导入。

有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到 fmt 包,但它只会被导入一次,因为没有必要导入多次)。

当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行 init 函数(如果有的话),依次类推。

等所有被导入的包都加载完毕了,就会开始对 main 包中的包级常量和变量进行初始化,然后执行 main 包中的 init 函数(如果存在的话),最后执行 main 函数。

Go 常用命令介绍

直接在终端中输入 go help 即可显示所有的 Go 命令以及相应命令功能简介,主要有下面这些:

  • build:编译包和依赖
  • clean:移除对象文件
  • doc:显示包或者符号的文档
  • env:打印 go 的环境信息
  • bug:启动错误报告
  • fix:运行 go tool fix
  • fmt:运行 gofmt 进行格式化
  • generate:从 processing source 生成 go 文件
  • get:下载并安装包和依赖
  • install:编译并安装包和依赖
  • list:列出包
  • run:编译并运行 go 程序
  • test:运行测试
  • tool:运行 go 提供的工具
  • version:显示 go 的版本
  • vet:运行 go tool vet

命令的使用方式为: go command [args], 除此之外,可以使用go help 来显示指定命令的更多帮助信息。

build 和 run 命令

就像其他静态类型语言一样,要执行 Go 程序,需要先编译,然后在执行产生的可执行文件。go build 命令就是用来编译 Go 程序生成可执行文件的。但并不是所有的 Go 程序都可以编译生成可执行文件的, 要生成可执行文件,Go 程序需要满足两个条件:

  • 该 Go 程序需要属于 main 包
  • 在 main 包中必须还得包含 main 函数

也就是说 Go 程序的入口就是 main.main, 即 main 包下的 main 函数,如下:

package main

import "fmt"

func main() {
    fmt.Println("Hello World!")
}

编译 hello.go,然后运行可执行程序:

$ go build hello.go   # 将会生成可执行文件 hello
$ ./hello           # 运行可执行文件
Hello World!

上面就是 go build 的基本用法,另外如果使用 go build 编译的不是一个可执行程序,而是一个包,那么将不会生成可执行文件。

go run 命令可以将上面两步并为一步执行(不会产生中间文件)。

$ go run hello.go
Hello World!

上面两个命令都是在开发中非常常用的。

此外 go clean 命令,可以用于将清除产生的可执行程序:

$ go clean    # 不加参数,可以删除当前目录下的所有可执行文件
$ go clean sourcefile.go  # 会删除对应的可执行文件
install 命令

用来编译和安装 Go 程序,我们可以将它与 build 命令对比:

installbuild
生成的可执行文件路径工作目录下的 bin 目录下当前目录下
可执行文件的名字与源码所在目录同名默认与源程序同名,可以使用-o 选项指定
依赖将依赖的包放到工作目录下的 pkg 文件夹下-
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值