Go 語法基礎--學習筆記

安裝以及基本語法參考官方文檔即可。

入門資源分享:

環(huán)境變量

  • GOPATH 是什么?

    GOPATH設置目錄用來存放Go源碼喝峦,包管理路徑,Go的可運行文件呜达,以及相應的編譯之后的包文件谣蠢。
    $GOPATH下通常會存在三個文件夾:src(存放源代碼),pkg(包編譯后生成的文件)查近,bin(編譯生成的可執(zhí)行文件)眉踱。
    $GOPATH 應該只有一個路徑,每個go項目都應當做一個包霜威,并且存放于src目錄下谈喳。

  • GOROOT是什么?

    GOROOT是Golang的安裝環(huán)境戈泼,全局唯一的變量婿禽,通常不用修改。

語法學習

數(shù)組與切片slice

Go中數(shù)組是值類型大猛。aa = array 時會拷貝array中所有元素到aa扭倾。

對于聲明了長度的數(shù)組,其長度不可再更改挽绩。

a:=[3]{1,2,3}
a[3]=4//Error: invalid array index 3 (out of bounds for 3-element array)

slice 可以看做是容量可變的數(shù)組膛壹。初始化時可以為其指定容量,但是其空間大小會隨著內容長度的變化進行再分配唉堪。切片是引用類型模聋。

func main() {
    s:=make([]int,0)
    // s:=[]int{}
    fmt.Println(s)
    println(s)
    s=append(s,1)
    fmt.Println(s)
    println(s)
}
// ouputs are
[]
[0/0]0x1155c08
[1/1]0xc4200180b8 //地址已經變化
[1]

聲明并賦值一個一維數(shù)組

arr := [3]int{1,2,3}
var arr [3]int = [3]int{1, 2, 3}
定義一個空數(shù)組
var arr []int

對數(shù)組賦值:可以在其長度內直接賦值。

```
var arr [2]int
arr[0]=1
//arr is [1, 0]
```

對slice賦值唠亚,不能arr[0]=1链方,會報錯“index out of range”≡钏眩可以通過append方法實現(xiàn)祟蚀。

```
var a []int
a = append(a, 1)
// a=[1]
```

for 循環(huán)的使用方式

  • 最常見使用方式:

    for i := 0; i < count; i++ {
    }
    
  • 當做while 使用:

    for condition {
    }
    
  • for range

    for index,item := range arr {
    }
    

注意返回值 return

如果函數(shù)有返回值共啃,必須在函數(shù)聲明時指定其返回類型

func increase(a int) int{
    return a+1
}

在使用別的語言時,可能在函數(shù)體中會使用if (conditon) {return }來停止執(zhí)行函數(shù)體中的內容暂题。但是在Golang 中一個函數(shù)只能返回函數(shù)定義時指定的類型移剪,所以這種判斷條件要盡可能前置,如果不滿足條件薪者,就不讓其執(zhí)行該函數(shù)纵苛。

結構體與指針

推薦:通俗易懂文檔

結構體可以有自己的屬性和方法。

type Person struct {
    name string
    age  int
}

func (p Person) say() {
    fmt.Println("i can say ...")
}

結構體內的屬性簡單易懂言津,而屬于他的內部方法是通過在定義func時指明其參數(shù)是哪個結構類型來實現(xiàn)的攻人。

結構體只是其他類型的組合而已,一樣是值傳遞悬槽,所以每次賦值給其他變量都是值拷貝怀吻。如果想一直基于一個結構體進行操作,可以使用指針類型的參數(shù)初婆。

例子1:實現(xiàn)一個有固定長度的棧蓬坡,并含有pop/push方法。

package main

import (
    "fmt"
)

func main() {
    var s stack
    s.push(1)
    s.push(2)
    fmt.Println(s)
    fmt.Println(s.pop())
}

type stack struct {
    index int
    data  [10]int
}

func (s *stack) push(v int) {
    s.data[s.index] = v
    s.index++
}

func (s *stack) pop() int {
    s.index--
    return s.data[s.index]
}

如果在pop push 方法不是提供指向stack的指針磅叛,而是stack類型的變量的話屑咳,每次執(zhí)行push或pop都是基于s的副本進行操作的,所以打印s仍然維全0弊琴。

例子2: 嘗試聲明一個底層類型為int的類型兆龙,并實現(xiàn)某個調用方法該值就遞增100。如 a=0, a.increase() == 100敲董。

分析:要實現(xiàn)該值的自增紫皇,必須使用指針類型。

package main

import "fmt"

type TZ int

func (z *TZ) increase(){
    *z+=100
}

func main() {
    a:=TZ(0)
    //var a TZ
    a.increase()
    fmt.Println(a)
}

注意創(chuàng)建TZ類型的 實例時腋寨,因為其底層是int類型聪铺,所以初始化是a:=TZ(0),而不能是a:=TZ{}精置。

進一步的计寇,如果increase 方法接受一個被加數(shù)锣杂。

func (z *TZ) increase(num int){
    *z+=num // mismatched types TZ and int
}

可以通過以下方式修改

  • *z+=TZ(num ) 進行類型轉換
  • func (z *TZ) increase(num TZ) 函數(shù)聲明時直接指定接受參數(shù)類型為TZ類型脂倦,因為其底層也是int 類型,所以調用increase(10)也完全沒有問題元莫。
接口

接口也是一種獨特的類型赖阻,可以看做是一組method的組合,如果某個對象實現(xiàn)了該接口的所有方法踱蠢,則該對象就實現(xiàn)了該接口(該類型的值就可以作為參數(shù)傳遞給任何將該接口作為接受參數(shù)的方法)例子如下:兩個struct 類型都實現(xiàn)了接口I 的方法火欧,所以以I作為輸入參數(shù)的方法棋电,也都可以接受這兩個struct 類型作為參數(shù)。即同一個接口可以被多種類型實現(xiàn)苇侵。

package main

import "fmt"

type I interface {
    getName() string
}
type T struct {
    name string
}

func (t T) getName() string {
    return t.name
}

type Another struct {
    name string
}

func (a Another) getName() string {
    return a.name
}

func Hello(i I) {
    fmt.Printf("my name is %s", i.getName())
}

func main() {
    Hello(T{name: "free"})
    fmt.Println("\n")
    Hello(Another{name: "another"})
}

空接口: 定義的一個接口類型赶盔,如果沒有任何方法就是一個空接口∮芘ǎ空接口自動被任何類型實現(xiàn)于未,所以任何類型都可以賦值給這種空接口。下邊的例子可以看到陡鹃,聲明一個空接口類型的變量之后烘浦,可以給其賦值各種類型(int,string, struct)而不會報錯。所以利用空接口配合switch type可以實現(xiàn)泛型萍鲸。

補充: Golang中通過.(type)實現(xiàn)類型斷言闷叉。

if t, ok := i.(*S); ok {
    fmt.Println("s implements I", t)
}

如果ok為真,則i是*S類型的值脊阴。

example1:

package main

import "fmt"

type I interface {}

type T struct {
    name string
}

func main() {
    var val I
    val=5
    fmt.Printf("val is %v \n",val)
    val="string"
    fmt.Printf("val is %v \n",val)
    val=T{"struct_name"}
    fmt.Printf("val is %v \n",val)
    switch t:=val.(type) {
    case int:
        fmt.Printf("type int %T \n",t)
    case string:
        fmt.Printf("Type string %T \n", t)
    case T:
        fmt.Printf("Type string %T \n", t)
    default:
        fmt.Printf("Unexpected type %T", t)
    }
}

輸出為:

val is 5 
val is string 
val is {struct_name} 
Type string main.T 

example2: 實現(xiàn)一個包含不同類型元素的數(shù)組握侧。通過定義一個空接口,然后定義一個接收空接口類型元素的數(shù)組即可嘿期。

type Element interface{}
type Vector struct{
    a []Element
}

實現(xiàn) Set(index. element) 方法

func (p *Vector) Set(index int, e Element) {
    p.a[i]=e
}
協(xié)程

Part1: 協(xié)程藕咏、線程與進程分別是什么?

  • 每個運行在機器上的程序都是一個進程秽五,進程是運行在自己內存空間的獨立執(zhí)行體孽查。

  • 一個進程內可能會存在多個線程,這些線程都是共享一個內存地址的執(zhí)行體坦喘。幾乎所有的程序都是多線程的盲再,能夠保證同時服務多個請求。

    但是多線程的應用難以做到精確瓣铣,因為他們會共享內存中的數(shù)據答朋,并以無法預知的方式對數(shù)據進行操作。所有不要使用全局變量或共享內存棠笑,他們會給你的代碼在并發(fā)時帶來危險梦碗。解決之道在于同步不同的線程,對數(shù)據加鎖蓖救,這樣同時就只有一個線程可以變更數(shù)據洪规。

  • 因為對多線程加鎖會帶來更高的復雜度,Golang采用協(xié)程(goroutines)應對程序的并發(fā)處理循捺。協(xié)程與線程之間沒有一對一的關系斩例,協(xié)程是利用協(xié)程調度器根據一個或多個線程的可用性,映射在他們之上的从橘。協(xié)程比線程更輕量念赶,使用更少的內存和資源础钠。協(xié)程可以運行在多個線程之間,也可以運行在線程之內叉谜。

  • 協(xié)程的棧會根據需要進行伸縮旗吁,不出現(xiàn)棧溢出。協(xié)程結束的時候回靜默退出停局,棧自動釋放阵漏。

  • 協(xié)程工作在相同的地址空間,共享內存翻具,所以該部分必須是同步的履怯。Go使用channels來同步協(xié)程,通過通道來通信

Part2: Go協(xié)程的使用

Golang使用通道channel實現(xiàn)協(xié)程之間的通信裆泳,同一時間只有一個協(xié)程可以訪問數(shù)據叹洲,避開了共享內存帶來的坑。

channel 是引用類型工禾。channel中的通信是必須寫入一個讀取一個的运提,如果沒有了讀取,也不會再寫入闻葵,此時會認為通道已經被阻塞民泵,因為channel中允許只能同時存在一個元素。如果不再寫入槽畔,讀取channel也會隨著channel變空而結束栈妆。

  • 創(chuàng)建

    先聲明一個字符串通道,然后創(chuàng)建

    var ch1 chan string
    ch1 = make(chan string)
    

    或者直接創(chuàng)建:

    ch1 := make(chan string)
    //構建int通道
    
    chanOfChans := make(chan int)
    
  • 符號通信符

    使用箭頭厢钧,方向即代表數(shù)據流向

    ch <- int1 將int1寫入到channel

    int2 := <- ch 從channel中取出值賦給int2

  • 例子

    • 使用go關鍵字開啟一個協(xié)程
    • 兩個協(xié)程之間的通信鳞尔,需要給同一個通道作為參數(shù)。
    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
     ch:=make(chan string)
     go sendData(ch)
     go getData(ch)
     time.Sleep(1e9)
    }
    
    func getData(ch chan string){
        for{
            fmt.Printf("output is %s \n",<-ch)
        }
    }
    
    func sendData(ch chan string){
        ch<-"hello"
        ch<-"world"
        ch<-"haha"
    }
    

一些需要注意的點

變量大小寫

Golang中沒有定義某變量維私有或者全局的關鍵字早直,而是通過符號名字的首字母是否大小寫來定義其作用域的寥假。這些符號包括變量、struct,interface 和func 霞扬。針對func/變量我們應該都已經知道只有首字母大寫才能在別的包中調用該方法/變量糕韧,但是定義結構體時我們卻很容易忽略這一點。

package main

import (
"encoding/json"
"fmt"
"log"
"os"
)

type Page struct {
    title    string
    filename string
    Content  string
}

type Pages []Page

var pages = Pages{
    Page{
        "First Page",
        "page1.txt",
        "This is the 1st Page.",
    },
    Page{
        "Second Page",
        "page2.txt",
        "The 2nd Page is this.",
    },
}

func main() {
    fmt.Println(pages)
    pagesJson, err := json.Marshal(pages)
    if err != nil {
        log.Fatal("Cannot encode to JSON ", err)
    }
    fmt.Fprintf(os.Stdout, "%s", pagesJson)
}


//output is

 
[{First Page page1.txt This is the 1st Page.} {Second Page page2.txt The 2nd Page is this.}]
[{"Content":"This is the 1st Page."},{"Content":"The 2nd Page is this."}]

可以見到喻圃,經過序列化之后有些數(shù)據丟失萤彩,并且丟失的全是Page結構體中以小寫字母開頭的變量。結構體其實就是多個變量的聚合级及,其賦值仍然是值的拷貝乒疏,所以在定義結構體時额衙,一定要慎重饮焦,一旦后邊要通過其他方法對結構體進行處理怕吴,那么就最好要將其首字母大寫(這是避免編譯錯誤的最簡單方法),雖然這種格式=可能對Golang的初學者有點難以接受县踢。

局部變量初始化

在一個func內部转绷,我們可以通過a := anotherFunc(xxx)來直接對一個局部變量聲明并且初始化,但是要注意每次使用:=符號時硼啤,都是在定義一個新的局部變量议经。

在進行web開發(fā)時,aa,err:= exampleFunc(xxx)的表達式很常見谴返,注意兩點:

  • 等號左邊要至少有一個是未聲明過的變量煞肾,否則編譯失敗嗓袱;
  • 要防止等號左邊的局部變量遮蓋了你想要使用的全局變量籍救。

case1 :

func example(){
    aa,err := funcA('xxx')
    if err != nil {
        fmt.Println(err)
        return
    }
    err := funcB('yyyy')
    //should be err = funcB('yyyy')
    if err != nil {
        fmt.Println(err)
        return
    }
}

這兩個err其實是一個變量,在接受funcB的返回值時的再次聲明會導致編譯錯誤

case2: 在實現(xiàn)增刪改查方法的封裝時渠抹,我們一般都會對數(shù)據庫進行操作蝙昙。而在此之前,必須通過"database/sql"包提供的接口func Open(driverName, dataSourceName string) (*DB, error)實現(xiàn)對數(shù)據庫的驗證梧却。

如果是按照如下方式執(zhí)行Open()方法奇颠,那么在routerHandler方法中要如何對同一個db進行操作?

package main

import (
    "net/http"
    "strings"
    "database/sql"
    "encoding/json"
    _ "github.com/go-sql-driver/mysql"
    "fmt"
)

main(){
    db, err := sql.Open("mysql", "pass:password@/database_name")
    //聲明并初始化了一個DB的指針db.
    db.SetMaxIdleConns(20)

    http.HandleFunc("/list", getList)
    if err := http.ListenAndServe(":8080", nil); err != nil {
        panic(err)
    }

所以最好先初始化一個全局的指針var DB *sql.DB放航。然后在main函數(shù)中在對其賦值

var err error
DB, err = sql.Open("mysql", "pass:password@/database_name")

不要使用:=烈拒,否則又將sql.DB的指針賦值給了一個新的變量。也不要妄想我現(xiàn)在已經賦值給了首字母大寫的DB缺菌,其他方法中使用該符號不就實現(xiàn)了對同一個數(shù)據庫的操作了么?No, :=定義的是局部變量搜锰。

fmt中各種print方法
  • fmt.Printf() 格式化字符串

    var v int = 12
    fmt.Printf("result is %v", v)
    // result is 12
    
  • fmt.Println(args) 格式args

    fmt.Println("result is %v", v)
    //result is %v 12,不會打印出格式化字符串
    
  • fmt.Fprintf(w,"result is %v", value) 可以將格式化的字符串結果賦值給w蛋叼。

定義數(shù)據格式

場景: web開發(fā)中getUsers 接口需要返回json格式的用戶信息焊傅”蜂蹋總體思路:先根據返回數(shù)據的結構定義一個包含對象的數(shù)組接受Mysql的返回值狐胎,然后再對該對象序列化。

1.定義User字段

type User struct {
    Id      int
    Username string
    Age      string
}

2.定義Users字段

type Users []User

3.將從mysql的返回值賦給定義的數(shù)組歌馍。

func getList(w http.ResponseWriter, r *http.Request){
    user := User{}
    users := Users{}
    
    rows, err := DB.Query("SELECT * FROM userinfo")
    checkErr(err)
    for rows.Next() {
        err := rows.Scan(&user.Id, &user.Username, &user.Age)
        checkErr(err)
        users = append(users, user)
    }
    
    res, err := json.Marshal(users)
    checkErr(err)
    w.Write(res)
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末握巢,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子暴浦,更是在濱河造成了極大的恐慌溅话,老刑警劉巖歌焦,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件飞几,死亡現(xiàn)場離奇詭異,居然都是意外死亡独撇,警方通過查閱死者的電腦和手機屑墨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纷铣,“玉大人卵史,你說我怎么就攤上這事〕谈梗” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵儒拂,是天一觀的道長。 經常有香客問我社痛,道長,這世上最難降的妖魔是什么蒜哀? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任斩箫,我火速辦了婚禮撵儿,結果婚禮上乘客,老公的妹妹穿的比我還像新娘淀歇。我一直安慰自己易核,他們只是感情好,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布牡直。 她就那樣靜靜地躺著,像睡著了一般纳决。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上阔加,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機與錄音,去河邊找鬼胳喷。 笑死,一個胖子當著我的面吹牛厌蔽,可吹牛的內容都是我干的牵辣。 我是一名探鬼主播奴饮,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼择浊!你這毒婦竟也來了?” 一聲冷哼從身側響起琢岩,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎担孔,沒想到半個月后江锨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體糕篇,經...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡啄育,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年拌消,在試婚紗的時候發(fā)現(xiàn)自己被綠了挑豌。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片墩崩。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡氓英,死狀恐怖,靈堂內的尸體忽然破棺而出鹦筹,到底是詐尸還是另有隱情铝阐,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布铐拐,位于F島的核電站饰迹,受9級特大地震影響,放射性物質發(fā)生泄漏余舶。R本人自食惡果不足惜啊鸭,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赠制。 院中可真熱鬧,春花似錦、人聲如沸烟号。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至篙耗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宗弯,已是汗流浹背脯燃。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工蒙保, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辕棚,地道東北人邓厕。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓逝嚎,卻偏偏與公主長得像详恼,于是被迫代替她去往敵國和親补君。 傳聞我的和親對象是個殘疾皇子单雾,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內容