【Go基礎】基礎語法和踩坑

  1. Go 的 if 語句與 for 循環類似,表達式外無需小括號 ( ) ,而大括號 { } 則是必須的

  2. for 是 Go 中的 “while”
    此時你可以去掉分號,因為 C 的 while 在 Go 中叫做 for。
    無限循環
    如果省略循環條件,該循環就不會結束,因此無限循環可以寫得很緊湊。

  3. go語言沒有max和min函數,寫leetcode時候要自己寫,因為作者覺得這些函數太簡單了

  4. if 的簡短語句
    同 for 一樣, if 語句可以在條件表達式前執行一個簡單的語句。
    該語句聲明的變量作用域僅在 if 之內。

func pow(x, n, lim float64) float64 {
    if v := math.Pow(x, n); v < lim {
        return v
    }
    // return lim
    return v //undefined: v
}

來看下面的代碼

func pow(x, n, lim float64) float64 {
    if v := math.Pow(x, n); v < lim {
        return v
    } else {
        fmt.Printf("%g >= %g\n", v, lim)
    }
    // 這里開始就不能使用 v 了
    return lim
}

func main() {
    fmt.Println(
        pow(3, 2, 10),
        pow(3, 3, 20),
    )
}

在 main 的 fmt.Println 調用開始前,兩次對 pow 的調用均已執行并返回其各自的結果。

  1. switch
    Go 的 switch 語句類似于 C、C++、Java、JavaScript 和 PHP 中的,不過 Go 只運行選定的 case,而非之后所有的 case。 實際上,Go 自動提供了在這些語言中每個 case 后面所需的 break 語句。 除非以 fallthrough 語句結束,否則分支會自動終止。 Go 的另一點重要的不同在于 switch 的 case 無需為常量,且取值不必為整數

    沒有條件的 switch 同 switch true 一樣。
    這種形式能將一長串 if-then-else 寫得更加清晰。
package main

import (
    "fmt"
    "time"
)

func main() {
    t := time.Now()
    switch {
    case t.Hour() < 12:
        fmt.Println("Good morning!")
    case t.Hour() < 17:
        fmt.Println("Good afternoon.")
    default:
        fmt.Println("Good evening.")
    }
}
  1. defer
    defer 語句會將函數推遲到外層函數返回之后執行。
    推遲調用的函數其參數會立即求值,但直到外層函數返回前該函數都不會被調用
func main() {
    defer fmt.Println("world")

    fmt.Println("hello")
}
/*
  輸出 
  hello
  world
*/

推遲的函數調用會被壓入一個棧中。當外層函數返回時,被推遲的函數會按照后進先出的順序調用

  1. 結構體
    結構體字段使用點號來訪問。
type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    v.X = 4
    fmt.Println(v.X)
}

結構體字段可以通過結構體指針來訪問。

如果我們有一個指向結構體的指針 p,那么可以通過 (*p).X 來訪問其字段 X。不過這么寫太啰嗦了,所以語言也允許我們使用隱式間接引用,直接寫 p.X 就可以。

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    p := &v
    p.X = 1e9
    fmt.Println(v)
}
  1. 結構體文法

結構體文法通過直接列出字段的值來新分配一個結構體。

使用 Name: 語法可以僅列出部分字段。(字段名的順序無關。)

特殊的前綴 & 返回一個指向結構體的指針。

type Vertex struct {
    X, Y int
}

var (
    v1 = Vertex{1, 2}  // 創建一個 Vertex 類型的結構體
    v2 = Vertex{X: 1}  // Y:0 被隱式地賦予
    v3 = Vertex{}      // X:0 Y:0
    p  = &Vertex{1, 2} // 創建一個 *Vertex 類型的結構體(指針)
)
  1. 切片

每個數組的大小都是固定的。而切片則為數組元素提供動態大小的、靈活的視角。在實踐中,切片比數組更常用。

類型 []T表示一個元素類型為 T 的切片。

切片通過兩個下標來界定,即一個上界和一個下界,二者以冒號分隔:a[low : high]
它會選擇一個半開區間,包括第一個元素,但排除最后一個元素。

切片就像數組的引用,切片并不存儲任何數據,它只是描述了底層數組中的一段。
更改切片的元素會修改其底層數組中對應的元素。與它共享底層數組的切片都會觀測到這些修改。

  1. 切片文法
    切片文法類似于沒有長度的數組文法。

這是一個數組文法:
[3]bool{true, true, false}

下面這樣則會創建一個和上面相同的數組,然后構建一個引用了它的切片:
[]bool{true, true, false}

func main() {
    q := []int{2, 3, 5, 7, 11, 13}
    fmt.Println(q)

    r := []bool{true, false, true, true, false, true}
    fmt.Println(r)

    s := []struct {
        i int
        b bool
    }{
        {2, true},
        {3, false},
        {5, true},
        {7, true},
        {11, false},
        {13, true},
    }
    fmt.Println(s)
}

  1. 切片的長度與容量

切片擁有 長度 和 容量。

切片的長度就是它所包含的元素個數。

切片的容量是從它的第一個元素開始數,到其底層數組元素末尾的個數。

切片 s 的長度和容量可通過表達式 len(s) 和 cap(s) 來獲取。

你可以通過重新切片來擴展一個切片,給它提供足夠的容量。

func main() {
    s := []int{2, 3, 5, 7, 11, 13}
    printSlice(s)

    // 截取切片使其長度為 0
    s = s[:0]
    printSlice(s)

    // 拓展其長度
    s = s[:4]
    printSlice(s)

    // 舍棄前兩個值
    s = s[2:]
    printSlice(s)
}

func printSlice(s []int) {
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
/*
len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]
*/
  1. nil 切片

切片的零值是 nil。
nil 切片的長度和容量為 0 且沒有底層數組。

  1. 用 make 創建切片

切片可以用內建函數 make 來創建,這也是你創建動態數組的方式。

make 函數會分配一個元素為零值的數組并返回一個引用了它的切片:

a := make([]int, 5)  // len(a)=5

要指定它的容量,需向 make 傳入第三個參數:

b := make([]int, 0, 5) // len(b)=0, cap(b)=5

b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:]      // len(b)=4, cap(b)=4
func main() {
    a := make([]int, 5)
    printSlice("a", a)

    b := make([]int, 0, 5)
    printSlice("b", b)

    c := b[:2]
    printSlice("c", c)

    d := c[2:5]
    printSlice("d", d)
}

func printSlice(s string, x []int) {
    fmt.Printf("%s len=%d cap=%d %v\n",
        s, len(x), cap(x), x)
}
/*
a len=5 cap=5 [0 0 0 0 0]
b len=0 cap=5 []
c len=2 cap=5 [0 0]
d len=3 cap=3 [0 0 0]
*/
  1. 切片的切片

切片可包含任何類型,甚至包括其它的切片

import (
    "fmt"
    "strings"
)

func main() {
    // 創建一個井字板(經典游戲)
    board := [][]string{
        []string{"_", "_", "_"},
        []string{"_", "_", "_"},
        []string{"_", "_", "_"},
    }

    // 兩個玩家輪流打上 X 和 O
    board[0][0] = "X"
    board[2][2] = "O"
    board[1][2] = "X"
    board[1][0] = "O"
    board[0][2] = "X"

    for i := 0; i < len(board); i++ {
        fmt.Printf("%s\n", strings.Join(board[i], " "))
    }
}
/*
X _ X
O _ X
_ _ O
*/
  1. 向切片追加元素

為切片追加新的元素是種常用的操作,為此 Go 提供了內建的 append 函數。內建函數的文檔對此函數有詳細的介紹。

append 的第一個參數 s 是一個元素類型為 T 的切片,其余類型為 T 的值將會追加到該切片的末尾。

append 的結果是一個包含原切片所有元素加上新添加元素的切片。

s 的底層數組太小,不足以容納所有給定的值時,它就會分配一個更大的數組。返回的切片會指向這個新分配的數組。
(要了解關于切片的更多內容,請閱讀文章 Go 切片:用法和本質。)

  1. Range

for 循環的 range 形式可遍歷切片或映射。

當使用 for 循環遍歷切片時,每次迭代都會返回兩個值。第一個值為當前元素的下標,第二個值為該下標所對應元素的一份副本。

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
    for i, v := range pow {
        fmt.Printf("2**%d = %d\n", i, v)
    }
}

可以將下標或值賦予 _來忽略它。
for i, _ := range pow
for _, value := range pow

若你只需要索引,忽略第二個變量即可。
for i := range pow

func main() {
    pow := make([]int, 10)
    for i := range pow {
        pow[i] = 1 << uint(i) // == 2**i
    }
    for _, value := range pow {
        fmt.Printf("%d\n", value)
    }
}

練習
實現 Pic。它應當返回一個長度為 dy 的切片,其中每個元素是一個長度為 dx,元素類型為 uint8 的切片。當你運行此程序時,它會將每個整數解釋為灰度值(好吧,其實是藍度值)并顯示它所對應的圖像。

圖像的選擇由你來定。幾個有趣的函數包括 (x+y)/2, xy, x^y, xlog(y) 和 x%(y+1)。

(提示:需要使用循環來分配 [][]uint8 中的每個 []uint8;請使用 uint8(intValue) 在類型之間轉換;你可能會用到 math 包中的函數。)

import (
    "golang.org/x/tour/pic"
    //"math"
)

func Pic(dx, dy int) [][]uint8 {
    a:=make([][]uint8,dy)
    for x:=range a{
        b:=make([]uint8,dx)
        
        for y:=range b{
            b[y]= uint8(x%(y+1))
        }
        a[x]=b
    }
    return a
}

func main() {
    pic.Show(Pic)
}
  1. 映射

映射將鍵映射到值。映射的零值為 nil 。nil 映射既沒有鍵,也不能添加鍵。
映射的文法與結構體相似,不過必須有鍵名。

make 函數會返回給定類型的映射,并將其初始化備用。若頂級類型只是一個類型名,你可以在文法的元素中省略它。

type Vertex struct {
    Lat, Long float64
}

var m = map[string]Vertex{
    "Bell Labs": Vertex{
        40.68433, -74.39967,
    },
    "Google": Vertex{
        37.42202, -122.08408,
    },
    //"Bell Labs": {40.68433, -74.39967},
    //"Google":    {37.42202, -122.08408},
}
}

func main() {
    //  m = make(map[string]Vertex)
    // m["Bell Labs"] = Vertex{
        40.68433, -74.39967,
    }
    fmt.Println(m)
}
  1. 修改映射

在映射 m 中插入或修改元素:
m[key] = elem

獲取元素:
elem = m[key]

刪除元素:
delete(m, key)

通過雙賦值檢測某個鍵是否存在:
elem, ok = m[key]
若 key 在 m 中,ok 為 true ;否則,ok 為 false。
若 key 不在映射中,那么 elem 是該映射元素類型的零值。
同樣的,當從映射中讀取某個不存在的鍵時,結果是映射的元素類型的零值。

注 :若 elem 或 ok 還未聲明,你可以使用短變量聲明:
elem, ok := m[key]

    m := make(map[string]int)

    m["Answer"] = 42
    fmt.Println("The value:", m["Answer"])

    m["Answer"] = 48
    fmt.Println("The value:", m["Answer"])

    delete(m, "Answer")
    fmt.Println("The value:", m["Answer"])

    v, ok := m["Answer"]
    fmt.Println("The value:", v, "Present?", ok)
/*
The value: 42
The value: 48
The value: 0
The value: 0 Present? false
*/

練習:映射
實現 WordCount。它應當返回一個映射,其中包含字符串 s 中每個“單詞”的個數。函數 wc.Test 會對此函數執行一系列測試用例,并輸出成功還是失敗。

import (
    "golang.org/x/tour/wc"
    "strings"
)

func WordCount(s string) map[string]int {
    
    chrs := strings.Fields(s)
    mp := make(map[string]int)
    for _, c :=range chrs{
        mp[c] += 1
    }
    return mp
}

func main() {

    wc.Test(WordCount)
}
  1. 函數值

函數也是值。它們可以像其它值一樣傳遞。
函數值可以用作函數的參數或返回值。

func compute(fn func(float64, float64) float64) float64 {
    return fn(3, 4)
}

func main() {
    hypot := func(x, y float64) float64 {
        return math.Sqrt(x*x + y*y)
    }
    fmt.Println(hypot(5, 12))

    fmt.Println(compute(hypot))
    fmt.Println(compute(math.Pow))
}
/*
13
5
81
*/

21.函數的閉包

Go 函數可以是一個閉包。閉包是一個函數值,它引用了其函數體之外的變量。該函數可以訪問并賦予其引用的變量的值,換句話說,該函數被這些變量“綁定”在一起。

例如,函數 adder 返回一個閉包。每個閉包都被綁定在其各自的 sum 變量上。

import "fmt"

func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x
        return sum
    }
}

func main() {
    pos, neg := adder(), adder()
    for i := 0; i < 10; i++ {
        fmt.Println(
            pos(i),
            neg(-2*i),
        )
    }
}
/*
0 0
1 -2
3 -6
6 -12
10 -20
15 -30
21 -42
28 -56
36 -72
45 -90
*/

練習

現在來通過函數做些有趣的事情。

實現一個 fibonacci 函數,返回一個函數(一個閉包)可以返回連續的斐波納契數。

關鍵信息

閉包函數會引用函數體以外的值,可以對其修改


import "fmt"

// 返回一個“返回int的函數”
func fibonacci() func() int {
    first, second := 0, 1
    return func() int {
        tmp := first
        first, second = second, (first + second)
        return tmp
    }
}

func main() {
    f := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Println(f())
    }
}
/*
0
1
1
2
3
5
8
13
21
34
*/
  1. 看這么一個表達式

f func(func(int,int) int, int) func(int, int) int

函數名 func標識符(參數) 返回值
f=函數名
括號內func(int,int) int, int這些都是參數
括號后func(int,int) int是返回值

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372