1. 程式人生 > >Golang基本程式結構

Golang基本程式結構

今天的文章,我們來了解一下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語句範圍。