Go 語言程序設計-筆記

第一章五個程序

都很好!
但是初學編程/沒有其他語言基礎的不容易看懂。

記一遍不熟悉的東西:

  1. who = strings.Join(os.Args[1:], " ")
    //一個string類型sliece和一個分隔符為參數(shù)輸入,返回一個分隔符把slice中所有字符串連接在一起的新字符串
    //把slice中所有元素拼接成一個字符串的神器

  2. //os.Args[0]存放的是程序名字,可以打印看看

  3. //filepath.base 返回傳入路徑的基礎名=文件名
    //Base函數(shù)返回路徑的最后一個元素。在提取元素前會去掉末尾的路徑分隔符。如果路徑是"",會返回".";如果路徑是只有一個斜桿構成,會返回單個路徑分隔符。

  4. os.Exit(1)
    //0正確,非0 錯誤

  5. stacker的例子是很好的鴨子類型的例子,棧先進后出,

func (stack *Stack) Pop() (interface{}, error) {
    theStack := *stack
    if len(theStack) == 0 {
        return nil, errors.New("can't Pop() an empty stack")
    }
    x := theStack[len(theStack)-1]
    *stack = theStack[:len(theStack)-1]
    return x, nil
}

//receiver類型是*Stack,因為要修改原stack,theStack,中間量

  1. 文件讀寫,截一段代碼
func americanise(inFile io.Reader, outFile io.Writer) (err error) {
    reader := bufio.NewReader(inFile)
    writer := bufio.NewWriter(outFile)
    //一個延遲函數(shù),等緩沖區(qū)的內(nèi)容全部完成讀寫
    defer func() {
        if err == nil {
            err = writer.Flush()
        }
    }()
    //定義replacer是為了不重復命名err如果這里不給replacer,下一句就是if replacer, err:= 這樣err就是個影子變量,因為返回值那里已經(jīng)命名過了
    var replacer func(string) string
    if replacer, err = makeReplacerFunction(britishAmerican); err != nil {
        return err
    }
...(未完)

//這個程序還用了閉包,閉包可以保留之前的狀態(tài),從而把之前的文檔中詞語做特換

  1. goroutine
    polarCoord := <-questions這里阻塞

第二章 布爾&數(shù)值類型

  1. _不是新值,不初始
  2. 二元邏輯符 短路邏輯,比如a||b,a為真,b不用判斷
  3. slice不能比較,可以通過reflect.DeepEqual()完成
  4. 類型轉換string(a),CONST
  5. byte==uint 8
  6. 異或(XOR),不同為1,相同為0,與非(ANDNOT),先與后非
  7. 大整數(shù),math/big包,這里對應用開發(fā)來說不是重點,可以略過,需要用的地方再回來查
  8. statistics的例子,用到了net/http包里的一些方法,,homepage里用到,書里講解很詳細,好評。

第三章 字符串

  1. 字符串只包含7位的ASCII字符,才能被字節(jié)索引,不過go的一個字符一個字符迭代更實用
//一個無聊的測試,可運行
func main() {
    fmt.Println("Hello, playground")
    a := "abcdefg"
    //a是只有ASCII字符的string,可以直接用索引的方式得到每一個字符
    //系統(tǒng)會把string按[]byte來處理,所以直接輸出a[1]會得到一個0-255之間的數(shù)字
    //string()強轉了a[1]的類型,所以可以輸出b
    fmt.Println("hi,a", string(a[1]))

    //difference存儲的是中文,中文一個字符可能不止存在一個索引指向的位置上,所以雖然difference只有8字符,但是索引大于7的地方也可能存了值
    difference := "皇家葫蘆娃,go"
    fmt.Println("difference[8]", string(difference[8]))
    for i, qw := range difference {
        //用索引取值不能輸出中文字符,直接輸出value可以,
        fmt.Println("hi,", string(difference[i]))
        fmt.Println("hi中文,", string(qw))
    }
//非ASCII轉成rune類型可以直接通過索引輸出漢字
    r := []rune(difference)
    for i := 0; i < len(r); i++ {
        fmt.Println("r[", i, "]=", r[i], "string=", string(r[i]))
    }   
}
  1. “可解析字符串字面量”、`原生字符串字面量(可包含任何字符)`
  2. strconv.Itoa(i), 返回int類型i的字符串表示 和一個錯誤值,如果i=65, 返回“65”,nil
  3. 一個以個的方式追加字符串
var buffer bytes.Buffer
for{
  if piece,ok:=getNextValidString(); ok{
    buffer.WriteString(piece)
  }else{
break
}
}
fmt.Print(buffer.String(),"\n")
  1. 講fmt包,有個講包的github也講了這個,里面有一段

為避免以下這類遞歸的情況:
type X string
func (x X) String() string { return Sprintf("<%s>", x) }
需要在遞歸前轉換該值:
func (x X) String() string { return Sprintf("<%s>", string(x)) }
print等,輸出到控制臺
Fprint等,輸出到一個writer
Sprint等,輸出到一個字符串

  1. bufio.Reader.ReadString('\n')讀,遇到\n停
  2. 調(diào)試可能會用到的格式化輸出方法:
i:=5
fmt.Printf("|%p->%d|",&i,i)
//結構體
fmt.Printf("%v\n",[]float64{1.00,2.00,3.00})
fmt.Printf("%#v\n",[]float64{1.00,2.00,3.00})
//輸出結果:|address->5|
//[1.00,2.00,3.00]
//[]float64{1.00,2.00,3.00}加上#修飾符,可以輸出結構體類型那部分(map也可以),行話叫:以編程的形式輸出Go語言代碼
//%q適合輸出slice,會使每個字符串都是可識別的
  1. strings包,很好玩的一個包
    舉個??
fmt.Printf("%q\n", strings.SplitAfterN("a,b,c", ",", 2))
//Output:["a," "b,c"]返回的slice最多有2個string
  1. 進階版分割,可以用
//非符號的字段,一段一段拿出來
f := func(c rune) bool {
    return !unicode.IsLetter(c) && !unicode.IsNumber(c)
}
fmt.Printf("Fields are: %q", strings.FieldsFunc("  foo1;bar2,baz3...", f))
Output: Fields are: ["foo1" "bar2" "baz3"]
  1. string.replac(要執(zhí)行replace的string,“被替換string”,“替換成string”,“-1”) -1表示全部替換,其他數(shù)字表示替換幾次;strings.Repeat 畫三角塔神器,strings里很多方法都經(jīng)常用,不過也很簡單,不寫了,用的時候去查庫就可以
  2. strconv所有strconv轉換函數(shù)都是返回一個結果,一個error,轉換成功,結果為nil
  3. unicode ,utf8,regexp(compile,為了安全,編譯通過返回一個*regexp)馬住,用到再來

第四章 集合類型

  1. chan,func,method,map,slice 引用
  2. 指針,*
z:=17
pi:=&z
ppi:=&pi
fmt.Print(z,*pi,*ppi)
**ppi++
//==(*(*ppi))++, *(*ppi)++
fmt.Print(z,*pi,*ppi)
//output:171717
181818
  1. *也是類型修飾符,第一張stack那里有解釋過,那里是指一個結構體指針,用的時候是不需要解引用的,很方便
  2. new(type)==&type{(可以直接初始化賦值)}
    這兩種都分配一個type類型的空值,同時返回一個指向該值的指針
  3. 添加一個子切片:
s:=append(s,t...)添加slice t中所有值
s:=append(s,u[2,5]...)添加一個子切片
//把一個字符串字節(jié)添加到一個字節(jié)切片中
a:="abc"
b:=[]byte{'u','v'}
b=append(b,a...)
  1. 一個黑科技(我知道是我沒見過世面,不過很神奇?。?/li>
type Product struct {
    name string
}
func (product Product) String() string {
    return fmt.Sprintf("%s", product.name)
}
func main() {
    products:=[]*Product{{"a"},{"b"},{"c"}}
    fmt.Println(products)
//這里會調(diào)用String方法,雖然傳入的是個*Product類型的slice,但是打印的時候會解引用,把整個slice打出來
}
  1. 在src slice的index位置插入insert slice
func InsertStringSlice(src,insert []string,index int) []string{
  retrun append(slice[:index],append(insertion,slice[index:]...)...)
}
  1. 刪除slice中間的幾個元素s:=append(s[:1],s[5:]...)刪除了s1:5
  2. sort.Search的用法here
    官網(wǎng)例子很好玩,看書看累了可以試一試,嗯。
func GuessingGame() {
    var s string
    fmt.Printf("Pick an integer from 0 to 100.\n")
    answer := sort.Search(100, func(i int) bool {
        fmt.Printf("Is your number <= %d? ", i)
        fmt.Scanf("%s", &s)
        return s != "" && s[0] == 'y'
    })
    fmt.Printf("Your number is %d.\n", answer)
}

第五章 過程式編程

核心思想是介紹了go的語法,突出展示了go的簡介和第一章說過的一些特性

  1. 要避免使用影子變量,比如for循環(huán)中聲明的變量不要和外面的同名
  2. 類型轉換,提到很多次了string(a)
  3. 斷言:resultOfType, boolean:=expression.(Type)安全類型斷言;resultOfType:=expression.(Type)非安全型斷言,失敗會panic()
  4. switch后沒有跟表達式,編譯器會默認將其設置為true;類型開關用法:switch x.(type)
  5. 萬能for,用法簡單,其他筆記里記過了,,下一個
  6. 并發(fā),go func(),這個func是個已有函數(shù)或是臨時創(chuàng)建的匿名函數(shù),被調(diào)用的時候會立即執(zhí)行,但是在另一個goroutine,(有點類似多線程),當前的goroutine執(zhí)行會從下一條語句中立即恢復。所以,執(zhí)行一個go語句之后肯定至少有兩個goroutine在并發(fā)執(zhí)行。
    ??
func main() {
    ca := createCounter(2)
    cb := createCounter(102)
    for i := 0; i < 5; i++ {
        a := <-ca
        fmt.Printf("A %d \nB %d \n", a, <-cb)
    }
}
func createCounter(start int) chan int {
    next := make(chan int)
    go func(i int) {
        for {
            next <- i
            i++
        }
    }(start)
    return next
}
//輸出是a 2 b102 a3 b103 ... a6 b106
  1. select,哪個chan通就執(zhí)行哪個,很類似switch
  2. panic和err,總的來說,如果是邏輯錯誤,或者是希望一旦有錯就強制崩潰,用panic,配合recover食用效果更佳;err的話是首先想確保程序健康執(zhí)行,(公司大牛專門講過:是程序員的問題,panic;啟動的時候可以panic;邏輯問題不知道為什么出錯,panic;想不清楚要不要panic就不要panic,┑( ̄Д  ̄)┍;數(shù)據(jù)錯誤不要panic
  3. 自定義函數(shù)講了一下參數(shù)和返回值的基本寫法和比較優(yōu)雅的寫法,變成很有用看起來,然而我至今沒用到過
  4. init和mian,?可以不傳參沒有返回值,導入包的時候會執(zhí)行init然后main,如果導入包的時候用_當別名,那就是只執(zhí)行導入包的init函數(shù)
  5. 閉包,可以保留和他在統(tǒng)一作用于的變量的狀態(tài)和值,所有的匿名函數(shù)都是閉包。另一種形式是return一個閉包函數(shù)這樣的寫法真的是看多了就好了,好處是這個閉包里是可以使用return所在的函數(shù)體的一些變量的。
  6. 遞歸,除了大家熟悉的Fibonacci數(shù)以外還有個好玩的
//判斷一個單詞是否是一個回文詞,比如PULLUP,ROTOR
func IsPalindrome(word string) bool {
  if utf8.RuneCountInString(word)<=1{
  return true
  }
first, sizeOfFirst:=utf8.DecodeRuneCountInString(word)
last,sizeOfLast:=utf8.DecodeLastRuneCountInString(word)
if first!=last{return false}
return IsPalindrome(word[sizeOfFirst:len(word)-sizeOfLast])
}
  1. 動態(tài)函數(shù),其實也是init的一種用法,init中可以做一個判斷并協(xié)議中處理方式,如果符合,有限進行該處理,不符合就在包里繼續(xù)找方法

  2. 泛型函數(shù),返回一個interface{},然后通過調(diào)用的時候斷言:example().(type)從而把我們需要的值轉為我們需要的值,不過是很麻煩的一種寫法

  3. switch的一個小技巧,如果每個case中有重復代碼,可以直接在switch處加上一個if change,每個case上設置一個bool,eg:change=true

差不多就到這里了,struct和interface都是項目里特別常用的,看書好像不如實戰(zhàn)效果好。

----分隔符----

第六章

  1. go不支持重載方法,不可以定義兩個名字相同但是簽名不同的方法,但可以設置變參,這樣方法相當于可用性更高
  2. 可以自定義類型,給自定義的類型自定義方法
  3. 可通過結構體嵌套方式實現(xiàn)類似繼承的功能
  4. 注意傳遞值還是傳遞指針,傳遞指針才能改變原始數(shù)據(jù)的值,另外傳指針比傳值更高效
  5. 方法表達式(6.2.1.2)這里沒看懂,高人私我
  6. New***這類的方法可以看做是顯式的構造函數(shù),用來確保可以生成一個合法的某類型的實例
  7. 接口:聲明一個或多個方法,完全抽象,不能實例化,但可以創(chuàng)建一個實現(xiàn)了該接口的類型,這個類型可以被實例化。命名最好以er結尾(傳統(tǒng))
    *第六章這種講解方式有點看不下去。。。我 去第七章了,我慚愧

第七章

  1. main就是一個goroutine
  2. 小坑1:需要注意主goroutine退出的同時其他goroutine也會退出,所以我們得先確定其他goroutine已經(jīng)退出了,再退出主goroutine
  3. 小坑2:可能發(fā)生死鎖,用通道即可避免
  4. 解決方法1:常見的是讓主goroutine在一個done的channel上等,根據(jù)接收工作是否完成來覺得退出的時間
  5. 解決方法2:sync.WaitGroup,讓每個goroutine報告自己的完成狀態(tài),但是sync.WaitGroup本身也可能會產(chǎn)生死鎖,特別是當所有工作的goroutine都處于鎖定狀態(tài)的時候調(diào)用sync.WaitGroup.Wait()
  6. channel為并發(fā)運行的goroutine之間提供了一種無鎖通信方式,當一個通道發(fā)生通信時,發(fā)送通道和接受通道都處于同步狀態(tài)
  7. 單向通道,比如只為了傳遞參數(shù)的時候
chan<- //類型是只允許發(fā)送
<-chan //類型是只允許接收
  1. channel里發(fā)生boolean, int, float64都是安全的,因為都是值傳遞,復制的方式,string也安全,因為go不允許改string
  2. 指針和引用類型不行,不安全,對這類值必須是串行訪問,可以使用互斥量;或者是設定一個規(guī)則,一個指針或一個引用發(fā)送之后發(fā)送方就不會再訪問他,讓接受者來訪問和釋放指針或是引用的值;再或者讓所有導出方法不能修改指針和引用的值,可以修改的都不能導出。但比如*regexp.Regexp可以被多個goroutine訪問,因為這個指針指向的值的所有方法都不會改變這個值的狀態(tài)
  3. 一段簡單的代碼,
go func(){
  for _,job:=range jobList{
  jobs<-job  //阻塞,等待channel里有值傳過來
}
close(jobs)
}()
  1. 一般發(fā)送端關閉channel而不是接收端
  2. 一段棒棒的代碼,
func waitAndProcessResults(done <-chan struct{}, results <-chan Result) {
    for working := workers; working > 0; {
        select { // Blocking
        case result := <-results:
            fmt.Printf("%s:%d:%s\n", result.filename, result.lino,
                result.line)
        case <-done:
            working--
        }
    }
DONE:
    for {
        select { // Nonblocking
        case result := <-results:
            fmt.Printf("%s:%d:%s\n", result.filename, result.lino,
                result.line)
        default:
            break DONE
        }
    }
}
  1. 后面講的是幾個safeMap的例子,以代碼講解為主,就不記了,書上講解詳細的很

第八章

(之前寫過一點文件處理的代碼,是archive和unarchive,當然也涉及到讀寫,所以會跳過這部分相關的內(nèi)容,記相對不太熟悉的內(nèi)容)

  1. 主要在講encode/json包func Marshal(v interface{}) ([]byte, error)func Unmarshal(data []byte, v interface{}) error的用法

  2. 額,沒其他了好像。。

事實證明struct和interface包括func這類都適合邊做項目邊學或者邊看項目邊學,比干看書效率高一點,當然書上的例子也不錯,跟著看或者邊看邊做也會很好。

所以此篇應該完結了,二刷這本書的可能性不大,畢竟書是借閱的。┑( ̄Д  ̄)┍

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容