自己读Go程序设计语言的一些总结(更新ing...)
Go
本筆記用于記錄在閱讀Go程序設計語言的一些重要的知識點!并不完全!
第一章(入門)
命令行參數
在Go語言中可以直接go run 文件,也可以go build后會生成一個可執行文件,直接使用該可執行文件可以運行go文件。
#第一種 go run file.go #第二種 go build file.go ./file下面用一個程序來理解如何獲得命令行參數
package chapter1import ("fmt""os" )/** 該程序用來模擬linux的echo,輸入后面帶的參數 */ func main() {args := os.Argsvar s stringfor i := 1; i < len(args); i++ {s += args[i] + " "}fmt.Println(sy }運行效果
PS D:\goTrip\chapter1> go build .\echo1.go PS D:\goTrip\chapter1> .\echo1 aa bb cc aa bb cc補充(for range)
上面的代碼沒有任何問題,但是go語言的for循環其實不止這個用法,他和java不同,不用死板的i++,而是可以像java的for(:)這樣。
首先我們得直到os.Args是一種string的切片,由于要實現函數的功能,我們得從index=1開始遍歷,所以我們只用取切片的[1:],現在來實現函數。
package mainimport ("fmt""os" )/** 該程序用來模擬linux的echo,輸入后面帶的參數 */ func main() {args := os.Args var s stringfor _, val := range args[1:] {s += val + " "}fmt.Println(s) }前面的下劃線是什么?
這個在go中代表忽略該值,因為go語言創建了一個值但是不使用,會報錯,用了_,就可以不用也不會報錯。
被省略的是index!
補充(i++)
go中的i++對i+1,他等價于i += 1,也等價于i = i + 1.但是這些是語句,并不是表達式,所以j = i++不是合法語句。
優化(echo1)
在上面我們是通過循環通過追加舊的字符串來拼接命令行參數的,會將新的內容賦值給s,然后舊的內容垃圾回收。如果有大量的數據處理,這種代價會比較大,一種簡單并且高效的方式。
/** 該程序用來模擬linux的echo,輸入后面帶的參數 */ func main() {args := os.Argsjoin := strings.Join(args[1:], " ")fmt.Println(join) }找出重復行
有點像unix的dup,輸出輸入中次數大于1的行。
方式1(map+scanner)
第一個實現方式使用map+scanner實現。
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包下的掃描器,它可以讀取輸入,以行或者單詞為單位斷開。有點像java的scanner,簡直一毛一樣。
方式2(流)
第二個實現方式用流的方式讀入,并且可以讀入文件或者鍵盤鍵入的文字。(通過命令行參數控制)
大概使用一個count函數,參數1是*File,參數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)//記得關閉!!!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 }獲得多個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))} }小作業1,用io.copy將b文件讀入到控制臺
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}} }并發獲得多個URL
管道是用來go協程間通信的,這個管道很有意思,他默認需要兩個協程操作,比如一個協程對他進行數據操作了,他會阻塞直到另一個協程對他進行數據的寫入或者讀取。
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先到則會阻塞等待,如果go協程先到則會將數據寫入channel,如果main沒取,也會阻塞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) }一個Web服務器
我們這邊通過http的listen方法,啟動一個服務器
package mainimport ("fmt""net/http" )func main() {//路由http.HandleFunc("/", handler)//監聽一個端口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=/可以看到我們上面發送的get請求,我們的服務器成功的收到了。
計數器
我們為這個服務器加上了一個計數器
package mainimport ("fmt""net/http""sync" )var count int var lock sync.Mutexfunc main() {//路由http.HandleFunc("/", handler)http.HandleFunc("/count", counts)//監聽一個端口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() }第二章(程序結構)
flag包
有以下兩種常用的定義命令行flag參數的方法。
flag.Type()
基本格式如下:
flag.Type(flag名, 默認值, 幫助信息)*Type 例如我們要定義姓名、年齡、婚否三個命令行參數,我們可以按如下方式定義:
name := flag.String("name", "張三", "姓名") age := flag.Int("age", 18, "年齡") married := flag.Bool("married", false, "婚否") delay := flag.Duration("d", 0, "時間間隔")需要注意的是,此時name、age、married、delay均為對應類型的指針。
flag.TypeVar()
基本格式如下: flag.TypeVar(Type指針, flag名, 默認值, 幫助信息) 例如我們要定義姓名、年齡、婚否三個命令行參數,我們可以按如下方式定義:
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, "時間間隔")通過以上兩種方法定義好命令行flag參數后,需要通過調用flag.Parse()來對命令行參數進行解析。
支持的命令行參數格式有以下幾種:
- -flag xxx (使用空格,一個-符號)
- --flag xxx (使用空格,兩個-符號)
- -flag=xxx (使用等號,一個-符號)
- --flag=xxx (使用等號,兩個-符號)
代碼
package mainimport ("flag""fmt""strings" )var (n = flag.Bool("n", false, "忽略結尾的換行符")sep = flag.String("sep", " ", "替換輸出參數時候用的分隔符") )func main() {flag.Parse()fmt.Print(strings.Join(flag.Args(),*sep))if !*n{fmt.Println()} }可以發現我們使用flag所解析的參數*,因為flag.Bool和flag.String是返回的指針,我們需要取指針的值要用到 *
new
new用于產生某一個類型的指針,他是預定義的函數,不是關鍵字,所以如果在某一函數指定了名為new的變量,我們就用不了new函數了。
func demo(new int){//用不了new }變量的生命周期
如果是包范圍的變量,他的生命周期和我們程序的生命周期一樣。
如果是局部變量,它從被聲明創建開始,直到無法被訪問。垃圾回收器怎么知道它是否無法被訪問呢?有點像Java的GC Root,將每一個包級別的變量和每一個函數的局部變量,都當成一個源頭,以該通過指針和其他引用方式找到變量,如果變量沒有路徑能夠到達,說明無法被訪問。
即分析變量的可到達性。
變量逃逸分析
在go語言中,一般沒有發生逃逸的話,局部變量會申請棧空間,包級別的變量會申請堆空間,但是函數內的局部變量可能發生逃逸。
例如下面的情況,最終go會通過逃逸分析將d分配到堆空間上。
type Data{//... } func escape() *Data{d := &Data{}return d }包的別名
可以在函數上方對包進行一個別名,如下方例子,雖然Celsius和Fahrenheit底層都是Float64,但是他們是無法相互轉換的。
也不能一起計算,得做類型轉型。
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() {}作用域
一般會分為系統預留的值比如int,true,包級別:別入在函數外定義的var,局部:函數內或者語句塊內定義的。
go在查找變量的引用,會從內層(局部->系統)去查找,所以如果函數內定義了一個val,但是又有一個全局的val會優先使用局部變量。
對于for循環很有意思,他會創建兩個語法塊,一個是循環體的顯示語法塊,一個是隱式塊,包含了初始化的變量i,i++等。
所以他會出現一個很奇怪的現象,就是一個for里面可能有兩個i
作用域也會導致許多奇怪的問題,比如有一個包級別的屬性叫val,我們調用某個函數也會得到val,詳細見下圖。
這里我們其實想讓全局的val初始化,但是err和val都沒在函數內聲明,只能用:=,不然err會爆紅,表示找不到這個err,此時我們可以這樣
第三章(基本數據)
整數
Go具有有符號整數和無符號整數。并且每個整數都有8,16,32,64位,分別對應int8,int16,int32,int64(無符號整數 uint8,uint16,uint32,uint64).
對于n位有符號整數,他的取值范圍位-(2^(n-1) - 1) ~ 2^(n-1) - 1.
即對于int8他的取值范圍是-128~127
對于n位無符號整數,他的取值范圍是0~2^n - 1
即對于uint8他的取值范圍是0~255
由于整數有他的數值范圍,所以完全是可能溢出的,我們以范圍最小的uint8來演示一下溢出的情況。
位運算
直接看程序吧。
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++ {//統計x的二進制形式有幾個1if (x>>i)&1 != 0 {fmt.Println(i)}} }關于go的一些內置函數為什么返回有符號整數
比如go的len函數就是返回有符號整數,為什么要這么做,因為我們知道uint的數值范圍是0~?,所以按道理來說無符號整數應該恒大于等于0,那在開發中我們經常會寫出這種代碼。
for i := len(list);i >= 0;i--{//logic }假設返回的是uint,那么i也隨之變成uint,那i–一輩子不可能小于0,會陷入死循環。因此無符號整數常常只用于位運算或者算術運算符。
浮點數
go中支持兩種大小的浮點數float32,float64.
字符串
字符串是不可變的字節序列。它可以包含任何數據,包括零值字節。
例如下面的代碼,可以看到雖然str改變了,但是t持有的str的舊值仍沒有改變
由于str不可變,所以str內部的值也不能變
不可能出現str[0] = '0’這種情況。
字符串字面量
字符串的值可以直接寫成字符串字面量,形式上就是代雙引號的字節序列。
string的幾個標準包
4個標準包對字符串操作特別重要:bytes,strings,strconv,unicode
byetes包內有用于操作字節slice的方法。由于字符串不可變,因此按增量方式構建字符串會導致多次內存分配,用bytes.Buffer會更高效。
strconv包含將字符串轉換的函數,課轉換為布爾值,整數,浮點數等函數。
unicode具有判別文字符號值特性的函數,比如isDigit(是否是數字),isLetter(是否是字母)
strings包中具有一些操作字符串的函數,類似于bytes。
basename demo
首先不借助任何庫。
func basename(str string) string {//從后往前找到最后一個/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 {//從后往前找到最后一個/index := strings.LastIndex(str, "/")end := strings.LastIndex(str, ".")return str[index+1 : end] }對每三個數字就插入一個,
不用庫函數,遞歸
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:]) }模擬打印數組
有點像java的數組的tostirng,在這里是用bytes的buf接受字符串,[]和數字,這里值得注意的是fpringf的writer得傳一個指針。+
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)) }字符串轉數字
數字—》字符串
i := 1 s1 := fmt.Sprintf("%d", i) s2 := strconv.Itoa(i)字符串–》數字
atoi, err := strconv.Atoi("123") parseInt, err := strconv.ParseInt("123", 10, 64)parseInt第二個參數代表進制,第三個參數代表位數。
第四章(復合數據類型)
數組
數組的長度是固定的,所以在go中很少使用數組。更多的會使用切片。
var a [3]int var a [3]int = {1,2,3} a := [...]int{1,2,3}這里可以注意,如果此時用print(“%T\n”,a) 得到的類型會是[3]int,但是[3]int和[4]int是不一樣的。
如果數組的元素是可以比較的,那么數組也是可以比較的。
因為數組如果容量過大,作為方法的參數,會復制一個副本,這是十分消耗資源的。此時可以用指針,傳的就是引用。
切片
切片是一種可變長度的序列。切片中的類型是唯一的,例如[]T,切片中都是T類型。
切片有三個屬性,指針,長度,容量。長度是指slice中的元素的個數,它不能大于容量。容量是一般是第一個到最后一個元素的長度。
但是值得注意的是切片不能用==來比較,對于字節切片,可以直接調用bytes.equal,但是其他的就需要我們自己寫函數來比較了。
對于切片,如何判斷是一個空的切片,要用len(切片),如果用切片==nil,會發生一種情況,比如剛剛make出來的切片,此時是沒有元素的,但是不等于nil,他本來是空的。
通過make創建切片,可以有兩種方法。
make([]int,len) make([]int,len,cap)對于第一種返回的是整個數組的引用,而第二種是數組中len元素的引用,但是cap會為以后的slice留出空間。
append
growslice
首先將當前容量進行一個double,判斷是否大于舊容量。
若小于,則說明沒有初始化,所以直接將newCap賦值給數組即可。
如果當前的容量小于1024,會進行一個double,否則會進行一個1+1/4的擴容。
由于我們調用append并不知道是否會發生內存重分配,即并不知道是返回的原數組還是新數組,所以我們一般使用append函數都會將返回的切片賦值給原來的切片。
這句話可以由下面代碼很清楚的看出來,下面代碼大概是取出一個string切片的所有空字符串
所以建議用下面這種方式
或者直接操作切片
remove掉切片中index的值
可以用copy直接覆蓋掉index后的值,然后返回len-1的切片即可,詳細可以看代碼
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鍵值對的存儲結構,其中的key和val都是可以用比較的類型,所以在加入map的時候可以通過判斷map中是否有key。
值得注意的是,我們無法取map的地址,當我們用&map[‘bob’]的時候會編譯報錯,為什么呢?
我認為是因為map中的元素并不是永久的,可能隨著map的擴容,導致該位置上的元素rehash到了其他位置,可能會讓存儲地址無效。
如何有序的遍歷map
比如我們map是map[string]int,此時有一個需求,需要按照字典的順序讀取map,如何做呢?
看下面代碼,可以先對key排序,然后根據排序后的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,如果去到一個不存在的key,會自動返回默認值,但是萬一有一個key剛好val=0呢?那他明明存在,我們的邏輯判斷成了不存在。
所以得用下面的方式判斷:
if _, ok := dict["d"]; !ok {fmt.Println("不存在") }我想要切片作為key怎么辦
上面說過,對于map來說key是需要能通過==來判斷相等的,那萬一我就想要讓切片作為key呢?
我們可以間接的完成這個需求
dict := make(map[string]int)//先創建一個string為key的map func trans(s []string)string{return fmt.Sprintf("%q",s) //將切片轉換成string類型 } func Add(list []string){dict[trans(list)] += 1 }基本上所有的需求,都可以用上面的方法完成。當然也不一定非要是字符串類型,任何可以得到想要結果的可以使用==的結構都可以。
map是支持這種的map[string]map[string]string.
結構體
結構體將零個或者多個任意類型的命名變量組合在一起成為一個聚合的數據類型。
type Book struct{Name string //...... } var b book對于b,我們可以通過 . 來取出屬性,例如b.Name = ‘Go入門’,在go中也可以對指針類型的結構體使用 .
var b *book = &Book{Name: 'aaa'} b.Name = 'bbb' //等價于(*b).Name = 'bbb'需要注意的地方
加入某個函數能夠返回結構體,必須得返回結構體指針才可以對結構體的屬性進行操作,否則會找不到變量
正解:
結構體無法包含他自己,但是可以是指針
當我們學習算法的時候,經常需要自己手寫一個簡單的二叉樹數據結構。下方會報錯
換成指針即可。
寫一個小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}) }如果結構體屬性首字母是小寫,別的包無法引用
比如下面這種情況
a,b都是不可導出的,雖然上面代碼沒有顯示的引用a,b,但是他們被隱式的引用了,這也是不允許的。
結構體之間的比較
如果結構體的所有屬性都是可以比較的,那么結構體也是可以比較的。
既然結構體是可以比較的,說明結構體是可以當成map的key的。
匿名成員
比如我們模擬一下人類這個類。
如果我們把所有屬性寫在一個結構體,例如
type Human struct {Name stringAge intSchoolName stringWorkPlace string }這樣結構會比較不清晰,我們可以把一些部分抽出來。
type Human struct {Name stringAge intSchoolWork } type School struct {SchoolName string } type Work struct {WorkPlace string }Go允許我們定義不帶名字的結構體成員,只需要指定他的類型。這些結構體成員叫做匿名成員。
第五章(函數)
在GO中是值傳遞,即如果實參傳入的是非指針的類型,函
數會創建一個副本,對副本進行修改不會影響到本體,但是如果是引用類型,那么函數使用形參可能會間接修改本體。
異常
函數所發生的異常我們必須考慮,有以下幾個方法
1.遇到異常,返回異常打印異常
if err != nil{return nil,fmt.Errorf("%v",err) }2.設置超時時間并且重試
const timeount = 1 * time.Minute deadline := time.Now().Add(timeout) //重試邏輯 for tris:= 0;time.Now().Before();treis++{//邏輯if err == nil{break;}log.printf("err")//等待一定時間,指數退避策略time.Sleep(time.Second << uint(tries)) }3.輸出錯誤,優雅的結束
if err != nil{fmt.Fprintf(os.Stderr,"err : %v",err)os.exit(1) }4.日志記錄,繼續運行
if err != nil{log.printf(os.Stderr,"err : %v",err) }匿名函數
首先得知道什么是匿名函數,一般的函數是這樣的
func 函數名(...)(返回)匿名函數就是沒有函數名,例如
func (...)(返回)閉包
Go 語言支持匿名函數,可作為閉包。匿名函數是一個"內聯"語句或表達式。匿名函數的優越性在于可以直接使用函數內的變量,不必申明。
以下實例中,我們創建了函數 getSequence() ,返回另外一個函數。該函數的目的是在閉包中遞增 i 變量,代碼如下:
課程拓撲排序(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),不然在匿名函數中無法調用自己。
函數的作用域陷阱
看看下面這段代碼,大概意思是先打印所有的tempdir,然后會有一個匿名函數,匿名函數的作用也是打印位于循環內結構的d。看似沒什么問題把,看看打印結果
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為啥匿名函數的輸出全是112?
func的輸出似乎都被for的最后一個元素給覆蓋了,為什么?
因為我們知道閉包可以用上面的局部變量,可以知道當我們在下面的for循環中使用的局部變量是for循環中最后一次的。
正確的代碼?
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()} }變長函數
變長函數可以接受長度變化的參數。
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}...) }延遲調用
defer語句可以用來調用一個復雜的函數,即在函數的入口和出口設置調試行為。
當下面代碼:
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函數的延時調用,一定不要忘記defer的函數后面要帶個小括號。
如果沒帶就不會調用返回的函數,而是會在slow函數結束后直接調用trace。
再看另外一個例子。
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了一個函數,但仍然等到了double結束了才進入函數,所以可以推斷出,如果defer的函數會返回一個函數,先回進入函數取到那個返回的函數,然后等待主程序return,去延時調用返回函數,才會出現上面日志記錄的情況。
第六章(方法)
方法
func f(...)(返回) //普通方法 func (f *function)f(...)(返回) //方法屬于function?
總結
以上是生活随笔為你收集整理的自己读Go程序设计语言的一些总结(更新ing...)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Go程序设计语言 1.1 hello,w
- 下一篇: 《Go程序设计语言》- 第1章:入门