今天的文章,我们来了解一下Golang的程序结构,只有知道Golang的组成,才能够在使用时有的放矢,选择合适的方式。
包和包的初始化
Golang中的包是按目录结构组织的,下面假设是一个GOPATH下的src中的目录结构
src
folder1
folder2
foo1.go
foo2.go
则我们的包引用路径为folder1/folder2(基于src的相对路径),注意,这里提到的是包引用路径,而不是包名,包引用路径是在代码中的import填写的字符串。folder2下面的所有.go文件,第一行都应该是
package 包名
这里需要说明一下,包名是在代码中引用包的时候使用的,一般golang的习惯是将包名命名为和.go文件所在目录名一致,所以大家常常会认为包引用路径的最后一级目录名就是包名,这实际上是不对的。
包中的每一个.go文件可以有一个init函数,该函数不能被调用和引用,当程序启动时,会按照.go文件的加载顺序自动执行该函数,而且如果仅调用一个包中的init函数而不使用其它函数或变量,可以使用类似于下面的语法,减少导入的内容
import _ 包引用路径
下面我们看一个例子,目录结构如下所示
go-study/
├── package
│ ├── folder
│ │ └── hello.go
│ └── main.go
hello.go的内容:
package myPackage
import (
"fmt"
)
func init() {
fmt.Println("In init")
}
func Hello(word string) {
fmt.Println("Hello", word)
}
hello.go中函数Hello首字母大写,是一个包外可访问的函数,包的引用路径是go-study/package/folder,包名是myPackage,并不是folder
main.go使用hello.go中的Hello函数
package main
import (
"go-study/package/folder"
)
func main() {
myPackage.Hello("World")
}
运行一下看看
$ go run main.go
In init
Hello World
源码文件的组成
一个.go文件一般包含包声明,包路径引用,函数或方法等,还可以包含可选的全局常量定义,全局变量定义,interface定义,struct定义。函数或方法,全局变量,全局常量,interface和struct定义,都遵循一个原则,首字母大写的,作用域是全局的,首字母小写的,作用域为包作用域。前面的例子,我们已经看到了包和函数相关的内容,下面看看其他的内容。
定义全局常量常使用下面的方式(c1和c2为包作用域,C3作用域为全局)
const (
c1 = 0
c2 = "aaa"
C3 = 20
....
)
全局变量的定义方式就是把上面的const换成var即可,如
var (
v1 int
v2 string
V3 = 3
....
)
interface和struct的定义使用type关键字
type AInterface interface {
method1()
method2(a int)
Method3(a int) (int, error)
....
}
type AStruct struct {
v1 int
v2 string
V3 bool
...
}
这里为了演示,定义中的成员有首字母大小写的混杂,实际中,如果是包内部使用的interface和struct,可以将定义名称定为小写首字母,成员都使用小写首字母,如果是全局作用域,则都使用大写首字母,如果出现混杂的情况,大家可以考虑一下,是否自己的类设计出现了问题。
对于函数或方法内的局部变量,还有一种短变量声明方式,可以将变量的定义和赋初值在一步完成,如局部变量a之前没有定义过,我们可以这样写
a := 1
定义变量a,自动推断类型为int,赋初始值为1,也可以使用多变量的短变量声明
a, b := 1, 2
但是必须要求a和b至少有一个之前没有定义过,否则编译无法通过。
变量的生命周期
包级别或全局的变量,生命周期是整个程序存活时间。局部变量的生命周期比较复杂,但总的原则是当变量不再被引用时,生命周期会结束,比如下面的函数
func f() {
a := 1
fmt.Println(a)
}
当函数执行结束后,a的生命周期就会终结,下面的例子,变量发生了逃逸
func f() *int {
a := 1
return &a
}
在其他地方调用f函数
b := f()
fmt.Println(*b)
b通过指针引用着a局部变量的内存,a原本的内存空间在栈上,但是由于b的引用,需要延长生命周期,会在堆上分配空间,拷贝内容到新的内存,之后将堆上的地址赋值给b,但是由于每一次变量逃逸都要经过一次内存由栈到堆的重新分配以及数据的拷贝,性能会受到影响,所以要尽可能避免逃逸的发生。
对于if语句和for语句,如下所示
if err := f(); err != nil {
}
for i := 0; i < 10; i++ {
}
err和i如果不发生逃逸,生命周期仅限于if语句和for语句范围。