Golang筆記

一、基本介紹

1、開發環境安裝-windows安裝

打開Golang官網,選擇對應版本,進行安裝。

2、環境變數配置

1)步驟

(1)首先在環境變數中添加 GOPATH,值為 go 的安裝目錄:

(2)然後在環境變數 PATH 中添加 go 安裝目錄下的 bin 文件夾。

(3)接著添加一個環境變數 GOPATH,值為你自己希望的工作目錄。

(4)最後重啟一下 命令行工具,輸入 go version命令即可查看版本資訊

2)GOROOT

$GOROOT,便是 Go 的安裝路徑,存放 Go 的內置程式庫。通常你安裝完後,你電腦的環境變數就會設好 GOROOT 路徑。當你開發 Go 程式的時候,當你 import 內置程式庫的時候,並不需要額外安裝,而當程式運行後, 默認也會先去 GOROOT 路徑下尋找相對應的庫來運行。

3)GOPATH與Go工作區

GOPATH 是我們定義的自己的工作空間。

一個 GOPATH 工作區,一般這樣:

./
├── bin
├── pkg
└── src
    ├── hello_github
    └── hello_router.go

(1)bin:保存編譯後生成的可執行文件。我們的作業系統使用$PATH環境變數來查找無需完整路徑即可執行的二進位應用程式,建議將此目錄:$GOPATH/bin添加到我們的全局 $PATH 變數中。

(2)pkg:它保存已安裝的包對象(比如:.a)。每個目標作業系統和體系結構對都有自己的 pkg 子目錄。 Go 編譯包時生成的中間文件,用來快取提高編譯效率。

(3)src:包含源程式碼(比如:.go .c .h .s等)。 該路徑決定 import 包時的導入路徑或可執行文件名稱。

import包的搜索順序:
GOROOT/src:該目錄保存了Go標準庫程式碼。
GOPATH/src:該目錄保存了應用自身的程式碼和第三方依賴的程式碼。

3、Go程式開發注意事項

1)Go源文件以「go」為擴展名

2)Go應用程式的執行入口是main()方法

3)Go語言嚴格區分大小寫

4)Go方法由一條條語句構成,每個語句後不需要分號(Go語言會在每行後自動加分號)

5)Go編譯器是一行行進行編譯的,因此我們一行就寫一條語句,不能把多餘語句寫在同一行,否則會報錯。

6)Go語言定義的變數或者import的包如果沒有使用到,程式碼不能編譯通過。

7)大括弧都是成對出現的,缺一不可。

4、常用的轉義字元(escape char)

1)\t:一個製表位
2)\n:換行符
3)\\:一個\
4)\”:一個」
5)\r:一個回車

5、Go變數

1)使用基本步驟

(1)聲明變數(定義變數)

(2)賦值

(3)使用

2)Golang變數聲明和賦值的三種方式

(1)指定變數類型,聲明後若不賦值,使用默認值

//聲明同時賦值
var 變數名字 類型 = 表達式
//聲明時不賦值
var 變數名字 類型 

(2)根據值自行判定變數類型(類型推導)

var 變數名字 = 表達式

(3)省略var,注意:=左側的變數不應該是一級聲明過的,否則會導致編譯錯誤

變數名字 := 表達式

3)多變數聲明

//可以聲明時賦值,也可以不賦值
var 變數名字1,變數名字2 類型
////聲明時需賦值
var 變數名字1,變數名字2 =值1,值2
//類型推導,需要賦值
變數名字1,變數名字2 :=值1,值2

6、數據類型

 1)基本數據類型:變數存的就是值,也叫值類型。

(1)數值型

整數類型、浮點類型

(2)字元型

使用byte來保存單個字元字元

(3)布爾型(bool)

(4)字元串(string):官方將string歸屬到基本數據類型

2)派生數據類型

(1)指針(Pointer)-引用類型

指針類型:變數存的是一個地址,這個地址指向的空間存的才是值。獲取指針類型所指向的值,使用:*。

package main

import "fmt"

func main() {
	i := 10
	var p *int = &i
	fmt.Println(p, *p)
}

(2)數組-值類型

見數組章節

(3)結構體(struct)-值類型

見結構體章節

(4)管道(Channel)-引用類型

(5)函數

見函數章節

(6)切片(slice)-引用類型

見切片章節

(7)介面(interface)-引用類型

見介面章節

(8)map-引用類型

見nmap章節

 3)值類型與引用類型

(1)值類型:變數直接存儲值,記憶體通常在棧中分配。都有對應的指針類型,形式未*數據類型,比如int的對應的指針就是*int,依次類推。

(2)引用類型:變數存儲的是一個地址,這個地址對應的空間才是真正存儲數據(值),記憶體通常在堆上分配,當沒有任何變數引用這個地址時,該地址對應的數據空間就成為一個垃圾,由GC來回收。

7、函數

1)函數聲明

函數聲明包括函數名、形式參數列表、返回值列表(可省略)以及函數體。

func name(parameter-list) (result-list) {
    body
}

 形式參數列表描述了函數的參數名以及參數類型。這些參數作為局部變數,其值由參數調用者提供。返回值列表描述了函數返回值的變數名以及類型。如果函數返回一個無名變數或者沒有返回值,返回值列表的括弧是可以省略的。如果一個函數聲明不包括返回值列表,那麼函數體執行完畢後,不會返回任何值。

函數的類型被稱為函數的簽名。如果兩個函數形式參數列表和返回值列表中的變數類型一一對應,那麼這兩個函數被認為有相同的類型或簽名。形參和返回值的變數名不影響函數簽名,也不影響它們是否可以以省略參數類型的形式表示。
每一次函數調用都必須按照聲明順序為所有參數提供實參(參數值)。在函數調用時,Go語言沒有默認參數值,也沒有任何方法可以通過參數名指定形參,因此形參和返回值的變數名對於函數調用者而言沒有意義。
在函數體中,函數的形參作為局部變數,被初始化為調用者提供的值。函數的形參和有名返回值作為函數最外層的局部變數,被存儲在相同的詞法塊中。
實參通過值的方式傳遞,因此函數的形參是實參的拷貝。對形參進行修改不會影響實參。但是,如果實參包括引用類型,如指針,slice(切片)、map、function、channel等類型,實參可能會由於函數的間接引用被修改。

你可能會偶爾遇到沒有函數體的函數聲明,這表示該函數不是以Go實現的。這樣的聲明定義了函數簽名。

2)init函數

 

3)Deferred函數

 見異常處理章節

4)匿名函數

 

5)閉包

 

6)

 

8、包

 包的本質就是創建不同的文件夾來存放程式文件。每個包一般都定義了一個不同的名字空間用於它內部的每個標識符的訪問。每個名字空間關聯到一個特定的包,讓我們給類型、函數等選擇簡短明了的名字,這樣可以在使用它們的時候減少和其它部分名字的衝突。每個包還通過控制包內名字的可見性和是否導出來實現封裝特性。通過限制包成員的可見性並隱藏包API的具體實現,將允許包的維護者在不影響外部包用戶的前提下調整包的內部實現。通過限制包內變數的可見性,還可以強制用戶通過某些特定函數來訪問和更新內部變數,這樣可以保證內部變數的一致性和並發時的互斥約束。當我們修改了一個源文件,我們必須重新編譯該源文件對應的包和所有依賴該包的其他包。

1)包的三大作用

(1)區分相同名字的函數、變數等標識符

(2)當程式文件很多時,可以很好的管理項目

(3)控制函數、變數等訪問範圍,即作用域

 2)包的相關說明

(1)打包基本語法/聲明基本語法

package 包名

在每個Go語言源文件的開頭都必須有包聲明語句。包聲明語句的主要目的是確定當前包被其它包導入時默認的標識符(也稱為包名)。

(2)引入包的基本語法

import “包的路徑”

每個包是由一個全局唯一的字元串所標識的導入路徑定位。出現在import語句中的導入路徑也是字元串。(在import包時,路徑從$GOPATH的src下開始,不用帶src。

 3)包的匿名導入
如果只是導入一個包而並不使用導入的包將會導致一個編譯錯誤。但是有時候我們只是想利用導入包而產生的副作用:它會計算包級變數的初始化表達式和執行導入包的init初始化函數。這時候我們需要抑制「unused import」編譯錯誤,我們可以用下劃線_來重命名導入的包。像往常一樣,下劃線_為空白標識符,並不能被訪問。
import _ "image/png" // register PNG decoder
這個被稱為包的匿名導入。它通常是用來實現一個編譯時機制,然後通過在main主程式入口選擇性地導入附加的包。

9、數組

數組是多個相同類型數據的組合,一個數組一旦聲明/定義了,其長度是固定的,不能動態變化。

1)基本介紹(定義和初始化)

定義:var  數組名 [數組大小]數據類型

以下是四種數組初始化方式:

默認情況下,數組的每個元素都被初始化為元素類型對應的零值,對於數字類型來說就是0。我們也可以使用數組字面值語法用一組值來初始化數組

var q [3]int = [3]int{1, 2, 3}

var r  = [3]int{1, 2}
p  := [3]int{1, 2}
fmt.Println(r[2]) // "0"

在數組字面值中,如果在數組的長度位置出現的是「…」省略號,則表示數組的長度是根據初始化值的個數來計算。因此,上面q數組的定義可以簡化為:

b := [...]int{1, 2, 3}
var c = [...]int{1, 2, 3}
fmt.Println(b[2])
fmt.Println(c[2])

 可以指定元素值對應的下標:

var names=[3]string{1:"tom",0:"jack",2:"marry"}

數組的長度是數組類型的一個組成部分,因此[3]int和[4]int是兩種不同的數組類型。數組的長度必須是常量表達式,因為數組的長度需要在編譯階段確定。

2)數組遍歷

 (1)常規遍歷

package main

import "fmt"

func main() {
	var score [5]float64 = [5]float64{1.0, 2.0, 3.0, 4.0, 5.0}
	for i := 0; i < len(score); i++ {
		fmt.Println(score[i])
	}
}

 (2)for-range結構遍歷

 這個是go語言一種獨有的結構,可以用來遍歷訪問數組的元素,

基本語法:for index,value:=range array01{}

第一個返回值index是數組的下標,第二個value是在該下標位置的值,它們都是僅在for循環內部可見的局部變數,遍曆數組元素的時候如果不想使用下標index,可以直接把下標index標記為下劃線_,index和value的名稱是不固定的,也可以自行指定。

package main

import "fmt"

func main() {
	var score [5]float64 = [5]float64{1.0, 2.0, 3.0, 4.0, 5.0}
	for index, value := range score {
		fmt.Println(index, value)
	}
}

10、切片

1)基本介紹

Slice(切片)代表變長的序列,序列中每個元素都有相同的類型。一個slice類型一般寫作[]T,其中T代表slice中元素的類型;slice的語法和數組很像,只是沒有固定長度而已。

數組和slice之間有著緊密的聯繫。一個slice是一個輕量級的數據結構,提供了訪問數組子序列(或者全部)元素的功能,而且slice的底層確實引用一個數組對象。一個slice由三個部分構成:指針、長度和容量。指針指向第一個slice元素對應的底層數組元素的地址,要注意的是slice的第一個元素並不一定就是數組的第一個元素。長度對應slice中元素的數目;長度不能超過容量,容量一般是從slice的開始位置到底層數據的結尾位置。內置的len和cap函數分別返回slice的長度和容量。

2)切片定義的基本語法:

var 變數名 []類型

例如:

package main

func main() {
	var intArr [5]int = [...]int{1, 2, 3, 4, 5}
	myslice:=[]intArr[1:4]
}

 3)切片的使用

(1)方式一:定義一個切片,然後讓切片去引用一個已經創建好的數組。

(2)方式二:通過make來創建切片。

基本語法:var 切片名 []type=make([]type,len,[capacity])
參數說明:type:切片的類型,len:長度,capacity:容量(可選的)。

package main

import "fmt"

func main() {
	var myslice []int = make([]int, 4)
	myslice[0] = 100
	fmt.Println(myslice)
}

 (3)方式三:

 

 

11、map

 在Go語言中,一個map就是一個哈希表的引用,map類型可以寫為map[K]V,其中K和V分別對應key和value。map中所有的key都有相同的類型,所有的value也有著相同的類型,但是key和value之間可以是不同的數據類型。其中K對應的key必須是支援==比較運算符的數據類型,所以map可以通過測試key是否相等來判斷是否已經存在。雖然浮點數類型也是支援相等運算符比較的,但是將浮點數用做key類型則是一個壞的想法,最壞的情況是可能出現的NaN和任何浮點數都不相等。對於V對應的value數據類型則沒有任何的限制。

內置的make函數可以創建一個map:

ages := make(map[string]int) // mapping from strings to ints

 我們也可以用map字面值的語法創建map,同時還可以指定一些最初的key/value:

ages := map[string]int{
    "alice":   31,
    "charlie": 34,
}

 這相當於:

ages := make(map[string]int)
ages["alice"] = 31
ages["charlie"] = 34

 因此,另一種創建空的map的表達式是map[string]int{}

使用內置的delete函數可以刪除元素:

delete(ages, "alice") // remove element ages["alice"]

 

 

12、結構體

 結構體是一種聚合的數據類型,是由零個或多個任意類型的值聚合成的實體。每個值稱為結構體的成員。用結構體的經典案例是處理公司的員工資訊,每個員工資訊包含一個唯一的員工編號、員工的名字、家庭住址、出生日期、工作崗位、薪資、上級領導等等。所有的這些資訊都需要綁定到一個實體中,可以作為一個整體單元被複制,作為函數的參數或返回值,或者是被存儲到數組中,等等。

下面兩個語句聲明了一個叫Employee的命名的結構體類型,並且聲明了一個Employee類型的變數dilbert:

type Employee struct {
    ID        int
    Name      string
    Address   string
    DoB       time.Time
    Position  string
    Salary    int
    ManagerID int
}

var dilbert Employee

賦值:

type Point struct{ X, Y int }

p := Point{1, 2}
var q Point = Point{12, 12}

13、方法

 在函數聲明時,在其名字之前放上一個變數,即是一個方法。這個附加的參數會將該函數附加到這種類型上,即相當於為這種類型定義了一個獨佔的方法。

 

package main

import (
	"fmt"
	"math"
)

type Point struct{ x, y float64 }

//func
func Distance(p, q Point) float64 {
	return math.Hypot(q.x-p.x, q.y-p.y)
}

//method
func (p Point) Distance(q Point) float64 {
	return math.Hypot((q.x - p.x), q.y-p.y)
}

func main() {
	var p Point = Point{12, 12}
	var q Point = Point{8, 8}
	x := Distance(p, q)
	//function call
	fmt.Println(x)
	//method call
	y := p.Distance(q)
	fmt.Println(y)
}

 上面的程式碼里那個附加的參數p,叫做方法的接收器(receiver),早期的面向對象語言留下的遺產將調用一個方法稱為「向一個對象發送消息」。在Go語言中,我們並不會像其它語言那樣用this或者self作為接收器;我們可以任意的選擇接收器的名字。由於接收器的名字經常會被使用到,所以保持其在方法間傳遞時的一致性和簡短性是不錯的主意。這裡的建議是可以使用其類型的第一個字母,比如這裡使用了Point的首字母p。

在方法調用過程中,接收器參數一般會在方法名之前出現。這和方法聲明是一樣的,都是接收器參數在方法名字之前。

 

14、介面

 

15、錯誤處理機制

go中引入的處理方式為:defer、panic、recover
GO中可以先拋出一個panic的異常,然後再defer中通過recover捕獲這個異常,然後正常處理。
1)基本介紹
(1)defer
你只需要在調用普通函數或方法前加上關鍵字defer,就完成了defer所需要的語法。當執行到該條語句時,函數和參數表達式得到計算,但直到包含該defer語句的函數執行完畢時,defer後的函數才會被執行,不論包含defer語句的函數是通過return正常結束,還是由於panic導致的異常結束。你可以在一個函數中執行多條defer語句,它們的執行順序與聲明順序相反。
(2)panic
一般而言,當panic異常發生時,程式會中斷運行,並立即執行在該goroutine(可以先理解成執行緒,在第8章會詳細介紹)中被延遲的函數(defer 機制)。隨後,程式崩潰並輸出日誌資訊。日誌資訊包括panic value和函數調用的堆棧跟蹤資訊。panic value通常是某種錯誤資訊。對於每個goroutine,日誌資訊中都會有與之相對的,發生panic時的函數調用堆棧跟蹤資訊。
(3)recover
內置函數,可以捕獲到異常
2)錯誤處理的好處
進行錯誤處理後,程式不會輕易掛掉。如果加入預警程式碼,程式會更加的健壯。
3)示例
package main

import "fmt"

func test() {
	//使用defer +recover來捕獲和處理異常
	defer func() {
		err := recover()
		if err != nil { //說明捕獲到異常
			fmt.Println("err:", err)
		}
	}()

	num1 := 10
	num2 := 0
	res := num1 / num2
	fmt.Println("rest=", res)
}
func main() {
	test()
	fmt.Println("main()...")
}

4)自定義錯誤

使用errors.New和panic內置函數

(1)errors.New(“錯誤說明”),會返回一個error類型的值,表示一個錯誤。

(2)panic內置函數,接收一個interface{}類型的值(也就是任何值)作為參數。可以接收error類型的變數,輸出錯誤資訊,並退出程式。

(3)示例

package main

import (
	"errors"
	"fmt"
)

func readConf(name string) (err error) {
	if name == "config.ini" {
		return nil
	} else {
		return errors.New("讀取文件錯誤...")
	}
}

func test() {
	err := readConf("config.in1i")
	if err != nil {
		panic(err)
	}
	fmt.Println("test()...")
}
func main() {
	test()
}