Go程式設計基礎
女主宣言
Go 是一個開源的程式語言,它能讓構造簡單、可靠且高效的軟體變得容易。現在Go的開發已經是完全開放的,並且擁有一個活躍的社群。本節課開始,李鋼老師將使用Go語言來做一些程式設計實踐方面的講解。
PS:豐富的一線技術、多元化的表現形式,盡在“HULK一線技術雜談”,點關注哦!
Splashdown! Apollo 7 Returns Home
by NASA IOTD
上面兩次課我講解了程式設計方面的基礎知識,這次開始,我使用Go語言來做一些程式設計實踐方面的講解。
今天先來說下Go語言中的一些我認為比較重要的知識點。
關於Go的基礎使用,這裡不做過多介紹,可以閱讀:
How to Write Go Code:
https://golang.org/doc/code.html
Effective Go:
https://golang.org/doc/effective_go.html
The Way to Go:
https://github.com/Unknwon/the-way-to-go_ZH_CN
重要的資料結構
slice
基礎知識
slice是go中最常用的資料結構之一,它相當於動態陣列,瞭解下它的內部實現,對我們使用來說有很大的好處:
slice的資料結構示例為:
type slice struct {
ptr *array //底層儲存陣列
len int //當前儲存了多少個元素
cap int //底層陣列可以儲存多少個元素(從ptr指向的位置開始)}
用張圖來表示:
我們常用的slice有個len和cap的概念,他們就是取len和cap這兩個欄位的值。
slice我們通常都用它做為動態陣列使用,但slice翻譯過來是切片的意思,為什麼呢?
我們來看個例子:
首先,我們建立一個slice:
s := make([]int, 5)
對應的資料結構為:
之後,我們再呼叫:
ss := s[2:4]
我們得到:
所以兩個slice,相當於是在底層array上的兩個切片。大家請注意下第二個slice的cap是3。
使用注意
slice在使用中有幾個很容易出錯的地方,需要大家注意下。
這裡先總結下最容易出錯的原因,就是多個slice在使用同樣的底層儲存時,修改一個slice會導致其它slice中的資料變化。
示例1:
s := []int{1, 2, 3} fmt.Println(s) ss := s[1:3] ss[0] = 0 fmt.Println(s, ss) s[1] = 11 fmt.Println(s, ss)
輸出
[1 2 3] [1 0 3] [0 3] [1 11 3] [11 3]
大家可以看到,由於兩個slice都是用同樣的底層array,所以修改其中一個就會導致另外一個的變化。
示例2:
func main() { s := []int{1, 2, 3} fmt.Println(s) foo(s) or foo(s[1:3]) fmt.Println(s) } func foo(ss []int) { ss[0] = 0 }
輸出:
[1 2 3] [1 0 3]
這個和上面同樣的原因
示例3:
s := []int{1, 2, 3} fmt.Println(s) ss := s[1:3] ss = append(ss, 4) fmt.Println(s, ss)
輸出:
[1 2 3] [1 2 3] [2 3 4]
這裡大家可以看到,由於append操作改變了其中一個slice的底層array,所以對其中一個slice的修改不會影響到另外一個。
map
關於map,有如下幾個地方需要注意:
使用先要初始化
var m map[string]int m["a"] = 1 會導致: panic: assignment to entry in nil map 正確使用: m := make(map[string]int) m["a"] = 1 fmt.Println(m)
輸出:
map[a:1]
map作為函式形參時,函式中對map的修改會影響實參中的值
func main() { m := make(map[string]int) m["a"] = 1 fmt.Println(m) foo(m) fmt.Println(m) } func foo(fm map[string]int) { fm["a"] = 11 }
輸出:
map[a:1] map[a:11]
對map做併發讀寫會導致panic
var gm map[int]int func main() { gm = make(map[int]int) for i := 0; i < 10; i++ { go foo(i) } time.Sleep(time.Second * 10) } func foo(i int) { for j := 0; j < 100; j++ { gm[i] = j } }
執行結果:
所以對map做併發讀寫時需要加鎖。
型別轉換
我們開發強型別語言程式時通常需要做型別轉換,Go中的型別轉換有兩種最常用的形式:
原生型別轉換
同一大型別下(如整數的int、int64,浮點數的float32、float64等),可以用型別加括號的形式,如:
int -> int64: var a int = 1 b := int64(a)
不同大型別下的轉換,使用strconv包中的方法
複雜型別轉換
複雜型別轉換,通常是interface轉指定型別。
這個要使用型別斷言:
var a interface{} = 1 b := a.(int)
請注意這裡如果型別斷言失敗的話,程式會panic,可以使用recover防止:
defer func() { if r := recover(); r != nil { fmt.Println(r) } }() var a interface{} = 1 b := a.(string)
輸出:
interface conversion: interface {} is int, not string
函式傳參時的指標和結構體
這裡只需要記住一點,就是結構體作為函式形參時,會做值拷貝,所以拷貝的那部分值的修改,不會反映到實參值。
type ta struct { i int } func main() { var a ta a.i = 1 foo(a) fmt.Println(a) } func foo(t ta) { t.i = 11 }
輸出:
{1}
同樣的:
type ta struct { i int } func main() { var a ta a.i = 1 a.foo() fmt.Println(a) } func (t ta) foo() { t.i = 11 }
輸出:
{1}
指標就不同了,會修改實參中的原值,這裡就不舉例了。
防止棧溢位
我們程式設計時有時會寫遞迴函式,遞迴雖然簡單,但是會有棧溢位的風險,解決方法是把遞迴轉迴圈,將儲存從棧空間轉移到堆空間上。
我們這裡舉個實際的例子,linux中有個tree命令,它能列出一個給定根目錄下所有的檔案,包括子目錄:
讀取目錄下的包括子目錄的所有檔案,最先想到的就是遞迴了,但是如果目錄層級過深,顯然會導致棧溢位,所以這是一個非常好的例子
實現程式碼如下:
func ListFilesInDir(rootDir string) ([]string, error) { rootDir = strings.TrimRight(rootDir, "/") if !DirExist(rootDir) { return nil, errors.New("Dir not exists") } var fileList []string dirList := []string{rootDir} for i := 0; i < len(dirList); i++ { curDir := dirList[i] file, err := os.Open(dirList[i]) if err != nil { return nil, err } fis, err := file.Readdir(-1) if err != nil { return nil, err } for _, fi := range fis { path := curDir + "/" + fi.Name() if fi.IsDir() { dirList = append(dirList, path) } else { fileList = append(fileList, path) } } } return fileList, nil }
由於slice這種動態儲存結構使用的是在堆上的空間,所以我們將遞迴轉迴圈解決這個問題。
參考
Go Slices: usage and internals:
https://blog.golang.org/go-slices-usage-and-internals
HULK一線技術雜談
由360雲平臺團隊打造的技術分享公眾號,內容涉及雲端計算、資料庫、大資料、監控、泛前端、自動化測試等眾多技術領域,通過夯實的技術積累和豐富的一線實戰經驗,為你帶來最有料的技術分享
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31555491/viewspace-2218730/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Go 併發程式設計 - Goroutine 基礎 (一)Go程式設計
- Socket程式設計基礎程式設計
- Shell程式設計-基礎程式設計
- python程式設計基礎Python程式設計
- shell程式設計基礎程式設計
- Java 基礎02Java程式設計基礎Java程式設計
- Windows程式設計系列:圖形程式設計基礎Windows程式設計
- 【socket程式設計基礎模板】程式設計
- QML程式設計 基礎 小白程式設計
- 【程式設計基礎】輸出程式設計
- 程式設計基礎知識程式設計
- 網路程式設計基礎程式設計
- python 程式設計基礎案例Python程式設計
- shell程式設計基礎二程式設計
- Go語言結構體(struct)物件導向程式設計基礎篇Go結構體Struct物件程式設計
- Java程式設計基礎33——JDBCJava程式設計JDBC
- React基礎:宣告式程式設計React程式設計
- shader程式設計基礎:畫線程式設計
- Golang併發程式設計基礎Golang程式設計
- 併發程式設計基礎(下)程式設計
- 併發程式設計基礎(上)程式設計
- Linux系統程式設計基礎Linux程式設計
- JavaSE基礎程式設計十題Java程式設計
- JAVA網路程式設計基礎Java程式設計
- Java 基礎程式設計筆記Java程式設計筆記
- Java併發程式設計基礎Java程式設計
- java程式設計師程式設計筆試基礎學習Java程式設計師筆試
- 《java程式設計基礎》java的基礎知識(三)Java程式設計
- 網路程式設計基礎知識程式設計
- python基礎(物件導向程式設計)Python物件程式設計
- python物件導向程式設計基礎Python物件程式設計
- 《java程式設計基礎》例題5.6Java程式設計
- Python程式設計基礎Task12Python程式設計
- JavaScript非同步程式設計-基礎篇JavaScript非同步程式設計
- dart基礎之非同步程式設計Dart非同步程式設計
- Java入門之基礎程式設計Java程式設計
- 併發程式設計——基礎概念(一)程式設計
- 併發程式設計——基礎概念(二)程式設計