重學 Golang

 

前言

這兩個月看的golang的教程也不少。但是總感覺自己的基礎知識不紮實。本篇爲看完一本書記載的。後續會不斷補充。

本篇博客不會記錄太基礎的知識。如果需要基礎知識,教程有的是。本篇記錄一下一些重要的用法和自己的理解。本篇也不會寫的很深因爲我golang水平並不高。只是作爲一個入門筆記。

在本篇之前可以學習下基礎教程:https://golangbot.com/ 

一 初識Go

go語言是 2006年1月2號下午3點4分5秒。這個時間創立的。所以一些時間的format函數用到了這個奇葩的時間。

go語言是由Google公司開發的一款靜態型(運行之前檢查編譯)&編譯型語言。帶有垃圾回收(很關鍵,不用我們釋放)對併發支持非常牛逼的語言。因爲go中 goroutines的存在允許以協程態運行任務。一個線程可以跑多個任務而不阻塞。從而高效的使用CPU計算資源來完成運轉。 go的goroutines與nginx這種 epoll事件驅動屬於兩種多路IO的形式。 多個goroutines用通道通信。

go語言的代碼可以直接輸出爲目標操作系統的可執行文件(windows  linux...) Go不像java使用虛擬機。go只有運行時(runtime)提供垃圾回收和goroutines 調度。go有自己的鏈接器,不依賴操作系統。因此使用Go寫的代碼,編譯成可執行文件。直接運行就成爲一個進程了。

go 使用併發編譯。所以說編譯也很快的!

go棄了  i++   ++i 這個對於編程語言初學者的來說不太好理解的東西。比如java裏面 a = i ++   a=++i  a是兩個值。

但是在go裏面 i++這種東西變成了一個語句。也就是  a:=i++ 會編譯報錯。

寫Go就像java似的,先在電腦上面搞一個SDK。

例如 Windows版的。go1.12.1.windows.amd64.msi 代表Windows 64位CPU  默認goroot是在c://go下、

二 語法&&流程控制

Go聲明變量跟java不太一樣。go將類型放在變量的後面。 

吐槽一下   type  s  *[]*Hello   這玩楞你看着不懵?  這種類型寫法真噁心。    

也可以在函數中 a:=1這麼賦值。不過 := 這個符號只能是左邊變量沒有全部被初始化(聲明)過纔可以、

var  a  int  聲明之後變量有一個初始值。切片,函數,map,channel,struct,指針變量的初始化爲nil,數字型(整,浮點)爲0

go實現兩個變量的換值也是非常方便,不需要再使用臨時變量了。

a,b:=10,9
fmt.Println(a,b)  //10  9
a,b=b,a
fmt.Println(a,b)  //9  10

go中有匿名變量這個操作(啞元變量)  a,_ :=GetElements(); 假設這個函數返回兩個值。  第二個值我們不需要。那麼用_在對應位置替換,就不會分配命名空間和內存了。是一個不錯的特性。

go是一個比java還強類型的語言(我認爲)。go中的 int64  int32 uint32 這種被認爲是三個數據類型。

[3]int   []int  [4]int這也被認爲是三個數據類型。-_-切片和數組。任何不一樣大小的數組就被認爲是多個類型。

我們不用聲明具體是多少位的,go會根據編譯時的操作系統來給變量分配是多少位的來滿足性能。

go的強制類型轉換是 int(field) 這樣  java是 (int)  對於整型強轉換要小心位數不夠導致數值不對!!

c:=10.02                                -----go
fmt.Println(int(c))   //10
double v = 10.02;                       -----java
System.out.println((int)v);

Go中的字符串處理採用uint8也就是byte  和 rune也就是int32 兩種。第一種是ascll碼。第二種是UTF-8編碼。

Unicode  ASCLL都是字符集。字符集爲每個字符分配一個唯一ID UTF-8是一種編碼規則。比如幾個字節認爲是一個字(漢字)UTF-8是變長的。1-4個字節不等來編碼一個字、

Go中也是有指針的。想當初我學C的時候,指針真的是聽挺難理解的概念。  * &
var  a int      &a就獲取到a變量的內存地址了。指針的值實際上就是內存地址的值 32/64平臺對應位數的地址。

b:=&a     b此時就可以理解爲是一個指針了(內存地址)    *b就是對內存地址的取值。  *&是互補的

&獲取內存地址   *獲取內存地址存儲的值。 go中有個方法爲創建對應類型的指針 new(Type)

程序的內存分配離不開棧和堆。在java中是這樣。在go中依然是這樣的。

對於棧來說。下面程序label處會進行棧內存分配。stack函數退出即釋放內存地址。棧內存比較快,但是比較少。

func stack() (d int){
   var a int    //label
   a = a + 1
   var c int    //label
   d = c + b
   return 
}

對於堆來說。可以分配大量的內存。速度較棧慢、對於堆的內存是由垃圾回收器進行回收的。

在內存分配時會出現變量逃逸這個變量分配方式。決定在內存分配時是使用棧還是堆。這是屬於編譯器層面的東西。

變量是否被取地址||變量是否發生逃逸是分配在堆還是棧的判斷條件。

取長度的len()函數  這個字符串默認取的是ASCLL碼。所以漢字的話長度就不一定是多少了。每個中文佔3個字節。

i, j := len("我的媽呀"), len("asdf")
fmt.Println(i, "---", j) //12 --- 4

此時len肯定不行啊,這跟我們想要的4不一樣。所以我們使用其他方法來獲取漢字字符串的長度。如下獲取和遍歷

n := utf8.RuneCountInString("這個還行asd")
fmt.Println(n)    //7
str:="哈哈哈asdf"
for _,s:=range str{
   fmt.Printf("%c \n",s)
}

或者使用 []rune數組轉一下字符串先、

i, j := len([]rune("我的媽呀")), len("asdf")

對於strings.Index等方法採用的是len的計算長度方式,所以中文算3個字節。

Go中的字符串也是不可變的,跟java一樣。不可變就表明線程安全無需加鎖。不需要寫時複製。hash值也是一份。

如下代碼,我們是通過修改[]byte這個切片來修改字符串的。

a:="hello world"
b:=[]rune(a)                   //可以用[]rune  也可以用 []byte   rune中文不會亂碼 
fmt.Println(string(b),"--",&b)
for i:=5;i<10;i++{
   b[i] = ' ' 
}
fmt.Println(string(b),"--",&b)

字符串中類似java中stringBuilder玩法。我們知道java中String做拼接會生成很多對象,多個+操作非常耗時、所以引入stringbuilder和stringBuffer   builder只能在線程安全的環境下使用。其本身是線程不安全的。

go中也是有類似這個stringBuilder的東西來進行高效的字符串連接的。  bytes.Buffer是線程不安全的。沒有鎖、

var stringBuilder bytes.Buffer        //聲明字節緩衝
stringBuilder.WriteString("哈嘍")   //把字符串寫入緩衝區
stringBuilder.WriteString("沃德")   
fmt.Println(stringBuilder.String())   //將緩衝以字符串形式輸出

go的特殊常量 iota 

這個常量在遇到 const 的時候就會被重置爲0 然後在const的()內遞增。只要遇到const就是0起始、

例如如下代碼 name = 0  a= 0 b=1 c=2

const name  = iota
const (
   a = iota
   b
   c
)
在定義常量組時,如果不提供初始值,則表示將使用上行的表達式。

即下面 i = 1<<0  j = 3<<1 k = 3 <<2 j = 3<<3 左移就是擴大2倍。移位操作。所以 i = 1     j = 6     k = 12     j = 24    k使用j的表達式,l使用k的表達式。

const (
   i = 1 << iota //1左移itoa
   j = 3 << iota //3左移itoa
   k             //3左移itoa
   l
)
func main() {
   //i= 1  j= 6  k= 12  l= 24
   fmt.Println("i=", i," j=", j," k=", k, " l=", l)
}

類型別名

如下所示   帶等號 = 的那個是設置的別名。在代碼的時候會有感覺,但是編譯後跟int就是一個東西。

type coin int              //類型定義
type intAlias = int        //類型別名
func main() {
   var a coin
   fmt.Printf("%T \n",a)  //main.coin 
   var b intAlias
   fmt.Printf("%T",b)     //int
}

類型定義是定義一個完全新的類型。享有原類型的方法和結構。

類型別名跟原類型是一個東西。在編譯後會變成原類型。

對於使用別名的類型,不能在本地添加成員方法,因爲不是一個包。

對於一些流程控制的使用

if err:=connect();err!=nil {    //執行了connect函數。函數出錯就輸出一下日誌。 控制了變量err的作用域
   doSomething
}

還有一個for循環非常好用的 range 可以作用於 數組,切片,字符串,map,channel 獲取索引&值

arr:=[]int{1,2,3,4}
for i,v:=range arr{
   fmt.Printf("index %d,val %v \n",i,v)
}

對於switch 語句go語言默認每個case 都有break 如果想順延需要加 fallthrough

goto這個東西規範都是禁止的。其他流程意義類似java

三 存儲

這小節記錄一下容器。java中的collection這種東西。

go有數組和切片的概念。區別就是數組的長度是固定的,切片的長度理解爲動態的。

切片的底層實現是一個指向數組的指針,一個len  一個cap屬性。

切片的截取採用 a[1:3] 這樣。  1是開始元素下標,3是截止元素下標。左閉右開區間。 a[1:]1往後都要。

可以用make([]int, 2, 10 )創建切片 參數表示:  type,len,cap 被make的切片是被直接分配內存的。

append方法是給切片追加元素,cap不夠就按原cap兩倍擴容。超過1024字節就按原cap的四分之一擴。

append有個坑就是,追加的元素沒超過原cap不會生成新的slice切片、會在原切片操作。操作不當會造成值的覆蓋

當超過原cap時進行擴充,生成新的slice  就沒事了。

要想刪除切片中的元素,就需要將該元素的前後切片append到一起。

map是有刪除函數的  delete(map,key)  go沒有直接清空一個map的方法。可以新創建一個map  沒用的map等垃圾回收就可以用了。垃圾回收效率也非常高。但是map是線程不安全的。不能同時進行讀寫操作。會拋異常。
fatal error: concurrent map read and map write

map的數據結構是哈希鏈表。使用鏈地址法。聽上去跟java一樣。但是實現不太一樣。

go中的map有  bucket  hmap等數據結構。

sync.map是支持併發讀寫的線程安全容器。如下store 存/改  load取 delete 刪    a.delete(key)

//a:=make(map[string]int)
a:=sync.Map{}
go func() {
   for{
      a.Store("a",1)
   }
}()
go func() {
   for{
      fmt.Println(a.Load("a"))
      time.Sleep(time.Second)
   }
}()
a.Range(func(key, value interface{}) bool {     //遍歷
   fmt.Println("---",key,value) 
   return true
})
wg:=&sync.WaitGroup{}
wg.Add(1)
wg.Wait()

sync.map沒有直接獲取長度的方法,只能自己遍歷++算一下。因此性能很差。畢竟加鎖,肯定相對差一點。

go中的鏈表。 container/list包中   類似  java中的  List<Object> s =new LinkedList<>();  沒有類型的約束。挺噁心的這種東西。

l := list.New()
element := l.PushBack("asd") //*Element
l.PushBack(11)
fmt.Println(l.Len())
l.Remove(element)
for i:=l.Front();i!=nil;i=i.Next(){
   fmt.Println(i.Value)
}

list不建議使用。感覺不太好。沒有泛型。

四 函數

函數可以作爲回調。

func connect ()error{
   return errors.New("asd")
}
func main() {
   var f func()error
   f=connect
   _ = f()
}

函數可以作爲值賦值給函數類型的變量。  也可以在形式參數時聲明。也可以作爲匿名函數存在、

Go 函數可以是一個閉包。閉包是一個函數值,它引用了函數體之外的變量。 這個函數可以對這個引用的變量進行訪問和賦值;換句話說這個函數被“綁定”在這個變量上。

沒有閉包的時候,函數執行完畢後就無法再更改函數中變量的值,內存被釋放掉;有了閉包後函數就成爲了一個變量的值,只要變量沒被釋放,函數就會一直處於存活並獨享的狀態,因此可以後期更改函數中變量的值。內存沒被釋放。

如下所示。label這個返回的匿名函數在main的for循環中執行多次、但是這個匿名函數中所引用的外部變量tmp3的值是有狀態的。每次的調用會使變量的值+1  直到程序死掉。而非外部變量tmp2沒有遞增輸出、

func add() func(int) int {
   sum,tmp,tmp3 := 0,0,0
   tmp++
   fmt.Println("tmp: ",tmp)
   return func(x int) int {     //label
      tmp2:=0
      tmp2++
      tmp3++
      fmt.Println("tmp2: ",tmp2,"   tmp3: ",tmp3)
      sum += x
      return sum
   }
}
func main() {
   //tmp:  1
   //tmp2:  1    tmp3:  1
   //tmp2:  1    tmp3:  2
   //tmp2:  1    tmp3:  3
   //13
   res := add()                     //res是返回的匿名函數
   for i := 1; i < 3; i++ {
      res(i)
   }
   fmt.Println(res(10))
}

go中還有類型推斷的斷言

m:=make(map[string]interface{})
m["a"]=1
for _,v:=range m{
   switch v.(type){               //注意此處的v必須是interface{}   相當於object
   case bool:
      fmt.Println("bool")        //一般類型斷言與switch一起用。
   case int:
      fmt.Println("int")
   default:
      fmt.Println("none")
   }
}

defer

defer類似java中的finally  只不過寫的隨意一點,寫哪都行。defer有一個調用棧。對於多個defer調用的函數按編碼順序入棧,所以執行的時候就是逆序執行了。因爲棧的後進先出特性。

error

一個可能造成錯誤的函數,需要返回值中返回一個錯誤接口,如果調用是成功的,錯誤接口將返回nil 否則返回錯誤、

這個是一個非常好的函數設計理念。

通過errors.New("")返回一個error。 但是在一些時候我們需要自定義error-----重寫Error方法就行了。

對於panic 和 recover 的玩法。

程序不行的時候觸發panic  如果沒在defer中發現recover函數就會宕機。

如果在觸發panic的函數中的defer中(無論回調還是什麼,只要在一個函數中) 使用了recover函數 就不會宕機,繼續執行defer的內容,執行完畢後,退出宕機點的函數,繼續執行。

五 結構體

結構體是一個比類更有擴展的東西。

使用new  & 構造的類型實例的類型是類型的指針。

new就不作示範了。源碼返回的就是  *Type   下面這個用&構造的結構體實例是指針類型、

type hellos struct {
   name,describe string
}
func main() {
   h:=&hellos{describe:"hello"}
   (*h).name="asd"   //等價於 h.name  語法糖可以省略額外部分
   fmt.Println(h,reflect.TypeOf(h)) //&{asd hello} *main.hellos
}

當結構體實例化時纔會被分配內存。除了上面的方式外還有一種初始化方式,按順序寫變量。不建議用這種,不清晰。

對於go的繼承玩法,就只能寫成結構體嵌套。

在java中我們給類寫實例方法和靜態方法。 在go中有接收器這一個概念。相當於this  接收器分爲值接收和指針接收。

官方建議將接收器的變量命名爲類型的第一個小寫字母。   (p  *Pointer)

指針接收器和值接收器的區別如下

指針接收器的成員變量在方法中的修改,在方法結束後依然有效。

而值接收器在方法中對成員變量的修改在方法外不可見。

在最開始我認爲值接收器就是對值的複製從而存在兩個變量兩個內存地址,原值沒法感受到改變,指針則是把內存地址給共享了,然後改了就能看見。直到我做了一個測試發現不是這樣的。

type hello struct {
   name,describe string
}
func (h hello) setName(name string) {
   fmt.Println("hello.setName: ",unsafe.Pointer(&h))//hello.setName:  0xc000048460
   h.name = name
}
func (h *hello) setDes(des string) {
   fmt.Println("hello.setDes: ",unsafe.Pointer(&h))//hello.setDes:  0xc00007e020
   h.describe = des
}
func main() {
   h:=&hello{name:"default"}
   fmt.Println(reflect.TypeOf(h))  //*main.hello
   fmt.Println("main.h: ",unsafe.Pointer(h))//main.h:  0xc000048420
   h.setName("newName")
   h.setDes("newDes")
   fmt.Printf("h.name: %s,h.describe: %s",h.name,h.describe)//h.name: default,h.describe: newDes
}

對於值接收器就是值被複制一份。如果值很大,那麼會造成很大的內存開銷。對於指針接收器則是指針被複制一份。傳給方法一個指針這個指針存儲的是原數據的指針。並不是直接像java那樣直接的共享內存地址。

接收器和參數傳遞的區別就是接收器方法屬於某個結構體!

public class GoTest {
    public static void hello(GoTest goTest){
        System.out.println(goTest);//zy.service.effective.eight.GoTest@3f99bd52
    }
    public static void main(String[] args) {
        GoTest goTest = new GoTest();
        System.out.println(goTest);//zy.service.effective.eight.GoTest@3f99bd52
        hello(goTest);
    }
}

不光結構體可以有他的方法。類型也可以。

type str string
func(s str)say(){
   fmt.Println("say")
}
func main() {
   var s str = "sss"
   s.say()
}

結構體可以類型內嵌和結構體內嵌。就是不寫變量名。及其噁心。不明白這麼定義語法的意義在哪、亂七八糟的。一點也不清晰。強烈建議不要這麼寫。

六 接口

又可以吐槽了。go中的接口滿足兩個條件就算。 

1.一個結構體的函數名返回值參數與接口中定義的一樣

2.接口中所有定義的函數,在結構體中都有對應的函數。     

滿足上述這兩條就算結構體實現了這個接口。可以看到沒有像java這樣implements 關鍵字說明實現關係、

那麼問題來了。寫了很多代碼之後。鬼知道你實現啥接口了????????

接口也可以嵌套。結果你懂的。就是全都得實現,更難看。

接口的玩法跟java差不多。都是表示行爲用的。

七 包

這個跟java類似。就是main方法必須main包才能run起來package   main

關於gopath和go modules包管理總結在這。https://blog.csdn.net/finalheart/article/details/103229436

照目前趨勢用go  modules就可以了。gopath缺點太多了。

別的沒啥說的,這個init函數需要注意一下。

init函數一個go文件中可以寫多個,但是寫多個沒有任何意義,不要這樣幹。嵌套引用的package init函數由最內層的開始被調用。下面寫了一個小demo  一目瞭然。

1.可以看到import包的時候寫的是真實的mkdir的目錄名,而main函數中我引入時使用的是pkName這個我自定義的包名。

2.init先加載引入的包也就是B.go 而且init是自動調用的。

3.我設定了 i 這個變量,目的是讓你明白init函數是執行在全局變量賦值初始化之後的。因爲輸出的是9而不是0 

八 併發 GoRoutines

這個是學go語言必須掌握和懂的。因爲學go就是爲了用這玩楞。

應知應會

學計算機肯定知道進程是內存分配的單元。線程是操作系統(cpu)調度的最小單元。因爲線程共享進程中的內存所以存在一系列併發資源競爭問題。go中提出了一個用戶態協程的概念goroutines 這個是go語言層面支持的,不需要在用代碼處理了。

進程之間通信通過中間件、socket等等。線程之間通信通過共享內存。而這個routines之間通信建議通過channel通道、因爲也是可以通過共享內存的,但是這樣就很low。

關於goroutines  它不是線程級別的。多個goroutines跑在一個線程裏面,通過一個CPU調度。因爲存在一個處理器來記錄每個goroutines的執行位置。發生阻塞會向處理器發送狀態,然後處理器切換別的goroutines運行。當然如果沒阻塞也會進行goroutines的切換(搶佔式)。從而挖掘單核CPU的計算能力。高併發環境比線程切換快超級多。

go  func  就可以了。異步跑一個你想幹的活。

runtime.GOMAXPROCS(runtime.NumCPU()) //獲取機器的CPU數量,設置進去。默認就是這個設置。

goroutines這個是處理併發的,不是並行。並行是指在一個時刻開始多個任務。併發是指在一個時間間隔開啓多個相同處理任務。就比如http包的  l.accept()

goroutines與其他語言的coroutine的區別

最主要區別:goroutines是搶佔式的。而c#、lua、Python中使用的coroutine是由本身決定的,yield從而交出CPU佔用權。

channel

通道是一種線程安全的工具,一個時刻只能一個goroutines寫和消費。先進先出,像隊列。

通道一般是多個goroutines協調任務通信用的。channel是可以帶緩衝的  make(chan int,10)10個緩衝,只有寫第11個纔會阻塞。

有定時器和延遲器這兩個玩意。

ticker:=time.NewTicker(time.Second)    //每到時間間隔就執行
timer:=time.NewTimer(time.Second)      //延遲執行
for{
   select {
   case <-ticker.C:
      fmt.Println("touch ticker")
   case <-timer.C:
      fmt.Println("timer stop")
   }
}

同步

編程離不開同步,goroutines也離不開,因爲不確定goroutines跑在哪幾個線程裏面。如果跑在一個線程裏面肯定沒問題,但是跑在多個線程裏面就會發生競爭。線程不安全。

與java裏面的Atomic類一樣go裏面也有atomic.

import "sync/atomic"
var a int64
func main() {
   //AddInt64 atomically adds delta to *addr and returns the new value.
   //func AddInt64(addr *int64, delta int64) (new int64)
   atomic.AddInt64(&a,1)   //可以直接return 線程安全。
}

在go裏面還可以使用互斥鎖sync.Mutex,類似java裏面的synchronized 爲了多任務執行完再往下走。go推出了一個叫做sync.WaitGroup等待組的東西。就是java裏面的CountDownLatch 

var (
   a int
   lock sync.Mutex    //互斥鎖
)
func add(wg *sync.WaitGroup){
   lock.Lock()
   defer lock.Unlock()
   a++  //線程不安全。加鎖後安全。
   wg.Done()
}
func main() {
   wg:= sync.WaitGroup{}
   for i:=0;i<1000;i++{
      wg.Add(1)
      go add(&wg)
   }
   wg.Wait()
   fmt.Println(a)
}

讀寫鎖類似

var (
   rwLock sync.RWMutex
   x int
)
func get()int{
   rwLock.RLock()
   defer rwLock.RUnlock()
   return x
}
func addX(wg *sync.WaitGroup){
   rwLock.Lock()
   defer rwLock.Unlock()
   x++
   wg.Done()
}
func main() {
   wg:= sync.WaitGroup{}
   for i:=0;i<1000;i++{
      wg.Add(1)
      go addX(&wg)
   }
   wg.Wait()
   fmt.Println(x)
}

九 反射

首先理解Type與Kind  Kind相當於一個大分類。

type NewInt int
type M struct {
}
func main() {
   m:= M{}
   t:=reflect.TypeOf(m)
   fmt.Println(t.Kind(),"---",t.Name()) //struct --- M
   var n NewInt = 10
   nType := reflect.TypeOf(n)
   fmt.Println(nType.Kind(),"---",nType.Name()) //int --- NewInt
   m1:=&M{}
   t2:=reflect.TypeOf(m1)
   fmt.Println(t2.Kind(),"---",t2.Name()) //ptr ---
   t2=t2.Elem()      //指針類型需要Elem來獲取
   fmt.Println(t2.Kind(),"---",t2.Name()) //struct --- M
}

其中有一個tag的概念,這個在做json、bson等轉格式的時候其實也用到了。

type M struct {
   Name string `json:"names"`
   desc string
}
func main() {
   m:= M{}
   t:=reflect.TypeOf(m)
   field, _ := t.FieldByName("Name")
   str := field.Tag.Get("json")
   fmt.Println(str)   //names
}

還有一些反射的用法就不作探究了。平時編程的時候用的比較少。

十 Go 的命令

下面的測試中,我的gopath是E:\Go-project

go build

1.go build會在當前文件夾將指定文件編譯成可執行文件。 go build hello.go    ./hello

2.也可以把多個文件編譯在一個EXE中。go build  a.go  service.go 以第一個文件名作爲EXE的名稱。

需要注意的是go  build 編譯的go文件只能有一個有main 函數。

3.go build -o reName.exe a.go service.go  這樣使用-o 參數就編譯出了一個自定義名稱的可執行文件。

4.go build 編譯一個包的話必須這個包在gopath的src下面,否則報如下錯。

can't load package: package go-tools: cannot find package "go-tools" in any of:
        C:\Go\src\go-tools (from $GOROOT)
        E:\Go-project\src\go-tools (from $GOPATH)
5.編譯包 需要寫從src下面一直到有go文件的目錄。哪怕目錄裏面沒有main函數也不會報錯就是不好用而已。可以看到下圖中我在Restart-Go下,並不是直接的src下。但是寫目錄的時候一定要寫相對src的路徑。(B.go沒有main但是可以編譯,不好用)

go  run (編譯後運行)

1.go  run  x.go  這個x.go裏面必須得有main函數。否則如下

# command-line-arguments
runtime.main_main·f: function main is undeclared in the main package

2.go  run不會在運行目錄生成可執行文件,會在一個臨時目錄生成。  go run a.go  --a dd   後面是參數。

3.需要注意的是如果不在gopath下。是不能被引用到的。如下圖。哪怕這看起來是上級的文件,但是引用不了。除非你在gopath下通過src的相對路徑import  然後使用  package名稱命名來引用方法就可以了。

4.go run 是跟gopath關係不大的。但是同一包得指定引用的其他文件。如下圖所示。

go  install(編譯並安裝)
1.go  install也是與gopath緊密相關的。緊密到只能在gopath下的相對src的目錄可以進行安裝。

2.安裝gopath下的目錄(Restart-Go在gopath/src下)   go install Restart-Go/service/main

會固定在gopath/bin下創建剛纔install編譯的可執行文件(也可能是在某處創建然後挪到bin下,不探究這個)

3.執行  go  install   前提是package  main不能有main包纔會在 pkg 目錄生成一個 .a文件、  下面報錯是main包沒有main函數。但是想編譯到pkg目錄下的東西是提供給別的程序用的。不需要main函數。(個人是這麼理解的)  所以包不能聲明爲main包。

go  get

這個可以說是第二常用的命令了。用這個本地得有git/svn當然我有git 像SVN這種比較舊的我就剛開始學編程時用過。

go get github.com/golang/go 這樣就下載到gopath下面了。  一般加 -v 參數。別的參數我感覺一般情況沒啥用。

go get -v github.com/golang/go  如下圖

go  test

1.單元測試。有一個要求是必須以 _test爲go文件的結尾才能使用測試。如果不加 _test會報錯。

2. 測試用例需要以Test爲開始  並且是下面這個參數、

func TestHelloB(t *testing.T) {
   t.Logf("hello")
}

3.go test 時的一些參數 go test -v B_test.go  輸出詳情。   -run   TestHelloB($) 可以正則的指定運行用例。

4.go  test 時引用其他包裏面的文件需要把那個文件也寫在go  test命令中 就像build一樣。

go  test   a_test.go  b.go  c.go    

5.go test還可以做性能測試。測試函數需要以Benchmark開頭。如下

func BenchmarkTestB(b *testing.B) {
   var n int
   for i:=0;i<b.N;i++{
      n++
   }
}

-bench=.  就相當於普通測試的那個  -run     -cover是覆蓋率。    -benchmem是內存信息、  

0B/op是每次調用分配的字節數    0 allocs/op 是每一次調用有0次分配。

 

十一 Others

1.首先明確channel的性能並不比鎖sync.Mutex 及sync.WaitGroup這種好、使用channel傳輸數據使goroutines之間通信纔是最好的使用方式。

2.反射是動態的特性,但是性能並不是太好。當然以我目前的水平還用不好反射這個特性。

3.接口的nil值是需要 value和type都爲nil纔可以的。

type Impl struct {

}
func (i Impl)String()string{
   return "asd"
}
func Get()fmt.Stringer{
   var i *Impl = nil
   return i    //return  nil
}
func main() {
   s:= Get()
   var i *Impl = nil
   if i == nil {
      fmt.Println("i nil")    //i nil
   }
   if s == nil {
      fmt.Println("nil")
   }else {
      fmt.Println("not nil")  //not nil
   }
}

上面這個demo中 Get方法返回的是一個指針,不是nil  在與nil比較時就不相等了。如果想改就改成return  nil。

4.go裏面的map要使用非動態類型數組(不要用切片) 非指針,非函數非閉包作爲key 因爲key哈希得一樣,不能是動態的。

5.對於結構體裏面可以聲明像 sync.Mutex  waitGroup這種東西,然後使用接收器方法來用這些東西同步結構體變量。

 

 

推薦的一個博客:https://draveness.me/golang/   可以作爲進階學習資源。

發佈了54 篇原創文章 · 獲贊 25 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章