自己读Go程序设计语言的一些总结(更新ing...)
Go
本筆記用于記錄在閱讀Go程序設(shè)計(jì)語言的一些重要的知識(shí)點(diǎn)!并不完全!
第一章(入門)
命令行參數(shù)
在Go語言中可以直接go run 文件,也可以go build后會(huì)生成一個(gè)可執(zhí)行文件,直接使用該可執(zhí)行文件可以運(yùn)行g(shù)o文件。
#第一種 go run file.go #第二種 go build file.go ./file下面用一個(gè)程序來理解如何獲得命令行參數(shù)
package chapter1import ("fmt""os" )/** 該程序用來模擬linux的echo,輸入后面帶的參數(shù) */ func main() {args := os.Argsvar s stringfor i := 1; i < len(args); i++ {s += args[i] + " "}fmt.Println(sy }運(yùn)行效果
PS D:\goTrip\chapter1> go build .\echo1.go PS D:\goTrip\chapter1> .\echo1 aa bb cc aa bb cc補(bǔ)充(for range)
上面的代碼沒有任何問題,但是go語言的for循環(huán)其實(shí)不止這個(gè)用法,他和java不同,不用死板的i++,而是可以像java的for(:)這樣。
首先我們得直到os.Args是一種string的切片,由于要實(shí)現(xiàn)函數(shù)的功能,我們得從index=1開始遍歷,所以我們只用取切片的[1:],現(xiàn)在來實(shí)現(xiàn)函數(shù)。
package mainimport ("fmt""os" )/** 該程序用來模擬linux的echo,輸入后面帶的參數(shù) */ func main() {args := os.Args var s stringfor _, val := range args[1:] {s += val + " "}fmt.Println(s) }前面的下劃線是什么?
這個(gè)在go中代表忽略該值,因?yàn)間o語言創(chuàng)建了一個(gè)值但是不使用,會(huì)報(bào)錯(cuò),用了_,就可以不用也不會(huì)報(bào)錯(cuò)。
被省略的是index!
補(bǔ)充(i++)
go中的i++對i+1,他等價(jià)于i += 1,也等價(jià)于i = i + 1.但是這些是語句,并不是表達(dá)式,所以j = i++不是合法語句。
優(yōu)化(echo1)
在上面我們是通過循環(huán)通過追加舊的字符串來拼接命令行參數(shù)的,會(huì)將新的內(nèi)容賦值給s,然后舊的內(nèi)容垃圾回收。如果有大量的數(shù)據(jù)處理,這種代價(jià)會(huì)比較大,一種簡單并且高效的方式。
/** 該程序用來模擬linux的echo,輸入后面帶的參數(shù) */ func main() {args := os.Argsjoin := strings.Join(args[1:], " ")fmt.Println(join) }找出重復(fù)行
有點(diǎn)像unix的dup,輸出輸入中次數(shù)大于1的行。
方式1(map+scanner)
第一個(gè)實(shí)現(xiàn)方式使用map+scanner實(shí)現(xiàn)。
package mainimport ("bufio""fmt""os" )func main() {record := make(map[string]int)s := bufio.NewScanner(os.Stdin)for s.Scan() {record[s.Text()] += 1}for k, v := range record {if v > 0 {fmt.Printf("dup line is %s\n", k)}} }scanner是bufio包下的掃描器,它可以讀取輸入,以行或者單詞為單位斷開。有點(diǎn)像java的scanner,簡直一毛一樣。
方式2(流)
第二個(gè)實(shí)現(xiàn)方式用流的方式讀入,并且可以讀入文件或者鍵盤鍵入的文字。(通過命令行參數(shù)控制)
大概使用一個(gè)count函數(shù),參數(shù)1是*File,參數(shù)2是記錄的map
package mainimport ("bufio""fmt""io""os" ) func main() {args := os.Argsrecord := make(map[string]int)//若長度為1,說明后面沒有追加文件名字if len(args) == 1 {count2(os.Stdin, record)} else {for _, v := range args[1:] {open, err := os.Open(v)if err != nil {continue}count1(open, record)//記得關(guān)閉!!!open.Close()}}for k, v := range record {if v > 1 {fmt.Printf("%s:%d\n", k, v)}} } func count1(stream *os.File, record map[string]int) {reader := bufio.NewReader(stream)for {line, _, err := reader.ReadLine()if err == io.EOF {break}record[string(line)] += 1}return } func count2(stream *os.File, record map[string]int) {reader := bufio.NewScanner(stream)for reader.Scan() {if reader.Text() == "exit" {break}record[reader.Text()] += 1}return }獲得多個(gè)URL
用http來get請求即可。
package mainimport ("fmt""io/ioutil""net/http""os" )func main() {for _, url := range os.Args[1:] {get, err := http.Get(url)if err != nil {fmt.Printf("err: %v\n", err)return}body := get.Bodyall, err := ioutil.ReadAll(body)if err != nil {fmt.Printf("err: %v\n", err)return}fmt.Println(string(all))} }小作業(yè)1,用io.copy將b文件讀入到控制臺(tái)
package mainimport ("fmt""io""net/http""os" )func main() {for _, url := range os.Args[1:] {get, err := http.Get(url)if err != nil {fmt.Printf("err: %v\n", err)return}body := get.Bodyio.Copy(os.Stdout, body)if err != nil {fmt.Printf("err: %v\n", err)return}} }并發(fā)獲得多個(gè)URL
管道是用來go協(xié)程間通信的,這個(gè)管道很有意思,他默認(rèn)需要兩個(gè)協(xié)程操作,比如一個(gè)協(xié)程對他進(jìn)行數(shù)據(jù)操作了,他會(huì)阻塞直到另一個(gè)協(xié)程對他進(jìn)行數(shù)據(jù)的寫入或者讀取。
package mainimport ("fmt""io""net/http""os""time" )func main() {c := make(chan string)start := time.Now()for _, url := range os.Args[1:] {go fetch(url, c)}//如果main先到則會(huì)阻塞等待,如果go協(xié)程先到則會(huì)將數(shù)據(jù)寫入channel,如果main沒取,也會(huì)阻塞for range os.Args[1:] {fmt.Println(<-c)}since := time.Since(start)fmt.Printf("%.2f has passed", since.Seconds()) }func fetch(url string, c chan string) {now := time.Now()get, err := http.Get(url)if err != nil {c <- fmt.Sprint(err)fmt.Printf("error,err:%v\n", err)}byte, err := io.Copy(io.Discard, get.Body)get.Body.Close()if err != nil {c <- fmt.Sprint(err)fmt.Printf("error,err:%v\n", err)}since := time.Since(now).Seconds()c <- fmt.Sprintf("%.2f %7d %s", since, byte, url) }一個(gè)Web服務(wù)器
我們這邊通過http的listen方法,啟動(dòng)一個(gè)服務(wù)器
package mainimport ("fmt""net/http" )func main() {//路由http.HandleFunc("/", handler)//監(jiān)聽一個(gè)端口http.ListenAndServe("localhost:8000", nil) }func handler(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "url=%v\n", r.URL.Path) } PS D:\goTrip\chapter1> go build .\serve.go PS D:\goTrip\chapter1> ./serve PS D:\goTrip\chapter1> ./fetch http://localhost:8000/ url=/可以看到我們上面發(fā)送的get請求,我們的服務(wù)器成功的收到了。
計(jì)數(shù)器
我們?yōu)檫@個(gè)服務(wù)器加上了一個(gè)計(jì)數(shù)器
package mainimport ("fmt""net/http""sync" )var count int var lock sync.Mutexfunc main() {//路由http.HandleFunc("/", handler)http.HandleFunc("/count", counts)//監(jiān)聽一個(gè)端口http.ListenAndServe("localhost:8000", nil) }func handler(w http.ResponseWriter, r *http.Request) {lock.Lock()count++lock.Unlock()fmt.Fprintf(w, "url=%v\n", r.URL.Path) }func counts(w http.ResponseWriter, r *http.Request) {lock.Lock()fmt.Fprintf(w, "count=%v\n", count)lock.Unlock() }第二章(程序結(jié)構(gòu))
flag包
有以下兩種常用的定義命令行flag參數(shù)的方法。
flag.Type()
基本格式如下:
flag.Type(flag名, 默認(rèn)值, 幫助信息)*Type 例如我們要定義姓名、年齡、婚否三個(gè)命令行參數(shù),我們可以按如下方式定義:
name := flag.String("name", "張三", "姓名") age := flag.Int("age", 18, "年齡") married := flag.Bool("married", false, "婚否") delay := flag.Duration("d", 0, "時(shí)間間隔")需要注意的是,此時(shí)name、age、married、delay均為對應(yīng)類型的指針。
flag.TypeVar()
基本格式如下: flag.TypeVar(Type指針, flag名, 默認(rèn)值, 幫助信息) 例如我們要定義姓名、年齡、婚否三個(gè)命令行參數(shù),我們可以按如下方式定義:
var name string var age int var married bool var delay time.Duration flag.StringVar(&name, "name", "張三", "姓名") flag.IntVar(&age, "age", 18, "年齡") flag.BoolVar(&married, "married", false, "婚否") flag.DurationVar(&delay, "d", 0, "時(shí)間間隔")通過以上兩種方法定義好命令行flag參數(shù)后,需要通過調(diào)用flag.Parse()來對命令行參數(shù)進(jìn)行解析。
支持的命令行參數(shù)格式有以下幾種:
- -flag xxx (使用空格,一個(gè)-符號(hào))
- --flag xxx (使用空格,兩個(gè)-符號(hào))
- -flag=xxx (使用等號(hào),一個(gè)-符號(hào))
- --flag=xxx (使用等號(hào),兩個(gè)-符號(hào))
代碼
package mainimport ("flag""fmt""strings" )var (n = flag.Bool("n", false, "忽略結(jié)尾的換行符")sep = flag.String("sep", " ", "替換輸出參數(shù)時(shí)候用的分隔符") )func main() {flag.Parse()fmt.Print(strings.Join(flag.Args(),*sep))if !*n{fmt.Println()} }可以發(fā)現(xiàn)我們使用flag所解析的參數(shù)*,因?yàn)閒lag.Bool和flag.String是返回的指針,我們需要取指針的值要用到 *
new
new用于產(chǎn)生某一個(gè)類型的指針,他是預(yù)定義的函數(shù),不是關(guān)鍵字,所以如果在某一函數(shù)指定了名為new的變量,我們就用不了new函數(shù)了。
func demo(new int){//用不了new }變量的生命周期
如果是包范圍的變量,他的生命周期和我們程序的生命周期一樣。
如果是局部變量,它從被聲明創(chuàng)建開始,直到無法被訪問。垃圾回收器怎么知道它是否無法被訪問呢?有點(diǎn)像Java的GC Root,將每一個(gè)包級(jí)別的變量和每一個(gè)函數(shù)的局部變量,都當(dāng)成一個(gè)源頭,以該通過指針和其他引用方式找到變量,如果變量沒有路徑能夠到達(dá),說明無法被訪問。
即分析變量的可到達(dá)性。
變量逃逸分析
在go語言中,一般沒有發(fā)生逃逸的話,局部變量會(huì)申請??臻g,包級(jí)別的變量會(huì)申請堆空間,但是函數(shù)內(nèi)的局部變量可能發(fā)生逃逸。
例如下面的情況,最終go會(huì)通過逃逸分析將d分配到堆空間上。
type Data{//... } func escape() *Data{d := &Data{}return d }包的別名
可以在函數(shù)上方對包進(jìn)行一個(gè)別名,如下方例子,雖然Celsius和Fahrenheit底層都是Float64,但是他們是無法相互轉(zhuǎn)換的。
也不能一起計(jì)算,得做類型轉(zhuǎn)型。
package main //攝氏度 type Celsius float64 //華氏度 type Fahrenheit float64const(AbsoluteZero Celsius = -273.15Freezing Celsius = 0Boiling Celsius = 100 )func CToF(c Celsius) Fahrenheit{//不可以//var f Fahrenheit = Freezingreturn Fahrenheit(c*9/5 + 32) }func FToC(f Fahrenheit) Celsius{return Celsius((f-32)*5/9) }func main() {}作用域
一般會(huì)分為系統(tǒng)預(yù)留的值比如int,true,包級(jí)別:別入在函數(shù)外定義的var,局部:函數(shù)內(nèi)或者語句塊內(nèi)定義的。
go在查找變量的引用,會(huì)從內(nèi)層(局部->系統(tǒng))去查找,所以如果函數(shù)內(nèi)定義了一個(gè)val,但是又有一個(gè)全局的val會(huì)優(yōu)先使用局部變量。
對于for循環(huán)很有意思,他會(huì)創(chuàng)建兩個(gè)語法塊,一個(gè)是循環(huán)體的顯示語法塊,一個(gè)是隱式塊,包含了初始化的變量i,i++等。
所以他會(huì)出現(xiàn)一個(gè)很奇怪的現(xiàn)象,就是一個(gè)for里面可能有兩個(gè)i
作用域也會(huì)導(dǎo)致許多奇怪的問題,比如有一個(gè)包級(jí)別的屬性叫val,我們調(diào)用某個(gè)函數(shù)也會(huì)得到val,詳細(xì)見下圖。
這里我們其實(shí)想讓全局的val初始化,但是err和val都沒在函數(shù)內(nèi)聲明,只能用:=,不然err會(huì)爆紅,表示找不到這個(gè)err,此時(shí)我們可以這樣
第三章(基本數(shù)據(jù))
整數(shù)
Go具有有符號(hào)整數(shù)和無符號(hào)整數(shù)。并且每個(gè)整數(shù)都有8,16,32,64位,分別對應(yīng)int8,int16,int32,int64(無符號(hào)整數(shù) uint8,uint16,uint32,uint64).
對于n位有符號(hào)整數(shù),他的取值范圍位-(2^(n-1) - 1) ~ 2^(n-1) - 1.
即對于int8他的取值范圍是-128~127
對于n位無符號(hào)整數(shù),他的取值范圍是0~2^n - 1
即對于uint8他的取值范圍是0~255
由于整數(shù)有他的數(shù)值范圍,所以完全是可能溢出的,我們以范圍最小的uint8來演示一下溢出的情況。
位運(yùn)算
直接看程序吧。
package mainimport "fmt"func main() {var x uint8 = 1<<1 | 1<<5 //"10001"var y uint8 = 1<<1 | 1<<2 //"00011"fmt.Printf("%08b\n", x)fmt.Printf("%08b\n", y)fmt.Printf("%08b\n", x&y)fmt.Printf("%08b\n", x|y)fmt.Printf("%08b\n", x^y)//就等同于x&(^y)fmt.Printf("%08b\n", x&^y)for i := uint8(0); i < 8; i++ {//統(tǒng)計(jì)x的二進(jìn)制形式有幾個(gè)1if (x>>i)&1 != 0 {fmt.Println(i)}} }關(guān)于go的一些內(nèi)置函數(shù)為什么返回有符號(hào)整數(shù)
比如go的len函數(shù)就是返回有符號(hào)整數(shù),為什么要這么做,因?yàn)槲覀冎纔int的數(shù)值范圍是0~?,所以按道理來說無符號(hào)整數(shù)應(yīng)該恒大于等于0,那在開發(fā)中我們經(jīng)常會(huì)寫出這種代碼。
for i := len(list);i >= 0;i--{//logic }假設(shè)返回的是uint,那么i也隨之變成uint,那i–一輩子不可能小于0,會(huì)陷入死循環(huán)。因此無符號(hào)整數(shù)常常只用于位運(yùn)算或者算術(shù)運(yùn)算符。
浮點(diǎn)數(shù)
go中支持兩種大小的浮點(diǎn)數(shù)float32,float64.
字符串
字符串是不可變的字節(jié)序列。它可以包含任何數(shù)據(jù),包括零值字節(jié)。
例如下面的代碼,可以看到雖然str改變了,但是t持有的str的舊值仍沒有改變
由于str不可變,所以str內(nèi)部的值也不能變
不可能出現(xiàn)str[0] = '0’這種情況。
字符串字面量
字符串的值可以直接寫成字符串字面量,形式上就是代雙引號(hào)的字節(jié)序列。
string的幾個(gè)標(biāo)準(zhǔn)包
4個(gè)標(biāo)準(zhǔn)包對字符串操作特別重要:bytes,strings,strconv,unicode
byetes包內(nèi)有用于操作字節(jié)slice的方法。由于字符串不可變,因此按增量方式構(gòu)建字符串會(huì)導(dǎo)致多次內(nèi)存分配,用bytes.Buffer會(huì)更高效。
strconv包含將字符串轉(zhuǎn)換的函數(shù),課轉(zhuǎn)換為布爾值,整數(shù),浮點(diǎn)數(shù)等函數(shù)。
unicode具有判別文字符號(hào)值特性的函數(shù),比如isDigit(是否是數(shù)字),isLetter(是否是字母)
strings包中具有一些操作字符串的函數(shù),類似于bytes。
basename demo
首先不借助任何庫。
func basename(str string) string {//從后往前找到最后一個(gè)/for i := len(str) - 1; i >= 0; i-- {if str[i] == '/' {str = str[i+1:]break}}var index intfor i := len(str) - 1; i >= 0; i-- {if str[i] == '.' {index = ibreak}}return str[:index] }借助lastIndex
func basename1(str string) string {//從后往前找到最后一個(gè)/index := strings.LastIndex(str, "/")end := strings.LastIndex(str, ".")return str[index+1 : end] }對每三個(gè)數(shù)字就插入一個(gè),
不用庫函數(shù),遞歸
package mainimport "fmt"func main() {fmt.Println(foo("123456789")) }func foo(str string) string {if len(str) <= 3 {return str}return str[:3] + "," + foo(str[3:]) }模擬打印數(shù)組
有點(diǎn)像java的數(shù)組的tostirng,在這里是用bytes的buf接受字符串,[]和數(shù)字,這里值得注意的是fpringf的writer得傳一個(gè)指針。+
package mainimport ("bytes""fmt" )func intsToString(arr []int) string {var buf bytes.Bufferbuf.WriteString("[")for i, v := range arr {if i > 0 {buf.WriteString(", ")}fmt.Fprintf(&buf, "%d", v)}buf.WriteString("]")return buf.String() }func main() {ints := make([]int, 0, 3)ints = append(ints, 1)ints = append(ints, 2)ints = append(ints, 3)fmt.Println(intsToString(ints)) }字符串轉(zhuǎn)數(shù)字
數(shù)字—》字符串
i := 1 s1 := fmt.Sprintf("%d", i) s2 := strconv.Itoa(i)字符串–》數(shù)字
atoi, err := strconv.Atoi("123") parseInt, err := strconv.ParseInt("123", 10, 64)parseInt第二個(gè)參數(shù)代表進(jìn)制,第三個(gè)參數(shù)代表位數(shù)。
第四章(復(fù)合數(shù)據(jù)類型)
數(shù)組
數(shù)組的長度是固定的,所以在go中很少使用數(shù)組。更多的會(huì)使用切片。
var a [3]int var a [3]int = {1,2,3} a := [...]int{1,2,3}這里可以注意,如果此時(shí)用print(“%T\n”,a) 得到的類型會(huì)是[3]int,但是[3]int和[4]int是不一樣的。
如果數(shù)組的元素是可以比較的,那么數(shù)組也是可以比較的。
因?yàn)閿?shù)組如果容量過大,作為方法的參數(shù),會(huì)復(fù)制一個(gè)副本,這是十分消耗資源的。此時(shí)可以用指針,傳的就是引用。
切片
切片是一種可變長度的序列。切片中的類型是唯一的,例如[]T,切片中都是T類型。
切片有三個(gè)屬性,指針,長度,容量。長度是指slice中的元素的個(gè)數(shù),它不能大于容量。容量是一般是第一個(gè)到最后一個(gè)元素的長度。
但是值得注意的是切片不能用==來比較,對于字節(jié)切片,可以直接調(diào)用bytes.equal,但是其他的就需要我們自己寫函數(shù)來比較了。
對于切片,如何判斷是一個(gè)空的切片,要用len(切片),如果用切片==nil,會(huì)發(fā)生一種情況,比如剛剛make出來的切片,此時(shí)是沒有元素的,但是不等于nil,他本來是空的。
通過make創(chuàng)建切片,可以有兩種方法。
make([]int,len) make([]int,len,cap)對于第一種返回的是整個(gè)數(shù)組的引用,而第二種是數(shù)組中l(wèi)en元素的引用,但是cap會(huì)為以后的slice留出空間。
append
growslice
首先將當(dāng)前容量進(jìn)行一個(gè)double,判斷是否大于舊容量。
若小于,則說明沒有初始化,所以直接將newCap賦值給數(shù)組即可。
如果當(dāng)前的容量小于1024,會(huì)進(jìn)行一個(gè)double,否則會(huì)進(jìn)行一個(gè)1+1/4的擴(kuò)容。
由于我們調(diào)用append并不知道是否會(huì)發(fā)生內(nèi)存重分配,即并不知道是返回的原數(shù)組還是新數(shù)組,所以我們一般使用append函數(shù)都會(huì)將返回的切片賦值給原來的切片。
這句話可以由下面代碼很清楚的看出來,下面代碼大概是取出一個(gè)string切片的所有空字符串
所以建議用下面這種方式
或者直接操作切片
remove掉切片中index的值
可以用copy直接覆蓋掉index后的值,然后返回len-1的切片即可,詳細(xì)可以看代碼
func remove(s []string, i int) []string {copy(s[i:], s[i+1:])return s[:len(s)-1] }reverse
package mainimport "fmt"func reverse(arr *[]int) {A := *arrl1 := len(*arr) - 1l := len(*arr)/2 - 1for i := 0; i <= l; i++ {A[i], A[l1-i] = A[l1-i], A[i]} }func main() {arr := []int{1, 2, 3, 4, 5}fmt.Println(arr)reverse(&arr)fmt.Println(arr) }map
map是一種key,val鍵值對的存儲(chǔ)結(jié)構(gòu),其中的key和val都是可以用比較的類型,所以在加入map的時(shí)候可以通過判斷map中是否有key。
值得注意的是,我們無法取map的地址,當(dāng)我們用&map[‘bob’]的時(shí)候會(huì)編譯報(bào)錯(cuò),為什么呢?
我認(rèn)為是因?yàn)閙ap中的元素并不是永久的,可能隨著map的擴(kuò)容,導(dǎo)致該位置上的元素rehash到了其他位置,可能會(huì)讓存儲(chǔ)地址無效。
如何有序的遍歷map
比如我們map是map[string]int,此時(shí)有一個(gè)需求,需要按照字典的順序讀取map,如何做呢?
看下面代碼,可以先對key排序,然后根據(jù)排序后的key去字典中取
package mainimport ("fmt""sort" )func main() {dict := make(map[string]int)dict["b"] = 1dict["a"] = 1dict["c"] = 1l := make([]string, 0, 3)for k := range dict {l = append(l, k)}sort.Strings(l)for _, v := range l {fmt.Printf("%s:%d\n", v, dict[v])} }如何判斷map中是否有元素?
比如我們map是map[string]int,如果去到一個(gè)不存在的key,會(huì)自動(dòng)返回默認(rèn)值,但是萬一有一個(gè)key剛好val=0呢?那他明明存在,我們的邏輯判斷成了不存在。
所以得用下面的方式判斷:
if _, ok := dict["d"]; !ok {fmt.Println("不存在") }我想要切片作為key怎么辦
上面說過,對于map來說key是需要能通過==來判斷相等的,那萬一我就想要讓切片作為key呢?
我們可以間接的完成這個(gè)需求
dict := make(map[string]int)//先創(chuàng)建一個(gè)string為key的map func trans(s []string)string{return fmt.Sprintf("%q",s) //將切片轉(zhuǎn)換成string類型 } func Add(list []string){dict[trans(list)] += 1 }基本上所有的需求,都可以用上面的方法完成。當(dāng)然也不一定非要是字符串類型,任何可以得到想要結(jié)果的可以使用==的結(jié)構(gòu)都可以。
map是支持這種的map[string]map[string]string.
結(jié)構(gòu)體
結(jié)構(gòu)體將零個(gè)或者多個(gè)任意類型的命名變量組合在一起成為一個(gè)聚合的數(shù)據(jù)類型。
type Book struct{Name string //...... } var b book對于b,我們可以通過 . 來取出屬性,例如b.Name = ‘Go入門’,在go中也可以對指針類型的結(jié)構(gòu)體使用 .
var b *book = &Book{Name: 'aaa'} b.Name = 'bbb' //等價(jià)于(*b).Name = 'bbb'需要注意的地方
加入某個(gè)函數(shù)能夠返回結(jié)構(gòu)體,必須得返回結(jié)構(gòu)體指針才可以對結(jié)構(gòu)體的屬性進(jìn)行操作,否則會(huì)找不到變量
正解:
結(jié)構(gòu)體無法包含他自己,但是可以是指針
當(dāng)我們學(xué)習(xí)算法的時(shí)候,經(jīng)常需要自己手寫一個(gè)簡單的二叉樹數(shù)據(jù)結(jié)構(gòu)。下方會(huì)報(bào)錯(cuò)
換成指針即可。
寫一個(gè)小demo試試,二叉樹排序
package mainimport "fmt"type Tree struct {val intleft, right *Tree }func sort(vals []int) {var root *Treefor _, v := range vals {root = add(root, v)}s := make([]int, 0, len(vals))s = TreeToSlice(s[:0], root)fmt.Println(s) }func add(node *Tree, val int) *Tree {if node == nil {t := new(Tree)t.val = valreturn t} else {if node.val < val {node.right = add(node.right, val)} else {node.left = add(node.left, val)}return node} }func TreeToSlice(s []int, root *Tree) []int {if root != nil {s = TreeToSlice(s, root.left)s = append(s, root.val)s = TreeToSlice(s, root.right)}return s }func main() {sort([]int{3, 1, 2, 4, 5}) }如果結(jié)構(gòu)體屬性首字母是小寫,別的包無法引用
比如下面這種情況
a,b都是不可導(dǎo)出的,雖然上面代碼沒有顯示的引用a,b,但是他們被隱式的引用了,這也是不允許的。
結(jié)構(gòu)體之間的比較
如果結(jié)構(gòu)體的所有屬性都是可以比較的,那么結(jié)構(gòu)體也是可以比較的。
既然結(jié)構(gòu)體是可以比較的,說明結(jié)構(gòu)體是可以當(dāng)成map的key的。
匿名成員
比如我們模擬一下人類這個(gè)類。
如果我們把所有屬性寫在一個(gè)結(jié)構(gòu)體,例如
type Human struct {Name stringAge intSchoolName stringWorkPlace string }這樣結(jié)構(gòu)會(huì)比較不清晰,我們可以把一些部分抽出來。
type Human struct {Name stringAge intSchoolWork } type School struct {SchoolName string } type Work struct {WorkPlace string }Go允許我們定義不帶名字的結(jié)構(gòu)體成員,只需要指定他的類型。這些結(jié)構(gòu)體成員叫做匿名成員。
第五章(函數(shù))
在GO中是值傳遞,即如果實(shí)參傳入的是非指針的類型,函
數(shù)會(huì)創(chuàng)建一個(gè)副本,對副本進(jìn)行修改不會(huì)影響到本體,但是如果是引用類型,那么函數(shù)使用形參可能會(huì)間接修改本體。
異常
函數(shù)所發(fā)生的異常我們必須考慮,有以下幾個(gè)方法
1.遇到異常,返回異常打印異常
if err != nil{return nil,fmt.Errorf("%v",err) }2.設(shè)置超時(shí)時(shí)間并且重試
const timeount = 1 * time.Minute deadline := time.Now().Add(timeout) //重試邏輯 for tris:= 0;time.Now().Before();treis++{//邏輯if err == nil{break;}log.printf("err")//等待一定時(shí)間,指數(shù)退避策略time.Sleep(time.Second << uint(tries)) }3.輸出錯(cuò)誤,優(yōu)雅的結(jié)束
if err != nil{fmt.Fprintf(os.Stderr,"err : %v",err)os.exit(1) }4.日志記錄,繼續(xù)運(yùn)行
if err != nil{log.printf(os.Stderr,"err : %v",err) }匿名函數(shù)
首先得知道什么是匿名函數(shù),一般的函數(shù)是這樣的
func 函數(shù)名(...)(返回)匿名函數(shù)就是沒有函數(shù)名,例如
func (...)(返回)閉包
Go 語言支持匿名函數(shù),可作為閉包。匿名函數(shù)是一個(gè)"內(nèi)聯(lián)"語句或表達(dá)式。匿名函數(shù)的優(yōu)越性在于可以直接使用函數(shù)內(nèi)的變量,不必申明。
以下實(shí)例中,我們創(chuàng)建了函數(shù) getSequence() ,返回另外一個(gè)函數(shù)。該函數(shù)的目的是在閉包中遞增 i 變量,代碼如下:
課程拓?fù)渑判?#xff08;dfs)
package mainimport ("fmt""sort" )var prereqs = map[string][]string{"algorithms": {"data structures"},"calculus": {"linear algebra"},"compilers": {"data structures","formal languages","computer organization",},"data structure": {"discrete math"},"discrete math": {"intro to programming"},"databases": {"data structures"},"formal languages": {"discrete math"},"networks": {"operating systems"},"operating systems": {"data structures", "computer organization"},"programming languages": {"data structures", "computer organization"}, }func topoSort(m map[string][]string) []string {var order []stringseen := make(map[string]bool)var findAll func(s []string)findAll = func(s []string) {for _, v := range s {if !seen[v] {seen[v] = truefindAll(m[v])order = append(order, v)}}}var keys []stringfor k, _ := range m {keys = append(keys, k)}sort.Strings(keys)findAll(keys)return order }func main() {for i, v := range topoSort(prereqs) {fmt.Printf("%d\t%s\n", i, v)} }注意得先聲明var findAll func(s []string),不然在匿名函數(shù)中無法調(diào)用自己。
函數(shù)的作用域陷阱
看看下面這段代碼,大概意思是先打印所有的tempdir,然后會(huì)有一個(gè)匿名函數(shù),匿名函數(shù)的作用也是打印位于循環(huán)內(nèi)結(jié)構(gòu)的d。看似沒什么問題把,看看打印結(jié)果
func main() {var rmdirs []func()for _, d := range os.TempDir() {fmt.Println(d)rmdirs = append(rmdirs, func() {fmt.Print("func:")fmt.Println(d)})}for _, dir := range rmdirs {dir()} } 67 58 92 85 115 101 114 115 92 77 121 72 111 112 101 92 65 112 112 68 97 116 97 92 76 111 99 97 108 92 84 101 109 112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112 func:112為啥匿名函數(shù)的輸出全是112?
func的輸出似乎都被for的最后一個(gè)元素給覆蓋了,為什么?
因?yàn)槲覀冎篱]包可以用上面的局部變量,可以知道當(dāng)我們在下面的for循環(huán)中使用的局部變量是for循環(huán)中最后一次的。
正確的代碼?
package mainimport ("fmt""os" )func main() {var rmdirs []func()for _, d := range os.TempDir() {dir := dfmt.Println(dir)rmdirs = append(rmdirs, func() {fmt.Print("func:")fmt.Println(dir)})}for _, dir := range rmdirs {dir()} }變長函數(shù)
變長函數(shù)可以接受長度變化的參數(shù)。
package mainimport "fmt"func test(vals ...int) {fmt.Println(vals) }func main() {test(1)test(1, 2)test(1, 2, 3)test([]int{4, 5, 6}...) }延遲調(diào)用
defer語句可以用來調(diào)用一個(gè)復(fù)雜的函數(shù),即在函數(shù)的入口和出口設(shè)置調(diào)試行為。
當(dāng)下面代碼:
package mainimport ("log""time" )func slow(){defer trace("hello")()time.Sleep(10 * time.Second) }func trace(msg string) func(){start := time.Now()log.Printf("enter %s",msg)return func() {log.Printf("exit %s(%s)",msg,time.Since(start))} }func main() {slow() }可以完成對trace的return函數(shù)的延時(shí)調(diào)用,一定不要忘記defer的函數(shù)后面要帶個(gè)小括號(hào)。
如果沒帶就不會(huì)調(diào)用返回的函數(shù),而是會(huì)在slow函數(shù)結(jié)束后直接調(diào)用trace。
再看另外一個(gè)例子。
func double(x int)(result int){defer func() {fmt.Printf("double(%d) = %d\n",x,result)}()time.Sleep(10 * time.Second)return x + x }這一次雖然是defer了一個(gè)函數(shù),但仍然等到了double結(jié)束了才進(jìn)入函數(shù),所以可以推斷出,如果defer的函數(shù)會(huì)返回一個(gè)函數(shù),先回進(jìn)入函數(shù)取到那個(gè)返回的函數(shù),然后等待主程序return,去延時(shí)調(diào)用返回函數(shù),才會(huì)出現(xiàn)上面日志記錄的情況。
第六章(方法)
方法
func f(...)(返回) //普通方法 func (f *function)f(...)(返回) //方法屬于function?
總結(jié)
以上是生活随笔為你收集整理的自己读Go程序设计语言的一些总结(更新ing...)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Go程序设计语言 1.1 hello,w
- 下一篇: 《Go程序设计语言》- 第1章:入门