GO學習筆記——包和封裝(17)

既然GO支持封裝,那麼相比於C++的三大訪問屬性:private,protected,public,GO只有private和public,因爲protected是因繼承而產生的。而C++的訪問屬性是根據類來說的,在一個類中相當於外界的屬性;但是GO中沒有類,GO中的訪問屬性是相對於包來說的,這一章就來講一講GO中的包,以及包是怎麼做到封裝的


包其實相當於一個目錄,如果我們把所有的源文件寫在一個文件中,那麼會不方便我們管理。這其實也是,在C++中我們也分頭文件和源文件,而且會有多個源文件。

包用於組織GO源代碼,提供了對代碼更好的管理與重用性。

總之,這麼理解,把一些功能相近的源文件封裝到一個包中,那麼這個包其實就是這些文件的一個目錄


一個目錄只能有一個包

因爲上面的理解,包其實就是一個目錄,那麼GO就規定了一個目錄就只能有一個包,如果有兩個包就會報錯,下面來看一下。
我新建了一個目錄叫newpackage,在這個目錄下創建了一個test1文件,那麼我的GoLand會自動幫我給我的這個文件添加這麼一句話

package newpackage

意思是,我這個test1文件是封裝在newpackage這個包中的。這邊包名默認就是這個目錄名。但其實包名不一定要和目錄名一樣,可以改,只要在同一個目錄下只有一個包名就可以了(但是默認約定包名和目錄名相同)。

我再創建了一個test2文件,也自動添加了上面那句話。但是我把包名改成了newpackage1,這個是編譯器就報錯了。

表示在同一個目錄中有多個包。

所以包就是一個目錄,一個目錄就是一個包,包名可以不和目錄名相同,但是一個目錄下只能有一個包。 

 

main包包含可執行程序入口

要生成Go語言可執行程序,必須要有main的package包,且必須在該包下有main函數

就像C++要有main函數一樣,GO也要有main函數,像之前的文章的測試代碼都有一個main函數,且都沒有創建別的包,都是在main包中測試的。

所以一個GO程序如果需要執行:

  • 必須要有main包(包名必須是main,不可以是別的名)
  • main包內必須要有main入口函數
  • 如果main包中沒有入口函數不能執行,如果只是別的名的包中有入口函數也不能執行

 

爲結構體定義的方法必須在一個包內

GO的面向對象使用結構體來做的,對一個結構體可以定義很多方法。

因爲需要封裝的原因,所以一個結構體中的方法必須在一個包內定義。也就是說,如果結構體定義在了包A中,那麼它對應的方法也必須都定義在包A中,不可以在別的包中定義,這樣就破壞了封裝的概念。但是可以是不同的文件,即可以在同一個包中的不同源文件中定義結構體的方法。

所以,爲了保證封裝,爲結構體定義的方法必須在一個包內。

 

創建自定義的包

下面我們把之前一章的測試用例,封裝成一個List包,來創建一個我們自定義的包。

先來看一下目錄結構

這邊將定義的List放到了一個List包的一個源文件list.go中

package List

import "fmt"

//定義一個單鏈表的節點
type Node struct {
	Next *Node
	Val int
}

//定義一個方法來遍歷這個單鏈表
func (node *Node) PrintList(){
	for node !=  nil{
		fmt.Print(node.Val)
		if node.Next != nil {
			fmt.Print("->")
		}
		node = node.Next
	}
}

爲了對別的包可見,因此我們將變量名改爲了首字母大寫,GO語言變量名首字母大寫表示對別的包可見(public),首字母小寫表示對別的包不可見(private)

剩下的main.go中,import了我們自定義的這個包,且它裏面只有一個main函數

package main

import "struct/List"

func main() {
	root := List.Node{Val:1}
	root.Next = &List.Node{Val:2}
	root.Next.Next = &List.Node{Val:3}
	root.Next.Next.Next = &List.Node{Val:4}
	root.PrintList()    //使用該方法
}

執行程序

1->2->3->4
Process finished with exit code 0

這樣,我們就把定義的List放到了一個包中,更方便我們管理我們的源文件。

 

我們要導入一個別的包都要用到import關鍵字,下面來說說導入包時的技巧以及import關鍵字的使用。


init函數

說import之前先說一下init函數。每個包都可以包含一個init函數,這其實就是一個初始化函數,在調用這個包之前,先做一些對其的初始化操作,這樣在我們使用這個包之前,一些必要的init操作就已經被做完了,不用我們再手動去執行一遍。

init 函數不應該有任何返回值類型和參數,在我們的代碼中也不能顯式地調用它。

我們爲上面的List包建一個init函數。

package List

import "fmt"

//加入的init函數
func init(){
	fmt.Println("before use package List")
}

//定義一個單鏈表的節點
type Node struct {
	Next *Node
	Val int
}

//定義一個方法來遍歷這個單鏈表
func (node *Node) PrintList(){
	for node !=  nil{
		fmt.Print(node.Val)
		if node.Next != nil {
			fmt.Print("->")
		}
		node = node.Next
	}
}

執行結果

before use package List
1->2->3->4

可以看到這裏也執行了init函數,並且在程序main函數執行之前執行了,其實它就是在導入這個包時就已經自動執行了。

 

import導入包初始化順序

這邊再說一下import導入其他包時的一些初始化順序:

  1. 如果一個main包裏面導入其他包,其他的包將被順序導入(按聲明的順序導入)
  2. 如果導入的包中有依賴包(導入包A依賴包B),會首先導入B包,然後初始化B包中的常量和變量,最後如果B包中有init,會自動執行init()(也就是說,會先初始化B包中的一些函數體外的常量和變量,再執行init函數
  3. 所有包導入完成後纔會對main中常量和變量進行初始化,然後執行main中的init函數(如果有的話),最後執行main函數(所有包都是先執行init函數)
  4. 如果一個包被導入多次,則該包只會被導入一次(就像C++裏的pragma once),不會被重複導入

 

import特殊用法

1.別名,將導入的包名命名爲另一個容易記的別名

有時候當包名很長時我們可以對該包起一個簡單容易記的別名,這裏我們對上述main包中導入的List包進行一個重命名做測試

package main

import A "struct/List"    //起別名A

func main() {
	root := List.Node{Val:1}
	root.Next = &List.Node{Val:2}
	root.Next.Next = &List.Node{Val:3}
	root.Next.Next.Next = &List.Node{Val:4}
	root.PrintList()    //使用該方法
}

這裏我們起了別名A,但是上述代碼程序會出錯

.\main.go:4:10: undefined: List

所以一旦起了別名,原來的名字就不可以用了,必須要全部變爲別名。

package main

import  A "struct/List"

func main() {
	root := A.Node{Val:1}
	root.Next = &A.Node{Val:2}
	root.Next.Next = &A.Node{Val:3}
	root.Next.Next.Next = &A.Node{Val:4}
	root.PrintList()    //使用該方法
}

上面這樣就可以了。

 

2.點(.)操作

點(.)標識的包導入後,調用該包中的函數時可以省略前綴包名(不建議,容易引起衝突,其實就是相當於C++中的 using namespace,使用某個命名空間),還是拿之前的程序做測試。

package main

import . "struct/List"	//在調用該包時省略包名

func main() {
	root := List.Node{Val:1}
	root.Next = &List.Node{Val:2}
	root.Next.Next = &List.Node{Val:3}
	root.Next.Next.Next = &List.Node{Val:4}
	root.PrintList()    //使用該方法
}

但是上述代碼還是會出錯

.\main.go:3:8: imported and not used: "struct/List"
.\main.go:6:10: undefined: List

所以和之前一樣,一旦一個包名被點(.)操作了,那麼原來的包名也不可以用了,用到的地方必須全部省略包名

package main

import . "struct/List"	//在調用該包時省略包名

func main() {
	root := Node{Val:1}    //在調用時,省略包名
	root.Next = &Node{Val:2}
	root.Next.Next = &Node{Val:3}
	root.Next.Next.Next = &Node{Val:4}
	root.PrintList()    //使用該方法
}

上面這樣就可以了。

 

3.下劃線(_)操作

導入了包,卻不在代碼中使用它,這在 Go 中是非法的。當這麼做時,編譯器是會報錯的。其原因是爲了避免導入過多未使用的包,從而導致編譯時間顯著增加。

然而,在程序開發的活躍階段,又常常會先導入包,而暫不使用它,所以這裏就可以使用下劃線(_)操作:

導入該包,但不導入整個包,而是執行該包中的init函數,因此無法通過包名來調用包中的其他函數。

package main

import _ "struct/List"

func main() {
	root := List.Node{Val:1}
	root.Next = &List.Node{Val:2}
	root.Next.Next = &List.Node{Val:3}
	root.Next.Next.Next = &List.Node{Val:4}
	root.PrintList()    //使用該方法
}

上述代碼會出現,因爲我們調用了該包中的內容。

.\main.go:10:10: undefined: List
package main

import _ "struct/List"

func main() {

}

這邊我們並沒有使用該包中的任何內容,程序不會編譯出錯,而會去執行該包中的init函數。

執行結果

before use package List


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章