安裝以及基本語法參考官方文檔即可。
入門資源分享:
環(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寫入到channelint2 := <- 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)
格式argsfmt.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)
}