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
,break
和continue
,
// 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ù): complex64
和complex128
布爾型
不能直接和整型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}]"
- GitHub的Web服務(wù)接口
- 摘錄譯文版兩段說明:
基本的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-nil
的error
時铐尚,其他返回值是未定義的(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迭代