Golang筆記--基礎(chǔ)語法

Golang基礎(chǔ)語法

[TOC]

一個大的程序是由很多小的基礎(chǔ)構(gòu)件組成的怖亭。變量保存值,簡單的加法和減法運算被組合成較復雜的表達式√铬耍基礎(chǔ)類型被聚合為數(shù)組或結(jié)構(gòu)體等更復雜的數(shù)據(jù)結(jié)構(gòu)攀细。然后使用if和for之類的控制語句來組織和控制表達式的執(zhí)行流程箫踩。然后多個語句被組織到一個個函數(shù)中,以便代碼的隔離和復用谭贪。函數(shù)以源文件和包的方式被組織

程序結(jié)構(gòu)

命名

  • Go命名規(guī)則:一個名字必須以一個字母(Unicode字母境钟,所以中文也可)或下劃線開頭,后面可以跟任意數(shù)量的字母俭识、數(shù)字或下劃線
  • Keyword: 不能用于自定義名字
break      default       func     interface   select
case       defer         go       map         struct
chan       else          goto     package     switch
const      fallthrough   if       range       type
continue   for           import   return      var
  • 預定義的名字: 可重新定義
內(nèi)建常量: true false iota nil

內(nèi)建類型: int int8 int16 int32 int64
          uint uint8 uint16 uint32 uint64 uintptr
          float32 float64 complex128 complex64
          bool byte rune string error

內(nèi)建函數(shù): make len cap new append copy close delete
          complex real imag
          panic recover
  • 頭字母的大小寫決定了名字在包外的可見性: 大寫開頭(函數(shù)外定義)可以被外部的包訪問
  • Go語言風格是盡量使用短小的名字,尤其局部變量, 個人認為如影響理解則用具有意義的長命名
  • Go語言程序員推薦使用駝峰式命名,縮寫全大寫
const lowerhex = "0123456789abcdef"
//QuoteRuneToASCII ...
func QuoteRuneToASCII(r rune) string

func appendQuotedRuneWith(buf []byte, r rune, quote byte, ASCIIonly, graphicOnly bool) []byte
  • Go lint工具可幫助檢測命名是否合規(guī)

聲明

聲明語句定義了程序的各種實體對象以及部分或全部的屬性. Go語言主要有四種類型的聲明語句:var慨削、const、type和func套媚,分別對應(yīng)變量缚态、常量、類型和函數(shù)實體對象的聲明

  • Go源文件以go作為后綴, 以包聲明開始
  • 之后import 導入依賴的包
  • 包級的類型堤瘤、變量玫芦、常量、函數(shù)的聲明本辐,無順序(函數(shù)內(nèi)必須先聲明)
package main

// 單行
// import "time"
// import log "github.com/sirupsen/logrus"
// 或者:
import(
    "time"
    // third-party包, 別名: log
    log "github.com/sirupsen/logrus"
)

const version = "0.0.1"

// Printer is a exported struct
type Printer struct {
    name string
}

//Print is a exported method
func(p *Printer) Print(){
    _ = printTime(time.Now()) //nolint
}

// 函數(shù)
func printTime(t time.Time) error{
    log.Info("now time: ", time.Now(), " version: ", version)
    return nil
}

// 主函數(shù)
func main() {
    var printer Printer
    printer.Print()
}
  • 函數(shù)的聲明
    • func 關(guān)鍵字
    • 函數(shù)名字:printTime
    • 形參列表(變量名 變量類型, 可選, 由調(diào)用者提供實參): t time.Time
    • 返回值列表(可選, 多個需用括號): error
    • 函數(shù)體, 花括號內(nèi): {...}
    • struct方法還包含receiver: (p *Printer)

變量

  • var 變量名字 類型 = 表達式, 未提供初始值則自動用零值初始化: var printer Printer
  • 簡潔方式,冒號等號(無冒號則為賦值操作): name := "tester", printer := Printer{}
  • 多個變量:
var i,j,k int
var b, f, s = true, 2.3, "four" // bool, float64, string
var f, err = os.Open(name)  //函數(shù)返回多個值
i, j := 0, 1
// 無冒號則為賦值操作
i, j = j, i // 交換 i 和 j 的值

指針變量

  • 一個指針的值是另一個變量的地址桥帆,
  • 通過指針医增,我們可以直接讀或更新對應(yīng)變量的值
  • 對于var x int聲明的變量x, 那么&x(取x變量的內(nèi)存地址)將產(chǎn)生一個指向x的指針
  • 該指針對應(yīng)的數(shù)據(jù)類型是 *int
  • 指針零值都是nil
  • 返回函數(shù)中局部變量的地址也是安全的(自動垃圾回收機制)
  • 指針示例:標準庫中flag包的關(guān)鍵技術(shù),它使用命令行參數(shù)來設(shè)置對應(yīng)變量的值
package main

import (
    "flag"
    "fmt"
    "strings"
)

// flag.Bool函數(shù)會創(chuàng)建對應(yīng)標志參數(shù)的變量:
// 三個屬性:名字“n”老虫,默認值(這里是false)叶骨,最后是描述信息
var n = flag.Bool("n", false, "omit trailing newline")
var sep = flag.String("s", " ", "separator")

func main() {
    flag.Parse()
    fmt.Print(strings.Join(flag.Args(), *sep))
    if !*n {
        fmt.Println()
    }
}
  • new內(nèi)置函數(shù)
    • 語法糖, 表達式new(T)將創(chuàng)建一個T類型的匿名變量
    • 初始化為T類型的零值
    • 返回變量地址,返回的指針類型為*T

變量生命周期和作用域

  • 包變量貫穿整個程序運行周期(運行時)
  • 部變量是動態(tài)的, 聲明到不再被引用
  • 作用域是指源代碼中可以有效使用名字的范圍(編譯時概念)
  • 句法塊是由花括弧所包含的一系列語句,塊內(nèi)局部變量不能被塊外部訪問
  • 內(nèi)置類型(int,float等),內(nèi)置函數(shù), 常量等作用域全局张遭,任何地方可用
  • 控制流標號邓萨,就是break、continue或goto語句后標號菊卷,則是函數(shù)級的作用域

賦值

  • 使用=號, 復合賦值: x *= scale 相當于 x = x * scale
  • 元組賦值: x, y = y, x 交換x,y
  • 多個返回值賦值
    • f, err = os.Open("foo.txt")
    • 這類函數(shù)會用額外的返回值來表達某種錯誤類型或bool判斷
    • 用下劃線空白標識符_來丟棄不需要的值
// 后續(xù)了解
v, ok = m[key]             // map lookup
v, ok = x.(T)              // type assertion
v, ok = <-ch               // channel receive

v = m[key]                // map查找缔恳,失敗時返回零值
_, exists := m[key] // _占位
  • 可賦值性
    • 隱式賦值: medals := []string{"gold", "silver", "bronze"}
    • 只有右邊的值對于左邊的變量是可賦值的,賦值語句才是允許的

類型

  • type 類型名字 底層類型
  • 命名類型還可以為該類型的值定義新的行為(方法集)
  • 對于類型T, 都有一個對應(yīng)的類型轉(zhuǎn)換操作T(x), 若T為指針可能還需要小括號: (*int)(0)
  • string []byte可轉(zhuǎn)換洁闰,數(shù)值可轉(zhuǎn)換
type Celsius float64    // 攝氏溫度
type Fahrenheit float64 // 華氏溫度

func(c Celsius) String() string{
    return fmt.Sprintf("%g°C", c)
}
var c Celsius
var f Fahrenheit
fmt.Println(c == 0)          // "true"
fmt.Println(f >= 0)          // "true"
fmt.Println(c == f)          // compile error: type mismatch
fmt.Println(c == Celsius(f)) // "true"!

控制流結(jié)構(gòu)

gpl并沒有專門章節(jié)講解基本控制流, 這里簡單列舉下吧.

  • if條件語句, 跟c/c++比條件不需要括號
// condition 為真歉甚,否則
if condition {
    ...
} else {
    ...
}

// 慣用一
if ok:= function(); ok {
 ...   
}

// 慣用二, 判定是否出錯
if val, err:= function(); err!=nil {
    ...
}

  • switch語句
switch x := 0; x {
case 0:
    fmt.Println(0)
case 1:
    fallthrough
default:
    fmt.Println("other")
}

// 不止是整型
switch coinflip() {
case "heads":
    heads++
case "tails":
    tails++
default:
    fmt.Println("landed on edge!")
}

// 無tag, 表達式
func Signum(x int) int {
    switch {
    case x > 0:
        return +1
    default:
        return 0
    case x < 0:
        return -1
    }
}
  • 循環(huán)語句, 不用小括號, 其他跟C/C++類似

i := 0
for ; i < 10; {
    i++
}

// or
for i:=0; i<10; i++ {
}

// 無限循環(huán)
for {
}

//slice、數(shù)組的range迭代
for i,value:=range someSlice {
    //...
}

//map range迭代
for k,v:=range someMap {
    ...
}

  • goto, breakcontinue,
// goto語句可以無條件地轉(zhuǎn)移到過程中指定的行
if condition {
    goto End
}
End:
   close(xxx)

//跳出內(nèi)層循環(huán)扑眉,不在執(zhí)行循環(huán)
for {
    if condition {
        break
    }
}

// continue 繼續(xù)下一次迭代
for {
    if condition {
        continue
    }
    // other states  skipped
}

// 跳出外層循環(huán), 使用Label

OutLoop:
for {
    for i:=0; i<10;i++ {
        if condition {
            break OutLoop
        }
    }
}

  • select 多路復用,在select阻塞, 隨機選擇一個消息到達的case執(zhí)行,詳細見后續(xù)并發(fā)章節(jié)
//for-select
Loop:
for {
    select {
        case v, ok:=<-someChan:
            if !ok {
                break Loop
            }
            //...
        case time.After(time.Second):
        break Loop
    }
}

包和文件

  • 包是為了支持模塊化纸泄、封裝、單獨編譯和代碼重用
  • 每個包都對應(yīng)一個獨立的名字空間, 引用時加包名: fmt.Println
  • 名字大寫字母開頭是從包中導出, 外部可調(diào)用
  • 文件以package xxx開頭, xxx包
  • 導入包:
import "fmt"
// or
import (
  "io"
  "time"
  cql "github.com/sylladb/gocqlx" // alias
  "golang.org/x/net/ipv4"
  _ "net/http"
)
  • 包初始化解決包級變量的依賴順序, 按聲明順序初始化, 多個文件按字母序發(fā)給編譯器
  • 包初始化函數(shù):func init(), 每個源文件可定義多個(建議1個), 不能被用戶調(diào)用或引用
  • 在解決依賴情況下以導入聲明的順序初始化, main包最后初始化
  • 命名盡量簡單,用單數(shù)(標準庫errors, bytes,strings,go/types是為了避免與預定義類型或關(guān)鍵字沖突)
  • 不推薦直接使用util這種容易和變量沖突的包名, 如標準庫使用imageutil,ioutil
  • go list std | wc -l查看標準包數(shù)目

基礎(chǔ)數(shù)據(jù)類型

數(shù)字腰素、字符串和布爾型聘裁。復合數(shù)據(jù)類型——數(shù)組結(jié)構(gòu)體

整型

  • 算術(shù)、邏輯和比較運算符(按優(yōu)先級遞減)
*      /      %      <<       >>     &       &^
+      -      |      ^
==     !=     <      <=       >      >=
&&
||
  • 一元加減法(正負號)
+      一元加法 (無效果)
-      負數(shù)
  • 位操作符
&      位運算 AND
|      位運算 OR
^      位運算 二元操作符 XOR, 一元操作符為取反
&^     位清空 (AND NOT)
<<     左移
>>     右移

浮點數(shù)

  • math.MaxFloat32表示float32能表示的最大數(shù)值弓千,大約是 3.4e38
  • math.MaxFloat64常量大約是1.8e308
  • %g, %f, %e(帶指數(shù)) fmt.Printf("%8.3f\n", math.Exp(float64(x)))
  • math.IsNaN()
  • 正無窮大和負無窮大衡便,分別用于表示太大溢出的數(shù)字和除零的結(jié)果;還有NaN非數(shù)洋访,一般用于表示無效的除法操作結(jié)果0/0或Sqrt(-1)
var z float64
fmt.Println(z, -z, 1/z, -1/z, z/z) // "0 -0 +Inf -Inf NaN"

復數(shù)

提供兩種精度復數(shù): complex64complex128

布爾型

不能直接和整型0, 1轉(zhuǎn)換

字符串

  • 一個字符串是一個不可改變的字節(jié)序列
  • 文本字符串通常被解釋為采用UTF8編碼的Unicode碼點(rune)序列
  • len函數(shù)可以返回字符串中的字節(jié)數(shù)目(不是rune字符數(shù)目, rune是int32等價類型)
  • 利用UTF8編碼, UTF8是一個將Unicode碼點編碼為字節(jié)序列的變長編碼(1-4Bytes)
  • 轉(zhuǎn)義,\uhhhh對應(yīng)16bit的碼點值镣陕,\Uhhhhhhhh對應(yīng)32bit
import "unicode/utf8"

w := "世界"
// "\xe4\xb8\x96\xe7\x95\x8c"
// "\u4e16\u754c"
// "\U00004e16\U0000754c"



s := "Hello, 世界"
fmt.Println(len(s))                    // "13"
fmt.Println(utf8.RuneCountInString(s)) // "9"

for i := 0; i < len(s); {
    r, size := utf8.DecodeRuneInString(s[i:])
    fmt.Printf("%d\t%c\n", i, r)
    i += size
}
fmt.Println(string(65))     // "A", not "65"
fmt.Println(string(0x4eac)) // "京"

標準庫中有四個包對字符串處理尤為重要:bytes、strings姻政、strconv和unicode包

  • 數(shù)字字符串轉(zhuǎn)換, strconv
import "strconv"

x := 123
y := fmt.Sprintf("%d", x)
fmt.Println(y, strconv.Itoa(x)) // "123 123"

x, err := strconv.Atoi("123")             // x is an int
y, err := strconv.ParseInt("123", 10, 64) // base 10, up to 64 bits
  • 字符串處理函數(shù), strings: Contains, Split

常量

  • const pi = 3.14159265358979323846264338327950288419716939937510582097494459
  • 常量聲明可以使用iota常量生成器初始化
const (
    _ = 1 << (10 * iota)
    KiB // 1024
    MiB // 1048576
    GiB // 1073741824
    TiB // 1099511627776             (exceeds 1 << 32)
    PiB // 1125899906842624
    EiB // 1152921504606846976
    ZiB // 1180591620717411303424    (exceeds 1 << 64)
    YiB // 1208925819614629174706176
)

復合數(shù)據(jù)類型

數(shù)組

  • 元素個數(shù)明確指定, 可以用省略號(由初始化值個數(shù)決定)
var a [3]int
var a [...]int={1,2,3}
r := [...]int{99: -1} //100 items
  • 實際示例: crypto/sha256包的Sum256函數(shù)對一個任意的字節(jié)slice類型的數(shù)據(jù)生成一個對應(yīng)的消息摘要呆抑。消息摘要有256bit大小,因此對應(yīng)[32]byte數(shù)組類型

切片slice

  • slice(切片)代表變長的序列: []T,不指定元素個數(shù)
  • 一個slice是一個輕量級的數(shù)據(jù)結(jié)構(gòu)汁展,提供了訪問數(shù)組子序列
    • 切片操作s[i:j], [3:], [:3], [:]所有元素
  • 內(nèi)置函數(shù)make, 創(chuàng)建一個匿名數(shù)組鹊碍,返回一個slice
  • cap容量
  • 內(nèi)置函數(shù)append, 向slice追加元素, 可用于nil, 可追加多個元素,甚至追加一個slice
  • 兩slice不能直接比較相等, bytes.Equal函數(shù)判斷兩個字節(jié)型slice是否相等([]byte)
make([]T, len)
make([]T, len, cap) // same as make([]T, cap)[:len]

// append
var s,ss []string
s = append(s, 'a')
ss = append(ss, s...)
  • 類似于:
type IntSlice struct {
    ptr      *int
    len, cap int
}

map

哈希表是一個無序的key/value對的集合善镰,key唯一妹萨,通過給定的key可以在常數(shù)時間復雜度內(nèi)檢索、更新或刪除對應(yīng)的value, map類型的零值是nil

// 創(chuàng)建炫欺,`make`可創(chuàng)建map
ages1 := make(map[string]int)
ages1["alice"] = 32

ages2 := map[string]int{
    "alice":   31,
    "charlie": 34,
}

// 刪除對應(yīng)key元素
delete(ages, "alice") 

// 是否存在
if _, exists:= ages2["alice"]; exists{
    fmt.Println("exists")
}

// access
ages["bob"]++

// range遍歷
for k, v:=range ages2{
    fmt.Println(k,v)
}
  • map中的元素并不是一個變量,因此我們不能對map的元素進行取址操作(因為地址會變)
  • 不能直接相等, 要判斷兩個map是否包含相同的key和value熏兄,要通過循環(huán)實現(xiàn)
  • 類似集合可以使用map[T]bool實現(xiàn)
  • map和slice參數(shù)傳引用

結(jié)構(gòu)體

  • 結(jié)構(gòu)體由零個或多個任意類型的值聚合而成, 每個值稱為結(jié)構(gòu)體的成員
  • 成員的輸入順序有意義(如以下Name, Address不同順序則為不同結(jié)構(gòu)體)
  • 考慮效率的話品洛,較大的結(jié)構(gòu)體通常會用指針的方式傳入和返回
  • 如果所有成員可比較, 則結(jié)構(gòu)體可以比較(==, !=),且可用作map的key
  • 結(jié)構(gòu)體類型的零值是每個成員都是零值(第9章sync.Mutex零值為未鎖定狀態(tài))
// 一般一行對應(yīng)一個成員树姨,也可以合并, 成員名字在前,類型在后
type Employee struct {
    ID           int
    Name,Address string
}

var dilbert Employee
  • S類型的結(jié)構(gòu)體可以包含*S指針類型的成員, 如以下二叉樹實現(xiàn)插入排序
type tree struct {
    value       int
    left, right *tree
}

// Sort sorts values in place.
func Sort(values []int) {
    var root *tree
    for _, v := range values {
        root = add(root, v)
    }
    appendValues(values[:0], root)
}

// appendValues appends the elements of t to values in order
// and returns the resulting slice.
func appendValues(values []int, t *tree) []int {
    if t != nil {
        values = appendValues(values, t.left)
        values = append(values, t.value)
        values = appendValues(values, t.right)
    }
    return values
}

func add(t *tree, value int) *tree {
    if t == nil {
        // Equivalent to return &tree{value: value}.
        t = new(tree)
        t.value = value
        return t
    }
    if value < t.value {
        t.left = add(t.left, value)
    } else {
        t.right = add(t.right, value)
    }
    return t
}
  • 結(jié)構(gòu)體字面值, 注意: 以下兩種方式不能混用, 且不能對未導出成員使用
type Point struct{ X, Y int }

// 按照順序
p := Point{1, 2}
// 指定成員名字
anim := gif.GIF{LoopCount: nframes}
  • 較大的結(jié)構(gòu)體通常會用指針的方式傳入和返回
  • 如果要在函數(shù)內(nèi)部修改結(jié)構(gòu)體成員的話,必須指針傳入, 因為Go函數(shù)傳值調(diào)用
pp := &Point{1, 2}
// 等價于
pp := new(Point)
*pp = Point{1, 2}
  • 結(jié)構(gòu)體嵌入和匿名成員, 可簡化編程, 簡單實現(xiàn)繼承, 看以下圓和輪的演進:
// 原始版本
type Circle struct {
    X, Y, Radius int
}

type Wheel struct {
    X, Y, Radius, Spokes int
}

相同屬性獨立出來, 便于維護

type Point struct {
    X, Y int
}

type Circle struct {
    Center Point
    Radius int
}

type Wheel struct {
    Circle Circle
    Spokes int
}

但是訪問繁瑣:

w.Circle.Center.X = 8
w.Circle.Center.Y = 8

結(jié)構(gòu)體內(nèi)只聲明數(shù)據(jù)類型而不指名成員名,這類成員就叫匿名成員

type Circle struct {
    Point
    Radius int
}

type Wheel struct {
    Circle
    Spokes int
}

這樣訪問成員(顯式形式訪問這些內(nèi)部成員的語法依然有效):

var w Wheel
// 快捷方式
w.X = 8            // equivalent to w.Circle.Point.X = 8
w.Y = 8            // equivalent to w.Circle.Point.Y = 8
w.Radius = 5       // equivalent to w.Circle.Radius = 5
w.Spokes = 20

但字面值定義需要遵循層次:

w = Wheel{Circle{Point{8, 8}, 5}, 20} //or
w = Wheel{
    Circle: Circle{
        Point:  Point{X: 8, Y: 8},
        Radius: 5,
    },
    Spokes: 20, // NOTE: trailing comma necessary here (and at Radius)
}

fmt.Printf("%#v\n", w)

注意:

  • fmt的%#v將打印成員名桥状,不止于值
  • 因為有隱式的名字, 不能同時包含兩個類型相同的匿名成員
  • 包外使用時, 未導出成員無法用簡化方式訪問

json

標準庫中的encoding/json帽揪、encoding/xml、encoding/asn1等包提供支持, 另外還有大量第三方j(luò)son庫可用(protobuf的jsonpb,jsoniter...)

  • 基本類型有數(shù)字(int, float),布爾型(true,false), 字符串(雙引號包含的unicode字符序列)
  • 復合類型, 數(shù)組(可編碼Golang的數(shù)組和slice), 對象(可編碼Golang的map和結(jié)構(gòu)體)
  • struct定義中成員之后反引號的tag可定義json
type Movie struct {
    Title  string
    Year   int  `json:"released"`
    Color  bool `json:"color,omitempty"`
    Actors []string
}

struct轉(zhuǎn)換為json的過程叫編碼(marshaling):

data, err := json.Marshal(movies)
if err != nil {
    log.Fatalf("JSON marshaling failed: %s", err)
}
fmt.Printf("%s\n", data)

輸出無縮進辅斟,難以閱讀(注意: 在最后一個成員或元素后面并沒有逗號分隔符):

[{"Title":"Casablanca","released":1942,"Actors":["Humphrey Bogart","Ingr
id Bergman"]},{"Title":"Cool Hand Luke","released":1967,"color":true,"Ac
tors":["Paul Newman"]},{"Title":"Bullitt","released":1968,"color":true,"
Actors":["Steve McQueen","Jacqueline Bisset"]}]

因此转晰,還可以使用:

data, err := json.MarshalIndent(movies, "", "    ")
//...
  • 編碼的逆操作是解碼,對應(yīng)將JSON數(shù)據(jù)解碼為Go語言的數(shù)據(jù)結(jié)構(gòu)(unmarshaling)
var titles []struct{ Title string }
if err := json.Unmarshal(data, &titles); err != nil {
    log.Fatalf("JSON unmarshaling failed: %s", err)
}
fmt.Println(titles) // "[{Casablanca} {Cool Hand Luke} {Bullitt}]"

基本的JSON類型有數(shù)字(十進制或科學記數(shù)法)士飒、布爾值(true或false)查邢、字符串,其中字符串是以雙引號包含的Unicode字符序列酵幕,支持和Go語言類似的反斜杠轉(zhuǎn)義特性扰藕,不過JSON使用的是\Uhhhh轉(zhuǎn)義數(shù)字來表示一個UTF-16編碼(譯注:UTF-16和UTF-8一樣是一種變長的編碼,有些Unicode碼點較大的字符需要用4個字節(jié)表示芳撒;而且UTF-16還有大端和小端的問題)邓深,而不是Go語言的rune類型。

這些基礎(chǔ)類型可以通過JSON的數(shù)組和對象類型進行遞歸組合笔刹。一個JSON數(shù)組是一個有序的值序列芥备,寫在一個方括號中并以逗號分隔;一個JSON數(shù)組可以用于編碼Go語言的數(shù)組和slice舌菜。一個JSON對象是一個字符串到值的映射萌壳,寫成以系列的name:value對形式,用花括號包含并以逗號分隔酷师;JSON的對象類型可以用于編碼Go語言的map類型(key類型是字符串)和結(jié)構(gòu)體

文本和HTML模板

text/template和html/template提供模板相關(guān)支持

一個模板是一個字符串或一個文件讶凉,里面包含了一個或多個由雙花括號包含的{{action}}對象

const templ = `{{.TotalCount}} issues:
{{range .Items}}----------------------------------------
Number: {{.Number}}
User:   {{.User.Login}}
Title:  {{.Title | printf "%.64s"}}
Age:    {{.CreatedAt | daysAgo}} days
{{end}}`

func daysAgo(t time.Time) int {
    return int(time.Since(t).Hours() / 24)
}

  • action中|操作符表示將前一個表達式的結(jié)果作為后一個函數(shù)的輸入,類似于UNIX中管道
  • 生成模板的輸出的處理步驟:
    • 第一步是要分析模板(執(zhí)行一次即可)并轉(zhuǎn)為內(nèi)部表示
    • 然后基于指定的輸入執(zhí)行模板
// 調(diào)用鏈順序:
// template.New先創(chuàng)建并返回一個模板山孔;
// uncs方法將daysAgo等自定義函數(shù)注冊到模板中懂讯,并返回模板;
// 最后調(diào)用Parse函數(shù)分析模板
report, err := template.New("report").
    Funcs(template.FuncMap{"daysAgo": daysAgo}).
    Parse(templ)
if err != nil {
    log.Fatal(err)
}
  • 模板解析失敗是致命錯誤(編譯前測試好), template.Must輔助函數(shù)可以簡化處理
// 模板解析失敗是致命錯誤(編譯前測試好), template.Must輔助函數(shù)可以簡化處理
var report = template.Must(template.New("issuelist").
    Funcs(template.FuncMap{"daysAgo": daysAgo}).
    Parse(templ))

func main() {
    result, err := github.SearchIssues(os.Args[1:])
    if err != nil {
        log.Fatal(err)
    }
    if err := report.Execute(os.Stdout, result); err != nil {
        log.Fatal(err)
    }
}
  • html/template模板包類似, 但是增加了字符串自動轉(zhuǎn)義特性
    • 避免輸入字符串和HTML台颠、JavaScript褐望、CSS或URL語法產(chǎn)生沖突的問題
    • 避免一些安全問題,諸如HTML注入攻擊
import "html/template"

var issueList = template.Must(template.New("issuelist").Parse(`
<h1>{{.TotalCount}} issues</h1>
<table>
<tr style='text-align: left'>
  <th>#</th>
  <th>State</th>
  <th>User</th>
  <th>Title</th>
</tr>
{{range .Items}}
<tr>
  <td><a href='{{.HTMLURL}}'>{{.Number}}</a></td>
  <td>{{.State}}</td>
  <td><a href='{{.User.HTMLURL}}'>{{.User.Login}}</a></td>
  <td><a href='{{.HTMLURL}}'>{{.Title}}</a></td>
</tr>
{{end}}
</table>
`))

函數(shù)

聲明(見前)

  • 函數(shù)聲明包括函數(shù)名串前、形參列表瘫里、返回值列表(可省略)以及函數(shù)體
func name(parameter-list) (result-list) {
    body
}
  • 函數(shù)的類型被稱為函數(shù)的標識符, 形參和返回值類型一一對應(yīng)被認為有相同的類型和標識符
type HandleFunc func(http.ResponseWriter, *http.RequestReader)
  • 函數(shù)可遞歸,即可直接或間接地調(diào)用自身
  • Golang函數(shù)可多值返回, 小括號包含
  • 返回值可指定變量名, 相同類型指定有意義的命名可增加可讀性
// width, height
func Size(rect image.Rectangle) (width, height int)

錯誤

  • 函數(shù)返回一個額外的返回值荡碾,通常是最后一個谨读,來傳遞錯誤信息(error)。如果導致失敗的原因只有一個坛吁,額外的返回值可以是一個布爾值(bool)
  • 通常劳殖,當函數(shù)返回non-nilerror時铐尚,其他返回值是未定義的(undefined),應(yīng)該被忽略
  • 某些情況其他值可返回有意義值, 如文件讀寫失敗, 仍然會返回讀寫字節(jié)數(shù), 這種情況應(yīng)該是先處理不完整的數(shù)據(jù)哆姻,再處理錯誤
  • EOF錯誤, 由文件讀取結(jié)束引發(fā)的讀取失敗

關(guān)于不使用異常的說明:

Go這樣設(shè)計的原因是由于對于某個應(yīng)該在控制流程中處理的錯誤而言宣增,將這個錯誤以異常的形式拋出會混亂對錯誤的描述,這通常會導致一些糟糕的后果

錯誤處理策略

  • 向上傳播
    • 描述詳盡, 包含上下文
    • 錯誤信息經(jīng)常是以鏈式組合在一起的矛缨,所以錯誤信息中應(yīng)避免大寫換行符
  • 重試策略
    • 偶然性的
    • 或由不可預知的問題導致
  • 輸出錯誤信息并結(jié)束程序
    • main函數(shù)
    • 程序內(nèi)部包含不一致爹脾,即bug導致
    • log.Fatal
  • 僅打印信息,不中斷不重試
  • 直接忽略策略

函數(shù)值

被看作第一類值(first-class values):函數(shù)像其他值一樣箕昭,擁有類型灵妨,可以被賦值給其他變量,傳遞給函數(shù)盟广,從函數(shù)返回

  • 函數(shù)類型的零值是nil, 可與nil比較闷串,nil調(diào)用會panic
  • 函數(shù)值之間不可比較, 不能用函數(shù)值作為map的key
  • 匿名函數(shù): func關(guān)鍵字后沒有函數(shù)名, 繞過函數(shù)只能在包級別定義的限制
    // 匿名函數(shù)
    add1:= func(r rune) rune { return r + 1 }
    fmt.Println(strings.Map(add1, "VMS"))      // "WNT"
    fmt.Println(strings.Map(func(r rune)rune {
      return r + 1
    }, "VMS")

警告:匿名函數(shù)捕獲迭代變量

循環(huán)迭代中,函數(shù)值中記錄的迭代變量(作用域在for詞法塊筋量,在該循環(huán)中生成的所有函數(shù)值都共享相同的循環(huán)變量)地址而不是值

注意以下賦值: dir := d

var rmdirs []func()
for _, d := range tempDirs() {
    dir := d // NOTE: necessary!
    os.MkdirAll(dir, 0755) // creates parent directories too
    rmdirs = append(rmdirs, func() {
        os.RemoveAll(dir)
    })
}
// ...do some work…
for _, rmdir := range rmdirs {
    rmdir() // clean up
}

后續(xù)遇到defer語句或for循環(huán)中g(shù)oroutine(go func(){...})類似!!!

可變參數(shù)

unc sum(vals...int) int {
    total := 0
    for _, val := range vals {
        total += val
    }
    return total
}

后續(xù)會遇到可變option個數(shù)傳遞

deferred函數(shù)

  • defer someFuncion()
  • 在包含該defer語句的函數(shù)其他語句完畢后才執(zhí)行
  • 多個defer后來先執(zhí)行
  • defer語句經(jīng)常被用于處理成對的操作烹吵,如打開、關(guān)閉桨武、連接肋拔、斷開連接、加鎖呀酸、釋放鎖
  • 通過defer機制保證在任何執(zhí)行路徑下凉蜂,資源被釋放
  • 釋放資源的defer應(yīng)直接跟在請求資源的語句后
func title(url string) error {
    resp, err := http.Get(url)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    
    ct := resp.Header.Get("Content-Type")
    if ct != "text/html" && !strings.HasPrefix(ct,"text/html;") {
        return fmt.Errorf("%s has type %s, not text/html",url, ct)
    }
    
    doc, err := html.Parse(resp.Body)
    if err != nil {
        return fmt.Errorf("parsing %s as HTML: %v", url,err)
    }
    // ...print doc's title element…
    return nil

panic和recover

當panic異常發(fā)生時,程序會中斷運行性誉,并立即執(zhí)行在該goroutine(可以先理解成線程窿吩,在第8章會詳細介紹)中被延遲的函數(shù)(defer 機制)。隨后错览,程序崩潰并輸出日志信息

  • 直接調(diào)用內(nèi)置的panic函數(shù)也會引發(fā)panic異常, 到達邏輯上不可達的路徑可以panic
  • panic會引起程序的崩潰纫雁,因此一般用于嚴重錯誤,如程序內(nèi)部的邏輯不一致
  • 明確正則表達式(大多數(shù)是字符串字面值)不會出錯,可使用regexp.MustCompile檢查輸入

通常來說倾哺,不應(yīng)該對panic異常做任何處理轧邪,但有時,也許我們可以從異常中恢復羞海,至少我們可以在程序崩潰前忌愚,做一些操作。舉個例子却邓,當web服務(wù)器遇到不可預料的嚴重問題時硕糊,在崩潰前應(yīng)該將所有的連接關(guān)閉;如果不做任何處理,會使得客戶端一直處于等待狀態(tài)

如果在deferred函數(shù)中調(diào)用了內(nèi)置函數(shù)recover癌幕,并且定義該defer語句的函數(shù)發(fā)生了panic異常衙耕,recover會使程序從panic中恢復昧穿,并返回panic value勺远。導致panic異常的函數(shù)不會繼續(xù)運行,但能正常返回时鸵。在未發(fā)生panic時調(diào)用recover胶逢,recover會返回nil。

deferred函數(shù)幫助Parse從panic中恢復饰潜。在deferred函數(shù)內(nèi)部初坠,panic value被附加到錯誤信息中;并用err變量接收錯誤信息彭雾,返回給調(diào)用者碟刺。我們也可以通過調(diào)用runtime.Stack往錯誤信息中添加完整的堆棧調(diào)用信息。

func Parse(input string) (s *Syntax, err error) {
    defer func() {
        if p := recover(); p != nil {
            err = fmt.Errorf("internal error: %v", p)
        }
    }()
    // ...parser...
}

注意: 不應(yīng)該試圖去恢復其他包引起的panic(有時難以做到)薯酝,安全的做法是有選擇性的recover

方法

  • 方法是一個和特殊類型關(guān)聯(lián)的函數(shù), 面向?qū)ο缶幊谈拍?
  • 方法關(guān)聯(lián)一個被稱為接收器的對象
  • 指針對象可避免復制, 可修改成員變量, 否則修改復制對象的成員半沽,改不了原來的對象
  • 不管receiver是指針類型還是非指針類型,都可以通過指針/非指針類型進行調(diào)用的吴菠,編譯器會根據(jù)方法自動轉(zhuǎn)換
  • Nil也是一個合法的接收器類型

通過嵌套struct繼承方法

  • 嵌入的struct方法可以被重新定義, 外部結(jié)構(gòu)在其方法可以顯式調(diào)用嵌入對象的方法
func (p *Point) ScaleBy(factor float64) {
    p.X *= factor
    p.Y *= factor
}

示例: sync.Mutex的Lock和Unlock方法被引入到匿名結(jié)構(gòu)中:

var cache = struct {
    sync.Mutex
    mapping map[string]string
}{
    mapping: make(map[string]string),
}


func Lookup(key string) string {
    cache.Lock()
    v := cache.mapping[key]
    cache.Unlock()
    return v
}

方法值和方法表達式

distanceFromP := p.Distance        // method value
fmt.Println(distanceFromP(q))      // "5"

bitmap

通常使用map[T]bool來表示集合, 但是用bitmap(byte[]實現(xiàn))是種更好的選擇:

  • 例如在數(shù)據(jù)流分析領(lǐng)域, 集合通常是非負整數(shù)
  • http分塊下載文件(16KB每塊)者填,可用bimap標記下載完成的塊
// An IntSet is a set of small non-negative integers.
// Its zero value represents the empty set.
type IntSet struct {
    words []uint64
}

// Has reports whether the set contains the non-negative value x.
func (s *IntSet) Has(x int) bool {
    word, bit := x/64, uint(x%64)
    return word < len(s.words) && s.words[word]&(1<<bit) != 0
}

// Add adds the non-negative value x to the set.
func (s *IntSet) Add(x int) {
    word, bit := x/64, uint(x%64)
    for word >= len(s.words) {
        s.words = append(s.words, 0)
    }
    s.words[word] |= 1 << bit
}

// UnionWith sets s to the union of s and t.
func (s *IntSet) UnionWith(t *IntSet) {
    for i, tword := range t.words {
        if i < len(s.words) {
            s.words[i] |= tword
        } else {
            s.words = append(s.words, tword)
        }
    }
}

// String returns the set as a string of the form "{1 2 3}".
func (s *IntSet) String() string {
    var buf bytes.Buffer
    buf.WriteByte('{')
    for i, word := range s.words {
        if word == 0 {
            continue
        }
        for j := 0; j < 64; j++ {
            if word&(1<<uint(j)) != 0 {
                if buf.Len() > len("{") {
                    buf.WriteByte(' ')
                }
                fmt.Fprintf(&buf, "%d", 64*i+j)
            }
        }
    }
    buf.WriteByte('}')
    return buf.String()
}

注意: bytes.Buffer的String()用法, 定義Strin()有助于fmt.Print會調(diào)用打印, 這種機制有賴于接口和類型斷言(詳見下一章)

封裝

OOB編程很重要的一點就是封裝(信息隱藏), 三個好處:

  • 最少知識: 無需調(diào)用方了解所有細節(jié), 僅需少量接口即可
  • 依賴抽象: 隱藏實現(xiàn)的細節(jié),可以防止調(diào)用方依賴那些可能變化的具體實現(xiàn)
  • 防止外部調(diào)用方對對象內(nèi)部的值任意地進行修改

回顧上一節(jié)的IntSet定義:

type IntSet struct {
    words []uint64
}

其實也可以這樣定義:

type IntSet []uint64

但是后者封裝性不如前者, 因為words成員是包外不可見的, 無法直接操作

封裝并不總是需要的, 比如time包的Duration暴露為int64的納秒, 這樣自定義相關(guān)常量成為可能:

const day = 24 * time.Hour

另外如第二種方式暴露內(nèi)部slice成員, 就可以直接用range迭代

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末做葵,一起剝皮案震驚了整個濱河市占哟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌酿矢,老刑警劉巖榨乎,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異瘫筐,居然都是意外死亡蜜暑,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門严肪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來史煎,“玉大人,你說我怎么就攤上這事驳糯∑螅” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵酝枢,是天一觀的道長恬偷。 經(jīng)常有香客問我,道長帘睦,這世上最難降的妖魔是什么袍患? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任坦康,我火速辦了婚禮,結(jié)果婚禮上诡延,老公的妹妹穿的比我還像新娘滞欠。我一直安慰自己,他們只是感情好肆良,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布筛璧。 她就那樣靜靜地躺著,像睡著了一般惹恃。 火紅的嫁衣襯著肌膚如雪夭谤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天巫糙,我揣著相機與錄音朗儒,去河邊找鬼。 笑死参淹,一個胖子當著我的面吹牛醉锄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播承二,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼榆鼠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了亥鸠?” 一聲冷哼從身側(cè)響起妆够,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎负蚊,沒想到半個月后神妹,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡家妆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年鸵荠,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伤极。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡蛹找,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出哨坪,到底是詐尸還是另有隱情庸疾,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布当编,位于F島的核電站届慈,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜金顿,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一臊泌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧揍拆,春花似錦渠概、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至顷牌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間塞淹,已是汗流浹背窟蓝。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留饱普,地道東北人运挫。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像套耕,于是被迫代替她去往敵國和親谁帕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容