Go學習筆記

本博客只是記錄我在學習go語言時的知識點,之前用的是C++

 

  1. 它沒有隱式的數值轉換,沒有構造函數和析構函數,沒有運算符重載,沒有默認參數,也沒有繼承,沒有泛型,沒有異常,沒有宏,沒有函數修飾,更沒有線程局部存儲。
  2. 最佳的幫助信息來自Go語言的官方網站,https://golang.org ,它提供了完善的參考文檔,包括編程語言規範和標準庫等諸多權威的幫助信息。同時也包含了如何編寫更地道的Go程序的基本教程,還有各種各樣的在線文本資源和視頻資源,它們是本書最有價值的補充。Go語言的官方博客 https://blog.golang.org 會不定期發佈一些Go語言最好的實踐文章,包括當前語言的發展狀態、未來的計劃、會議報告和Go語言相關的各種會議的主題等信息(譯註:http://talks.golang.org/ 包含了官方收錄的各種報告的講稿) 。 基於 Playground 構建的 Go Tour,https://tour.golang.org ,是一個系列的Go語言入門教程,它包含了諸多基本概念和結構相關的並可在線運行的互動小程序。 
  3. run。這個命令編譯一個或多個以.go結尾的源文件,鏈接庫文件,並運行最終生成的可執行文件

  4. build 這個命令生成一個可執行的二進制文件,之後你可以隨時運行它,不需任何處理。 

  5. import 聲明必須跟在文件的 package 聲明之後。 

  6. i++ 和i-- 是語句而不像C系的其它語言那樣是表達式。所以 j = i++ 非法,而且++和--都只能放在變量名後面,因此 --i 也非法。 

  7. Go語言只有for循環這一種循環語句。 

  8. Go語言不允許使用無用的局部變量(local variables) ,因爲這會導致編譯錯誤 

  9. 空標識符 (blank identifier) ,即 _ (也就是下劃線) 可用於任何語法需要變量名但程序邏輯不需要的時候, 例如, 在循環裏,丟棄不需要的循環索引, 保留元素值。 

  10. switch不帶操作對象時默認用true值代替,然後將每個case的表達式和true值進行比較 

  11. 數值類型變量對應的零值是0,布爾類型變量對應的零值是false,字符串類型對應的零值是空字符串,接口或引用類型(包括slice、map、chan和函數) 變量對應的零值是nil。數組或結構體等聚合類型對應的零值是每個元素或字段都是對應該類型的零值。 bytes.Buffer類型,結構體初始值就是一個隨時可用的空緩存 。sync.Mutex的零值也是有效的未鎖定狀態。 結構體沒有任何成員的話就是空結構體,寫作struct{}

  12. 簡短變量聲明語句中必須至少要聲明一個新的變量,簡短變量聲明語句對已經聲明過的變量就只有賦值行爲。

  13. 在Go語言中,返回函數中局部變量的地址也是安全的。 

  14. 編譯器會自動選擇在棧上還是在堆上分配局部變量的存儲空間,但可能令人驚訝的是,這個選擇並不是由用var還是new聲明變量的方式決定的。 

  15. 元組賦值是另一種形式的賦值語句,它允許同時更新多個變量的值。在賦值之前,賦值語句右邊的所有表達式將會先進行求值,然後再統一更新左邊對應變量的值。 如:
    x, y = y, x 

  16. 包的初始化首先是解決包級變量的依賴順序,然後安照包級變量聲明出現的順序依次初始化。

  17. init初始化函數除了不能被調用或引用外,其他行爲和普通函數類似。在每個文件中的init初始化函數,在程序開始執行時按照它們聲明的順序被自動調用。 

  18. 任何在在函數外部(也就是包級語法域) 聲明的名字可以在同一個包的任何源文件中訪問的,當前包的其它源文件無法訪問在當前源文件導入的包。 

  19. 位操作運算符 &^ 用於按位置零(ANDNOT) :表達式 z = x &^ y ,如果對應y中bit位爲1的話,結果對應z的bit位爲0,否則對應的bit位等於x相應的bit位的值。

  20. 數組傳參與C++不同時值傳遞,其實在Go語言中,所有的函數參數都是值拷貝傳入的,函數參數將不再是函數調用時的原始變量。

  21. 字符串的值是不可變的:一個字符串包含的字節序列永遠不會被改變,當然我們也可以給一個字符串變量分配一個新字符串值。可以像下面這樣將一個字符串追加到另一個字符串:
    s := "left foot"
    t := s
    s +=", right foot" 

  22. 數組和結構體都是有固定內存大小的數據結構。slice和map則是動態的數據結構,它們將根據需要動態增長。 

  23. 指針指向第一個slice元素對應的底層數組元素的地址,要注意的是slice的第一個元素並不一定就是數組的第一個元素。長度對應slice中元素的數目;長度不能超過容量,容量一般是從slice的開始位置到底層數據的結尾位置。

  24. 複製一個slice只是對底層的數組創建了一個新的slice別名 。

  25. 如果你需要測試一個slice是否是空的,使用len(s) == 0來判斷,而不應該用s == nil來判斷。因爲一個nil值的slice的長度和容量都是0,但是也有非nil值的slice的長度和容量也是0的,例如[]int{}或make([]int, 3)[3:]。

  26. 內置的copy函數可以方便地將一個slice複製另一個相同類型的slice。copy函數的第一個參數是要複製的目標slice,第二個參數是源slice,目標和源的位置順序和 dst = src 賦值語句是一致的。兩個slice可以共享同一個底層數組,甚至有重疊也沒有問題。copy函數將返回成功複製的元素的個數(我們這裏沒有用到) ,等於兩個slice中較小的長度,所以我們不用擔心覆蓋會超出目標slice的範圍。 

  27. 通常我們並不知道append調用是否導致了內存的重新分配,因此我們也不能確認新的slice和原始的slice是否引
    用的是相同的底層數組空間。同樣,我們不能確認在原先的slice上的操作是否會影響到新的slice。因此,通常是將append返回的結果直接賦值給輸入的slice變量:
    runes = append(runes, r)
    更新slice變量不僅對調用append函數是必要的,實際上對應任何可能導致長度、容量或底層數組變化的操作都是必要的。要正確地使用slice,需要記住儘管底層數組的元素是間接訪問的,但是slice對應結構體本身的指針、長度和容量部分是直接訪問的。 

  28. 雖然浮點數類型也是支持相等運算符比較的,但是將浮點數用做key類型則是一個壞的想法,正如第三章提到的,最壞的情
    況是可能出現的NaN和任何浮點數都不相等(兩個NaN是不相等的)。 

  29. map中的元素並不是一個變量,因此我們不能對map的元素進行取址操作:
    _ = &ages["bob"] // compile error: cannot take address of map element
    禁止對map元素取址的原因是map可能隨着元素數量的增長而重新分配更大的內存空間,從而可能導致之前的地址無效。

  30. Map的迭代順序是不確定的,並且不同的哈希函數實現可能導致不同的遍歷順序。在實踐中,遍歷的順序是隨機的,每一次遍歷的順序都不相同。 

  31. map上的大部分操作,包括查找、刪除、len和range循環都可以安全工作在nil值的map上,它
    們的行爲和一個空的map類似。但是向一個nil值的map存入元素將導致一個panic異常(即在向map存數據前必須先創建map) 

  32. 內置的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{} 

  33. 通過key作爲索引下標來訪問map將產生一個value。如果key在map中是存在的,那麼將得到
    與key對應的value;如果key不存在,那麼將得到value對應類型的零值 。但是有時候可能需要知道對應的元素是否真的是在map之中。例如,如果元素類型是一個數字,你可以需要區分一個已經存在的0,和不存在而返回零值的0,可以像下面這樣測試:
    age, ok := ages["bob"]
    if !ok { /* "bob" is not a key in this map; age == 0. */ } 或者
    if age, ok := ages["bob"]; !ok { /* ... */ } 

  34. 和slice一樣,map也是索引(一個哈希表的索引),map之間也不能進行相等比較;唯一的例外是和nil進行比較。 

  35. Go語言中並沒有提供一個set類型,但是map中的key也是不相同的,可以用map實現類似set的功能。

  36. 有時候我們需要一個map或set的key是slice類型,但是map的key必須是可比較的類型,但是slice並不滿足這個條件。不過,我們可以通過兩個步驟繞過這個限制。第一步,定義一個輔助函數k,將slice轉爲map對應的string類型的key,確保只有x和y相等時k(x) == k(y)才成立。然後創建一個key爲string類型的map,在每次對map操作時先用k輔助函數將slice轉化爲string類型。 

  37. 結構體成員的輸入順序也有重要的意義。交換成員出現的先後順序,就是定義了不同的結構體類型。

  38. 一個命名爲S的結構體類型將不能再包含S類型的成員:因爲一個聚合的值不能包含它自身。(該限制同樣適應於數組。) 但是S類型的結構體可以包含 *S 指針類型的成員 (和C++中的類一樣)

  39. 結構體初始化:

    type Point struct{ X, Y int }
    p := Point{
    1, 2} (因爲限制條件多,用的少)或
    p := Point{X : 1, Y:2} (兩者不能混用)
    也可以取地址:pp := &Point{1, 2} 等價於
    pp := new(Point)
    *pp = Point{12
  40. 一次函數調用返回錯誤時,常用的五種處理方式 :
    a)最常用的方式是傳播錯誤。一般而言,被調函數f(x)會將調用信息和參數信息作爲發生錯誤時的上下文放在錯誤信息中並返回給調用者,調用者需要添加一些錯誤信息中不包含的信息。
    b)重新嘗試失敗的操作 。如果錯誤的發生是偶然性的,或由不可預知的問題導致的。一個明智的選擇是重新嘗試失敗的操作。在重試時,我們需要限制重試的時間間隔或重試的次數,防止無限制的重試。
    c)輸出錯誤信息並結束程序。 這種策略只應在main中執行。對庫函數而言,應僅向上傳播錯誤,除非該錯誤意味着程序內部包含不一致性,即遇到了bug,才能在庫函數中結束程序。
    d)只需要輸出錯誤信息
    e)直接忽略掉錯誤 

  41. 在Go中,函數被看作第一類值(first-class values) :函數像其他值一樣,擁有類型,可以被賦值給其他變量,傳遞給函數,從函數返回。函數類型的零值是nil。調用值爲nil的函數值會引起panic錯誤。函數值可以與nil比較 ,但是函數值之間是不可比較的 

  42. 擁有函數名的函數只能在包級語法塊中被聲明,通過函數字面量(function literal) ,我們可繞過這一限制,在任何表達式中表示一個函數值。函數值字面量是一種表達式,它的值被稱爲匿名函數(anonymous function) ,且在函數中定義的內部函數可以引用該函數的變量 。

  43. 當匿名函數需要被遞歸調用時,我們必須首先聲明一個變量,再將匿名函數賦值給這個變量。 

  44. 在聲明可變參數函數時,需要在參數列表的最後一個參數類型之前加上省略符號“...”,這表示該函數會接收任意數量的該類型參數(只能出現在最後一個參數)。 如:func errorf(format string, num ...int ){。。。} 。調用者隱式的創建一個數組,並將原始參數複製到數組中,再把數組的一個slice作爲參數傳給被調函數 。如果實參本來就是個slice類型,則需在最後一個參數後加上省略符。 如:
    values := []int{1, 2, 3, 4}
    errorf("sss", values ...)

  45. 普通函數或方法前加上關鍵字defer,就完成了defer所需要的語法。當defer語句被執行時,跟在defer後面的函數會被延遲執行。直到包含該defer語句的函數執行完畢時,defer後的函數纔會被執行,不論包含defer語句的函數是通過return正常結束,還是由於panic導致的異常結束。你可以在一個函數中執行多條defer語句,它們的執行順序與聲明順序相反。 

  46. 1)defer語句經常被用於處理成對的操作,如打開、關閉、連接、斷開連接、加鎖、釋放鎖。 通過defer機制,不論函數邏輯多複雜,都能保證在任何執行路徑下,資源被釋放。釋放資源的defer應該直接跟在請求資源的語句後。 2)調試複雜程序時,defer機制也常被用於記錄何時進入和退出函數。 例子:
    func bigSlowOperation() {
        defer trace("bigSlowOperation")() // don't forget the extra parentheses
        // ...lots of work…
        time.Sleep(10 * time.Second) // simulate slow
        operation by sleeping
    }

    functrace(msg string) func() {
        start := time.Now()
        log.Printf(
    "enter %s", msg)
        return func() {
        log.Printf(
    "exit %s (%s)", msg,time.Since(start))
        }
    }

    需要注意一點:不要忘記defer語句後的圓括號,否則本該在進入時執行的操作會在退出時執行,而本該在退出時執行的,永遠不會被執行。
    每一次bigSlowOperation被調用,程序都會記錄函數的進入,退出,持續時間。 

  47. 在循環體中的defer語句需要特別注意,因爲只有在函數執行完畢後,這些被延遲的函數纔會執行。 這就要注意內存消耗了。如
    for _, filename := range filenames {
    f, err := os.Open(filename)

    if err != nil {
    return err
    }
    d
    eferf.Close() // NOTE: risky; could run out of file
    descriptors
    // ...process f…
    }
    這就會導致系統的文件描述符耗盡 ,可以用以下方法解決:將循環體中的defer語句移至另外一個函數。在每次循環時,調用這個函數。
    for _, filename := range filenames {
    if err := doFile(filename); err != nil {
    return err
    }
    }
    f
    uncdoFile(filename string) error {
    f, err := os.Open(filename)

    if err != nil {
    return err
    }
    d
    eferf.Close()
    // ...process f…
    } 

  48. 在Go的panic機制中,延遲函數的調用在釋放堆棧信息之前。 

  49. 如果在deferred函數中調用了內置函數recover,並且定義該defer語句的函數發生了panic異常,recover會使程序從panic中恢復,並返回panic value。導致panic異常的函數不會繼續運行,但能正常返回。在未發生panic時調用recover,recover會返回nil。 

  50. 在函數聲明時,在其名字之前放上一個變量,即是一個方法。這個附加的參數會將該函數附加到這種類型上,即相當於爲這種類型定義了一個獨佔的方法。 附加的參數,叫做方法的接收器(receiver) 。接收器.函數名叫做選擇器(也可以是接收器.字段),結構體中方法和字段的命名不能相同,因爲調用時會有歧義。方法可以被聲明到任意類型,只要不是一個指針或者一個interface。 

  51. 當這個接收器變量本身比較大時,我們就可以用其指針而不是對象來聲明方法 。在現實的程序裏,一般會約定如果一個類有一個指針作爲接收器的方法,那麼這個類的所有方法都必須有一個指針接收器,即使是那些並不需要這個指針接收器的函數。 此外,爲了避免歧義,在聲明方法時,如果一個類型名本身是一個指針的話,是不允許其出現在接收器中的 。

  52. 如果接收器p是一個Point類型的變量,並且其方法需要一個Point指針作爲接收器,我們可以用下面這種簡短的寫法:
    p.ScaleBy(2) //ScaleBy函數的原型:func (p *Point) ScaleBy(factor float64)
    編譯器會隱式地幫我們用&p去調用ScaleBy這個方法。我們不能通過一個無法取到地址的接收器來調用指針方法,比如臨時變量的內存地址就無法獲取得到:
    Point{1, 2}.ScaleBy(2) // compile error: can't take address of Point literal
    但是我們可以用一個 *Point 這樣的接收器來調用Point的方法,因爲我們可以通過地址來找到這個變量,只要用解引用符號* 來取到該變量即可。編譯器在這裏也會給我們隱式地插入* 這個操作符,所以下面這兩種寫法等價的:
    pptr.Distance(q) //Distance函數的原型是:func (p Point) Distance(q Point) float64   
    (*pptr).Distance(q) 

  53. a) 不管你的method的receiver是指針類型還是非指針類型,都是可以通過指針/非指針類型進行調用的,編譯器會幫你做類型轉換。b)在聲明一個method的receiver該是指針還是非指針類型時,你需要考慮兩方面的內部,第一方面是這個對象本身是不是特別大,如果聲明爲非指針變量時,調用會產生一次拷貝;第二方面是如果你用指針類型作爲receiver,那麼你一定要注意,這種指針類型指向的始終是同一塊內存地址,就算你對其進行了拷貝。 

  54. 調用方法可以通過方法值和方法表達式兩種,p.Distance叫作“選擇器”,選擇器會返回一個方法"值"即一個將方法(Point.Distance)綁定到特定接收器變量的函數。 如:

    p := Point{1, 2}
    q := Point{4, 6}
    distanceFromP := p.Distance// method value
    fmt.Println(distanceFromP(q)) // "5" 直接傳入參數就可以
    當T是一個類型時,方法表達式可能會寫作T.f或者(*T).f,會返回一個函數"值",這種函數會將其第一個參數用作接收器,所以可以用通常(譯註:不寫選擇器)的方式來對其進行調用:
    p := Point{1, 2}
    q := Point{4, 6}
    distance := Point.Distance// method expression
    fmt.Println(distance(p, q)) // "5"
    fmt.Printf("%T\n", distance) // "func(Point, Point) float64"
    scale := (*Point).ScaleBy
    scale(&p,2)
    fmt.Println(p)// "{2 4}"
    fmt.Printf("%T\n", scale) // "func(*Point, float64)" 
  55. Go語言裏的集合一般會用map[T]bool這種形式來表示,T代表元素類型 。在數據流分析領域,集合元素通常是一個非負整數,集合會包含很多元素,並且集合會經常進行並集、交集操作,這種情況下,bit數組會比map表現更加理想。 bit數組:http://blackbeans.iteye.com/blog/1812663

    https://blog.csdn.net/qq_37375427/article/details/79797359
  56. 接口類型是對其它類型行爲的抽象和概括;一個實現了這些方法的具體類型是這個接口類型的實例。因爲接口類型不會和特定的實現細節綁定在一起,通過這種抽象的方式我們可以讓我們的函數更加靈活和更具有適應能力。 

  57. interface{}類型(空接口類型),它沒有任何方法。因爲空接口類型對實現它的類型沒有要求,所以我們可以將任意一個值賦給空接口類型。 

  58. 接口類型的相關概念:http://www.cnblogs.com/susufufu/p/7353312.html

  59. 接口賦值:https://studygolang.com/articles/5788

  60. 接口方法的傳值和傳指針的注意:https://www.linuxidc.com/Linux/2017-05/143413.htm
     
     

 

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