The Go Programming Language
目录[隐藏] |
[编辑]Tutorial
- os.Args, slice, ~JS ArrayView?, [begin, end) 这其实是Python语法吧?
- 如果变量没有显示初始化,则实际上初始化为改type的零值,对string就是""
- for i=0; i<len(os.Args); i++ { ... } //不带()的for循环有点不太适应
- 不合法:y=x++ 或 ++x
- for _, arg := range os.Args[1:] {
- => strings.Join(os.Args[1:], " ")
- counts := make(map[string]int) //寻找重复的行;
- input := bufio.NewScanner(os.Stdin) //想起了Java的StringTokenizer或C++的cin
- for input.Scan() { counts[input.Text()]++ }
- fmt.Printf:
- %d %x %f ... %t(true/false)
- %c Unicode码点
- %q 双引号括起来的string
- %v 自然格式
- %T 类型
- f, err := os.Open(path) #注意,f的类型是*os.File
- 直接读取整个文件内容:data, err := ioutil.ReadFile(path)
- 图像编码的例子:Go类库太强大了
- Fetching a URL
- res, err := http.Get(url) #注意,这里能处理https连接
- b, err := ioutil.ReadAll(res.Body) #这里Body是一个流对象
- 并发化:
- ch := make(chan string)
- go fetch(url, ch) #启动一个goroutine
- <-ch #其实会在这里阻塞等待吧?
- func fetch(url string, ch chan<- string) {
- ch <- fmt.Sprint(err)
- Web服务器
- http.HandleFunc("/", handler)
- func handler(w http.ResponseWriter, r *http.Request) { //后者参数是一个*struct类型,而前者是个引用(复合类型?)
- r.URL.Path
- var mu sync.Mutex #用于保护goroutine's对共享变量的访问
[编辑]程序结构
- 名字
- 声明:var const type func
- package + import:借鉴Java的?
- :=是声明,=是赋值
- i, j = j, i
- 重名的第2次:=是赋值(但是必须至少声明一个新变量,wtf??)
- 指针
- x := 1; p := &x; *p = 2
- nil
- 可安全地返回局部变量的地址 => Pointer aliasing
- *p++ //不改变指针p的值
- new函数
- 变量的生命周期(要求编译器进行‘逃逸分析’)
- 赋值
- v, ok = x.(T) //m[key], <-ch
- _, err = io.Copy(dst, src)
- 类型声明
- type Celsius float64 //每个类型T对应一个类型转换T(x),允许转换当且仅当类型兼容
- func (c Celsius) String() string { return fmt.Sprintf("%g C", c) }
- type Celsius float64 //每个类型T对应一个类型转换T(x),允许转换当且仅当类型兼容
- 包与文件
- 包级变量的初始化;多个.go文件的导入顺序;func init() {...}
- Scope(编译期的名字解析)
- shadow
- else if嵌套在if中,可看到前者声明的变量
- 一个奇怪的例子:var cwd string; func init() { cwd, err := os.Getwd() //错误,全局变量cwd在init里没有被初始化
[编辑]基本数据类型
- 整数
- int/uint{8,16,32,64}, int
- rune(=int32):Unicode码点
- byte(=uint8)
- uintptr(参考Low Level Programming章节)
- x &^ y(用y来bit clear x, 都为1则清除,否则保持不变,
与非?) vs 同或? - len()返回int而不是uint是有一些额外的好处的。。。
- %#[1]x //[1]代表使用Printf的第一个操作数,#代表增加前缀0/0x
- 浮点数
- 使用float64
- fmt.Println(z, -z, 1/z, -1/z, z/z); //"0 -0 +Inf -Inf NaN"
- 复数
- import ("image", "image/color", "image/png", "math/complx", "os")
- Booleans
- 字符串
- UTF-内部编码:第i个字节不是第i个字符
- 无法修改:s[0]='L'; 但是可以重新引用:s+=", hi"
- "..."里的\xxx转义序列
- raw string literal:`...` (反引号,这里没C++11复杂,但是简单)多行时\r会被删除(留下\n?)
- 用于写正则表达式?
- Unicode
- UTF-8是Go语言的作者Ken Thompson和Rob Pike发明的:
- 使用1~4个字节,第1个字节的前缀分别为0 110 1110 11110,后面的都是10(最多可用21个比特的空间?不过还可以继续扩展)
- No rune's encoding is a substring of any other, so you can search by just bytes
- \uhhhh \Uhhhhhhhh
- "世界" = "\xe4\xb8\x96\xe7\x95\x8c" = "\u4e16\u754c"
- >=256的单个rune不能用"\xe4\xb8\x96"的形式表示
- utf8.RuneCountInString(s) 统计字符的个数
- 挨个解码:rune, size := utf8.DecodeRuneInString(s[i:]); i += size
- => for i, r := range s { ...
- => r := []rune(s)
- 如果遇到错误:产生一个替换字符, '\uFFFD'
- UTF-8是Go语言的作者Ken Thompson和Rob Pike发明的:
- *Strings and Byte Slices
- bytes.Buffer的WriteRune方法?专用的序列化
- 常量
- type Weekday int; const ( Sunday Weekday = iota, Monday, ... ) //常量生成器
- * untyped constants:常量表达式?
[编辑]复合类型
- 数组
- q := [...]int{1,2,3} //类型是[3]int,数组的长度是类型的一部分
- r := [...]int{99: -1} //前99个元素都是0
- import "ctypto/sha256"; c1 = sha256.Sum256([]byte("x")); //%x可以16进制打印全部元素
- 切片
- s[i:j], where 0<=i<=j<=cap(s)
- 不可比较(可能包含自身?‘间接的’),bytes.Equal可用于比较2个[]byte,但其他的需要自己写
- *测试为空:len(s)==0 不是s==nil
- 创建切片:make([]T, len, cap)
- append(slice并不是纯粹的引用类型)
- x = append(x, x...)
- Maps
- map元素不是变量,不能用&获取地址
- for k, v := range m { ...
- in := bufio.NewReader(os.Stdin); for { r, n, err := in.ReadRune(); if err!=io.EOF && err==nil { ...
- if r==unicode.ReplacementChar && n==1 { continue }
- 结构
- p := &Point{1,2}
- 匿名域(相当于OOP里子类重用基类的成员)
- 进一步的,任何类型都可以作为匿名域嵌入,哪怕没有subfields(这个有点像traits/mixin的概念)
- JSON
- data, err := json.Marshal(obj) //内部使用了反射机制(编译型的语言支持运行时反射??)
- if err := json.Unmarshal(data, &obj); err!=nil { ...
- 文本与HTML模板
- const t = `... {{.Name}} ... {{range .Items}} ... {{end}}`
- tpl := template.Must(template.New("escape").Parse(t)) ==> tpl.Execute(os.Stdout, dataIn)
[编辑]函数
- 可变大小的栈?!(这样看来,递归不用担心栈溢出了)
- 多返回值
- 显示错误处理:os.Open不仅会报告错误的原因,还会输出文件名
- io.EOF
- 函数作为值
- 匿名函数
- os.MkdirAll(dirpath, 0755) //循环变量每次会被更新,重命名到一个新的局部变量(使用go, defer时会遇到)
- Variadic函数
- func sum(vals ...int) int { ... } //<== sum(values...)
- ...interface{}
- ==> func sum(vals ...int) (result int) { defer func() { fmt.Printf(result) }() ... } //AOP
- func sum(vals ...int) int { ... } //<== sum(values...)
- 推迟的函数调用
- defer resp.Body.Close()
- Panic
- var scheme = regexp.MustCompile(`^https?:`)
- n := runtime.Stack(buf[:], false); os.Stdout.Write(buf[:n])
- Recover
- 例如,net/http包可调用recover()从用户handler的panic中恢复(?)
[编辑]方法
- func (p *Point) ScaleBy(factor float64) { ... } //名称是(*Point).ScaleBy
- 隐式转换:var p Point; ...; p.ScaleBy(2); //等于(&p).ScaleBy(2)
- 不能用于结构成员或slice元素,因它们是‘临时变量’,不能&取地址
- 隐式转换:var p Point; ...; p.ScaleBy(2); //等于(&p).ScaleBy(2)
- nil
- The type of an anonymous field may be a pointer to a named type, in which case fields and methods are promoted indirectly from the pointed-to object. (方法嵌入struct)
- 方法的值与表达式
- f := (*Point).ScaleBy //selector语法,这实际上是一个独立的函数,有别与C++里的成员函数指针?
- 封装(struct的小写字母开头的域外部不可见)
[编辑]Interfaces
- var _ io.Writer = (*bytes.Buffer)(nil)
- 案例分析:flag.Value
- 接口的值(类型描述符)
- dynamic dispatch
- 接口类型不可比较,会panic
- sort.Interface
- http.Handler
- type Handler interface { ServeHTTP(w ResponseWriter, r *Request) }
- func ListenAndServe(address string, h Handler) error
- ServeMux(就是一个微型的Web框架?)
- Go语言不需要Web框架...
- mux.Handle("/list", http.HandlerFunc(db.list)) //注意,http.HandlerFunc是一个类型转换
- func (db database) list(w http.ResponseWriter, r *http.Request) { ... }
- => mux.HandleFunc("/list", db.list)
- 注意:每个handler在单独的goroutin中执行
- error
- 案例分析:表达式求值
- *类型断言
- if w, ok := w.(*os.File); ok { ...
- *类型开关
[编辑]Goroutines and Channels
- time.Now().Format("15:04:05") //基于实例的格式化,参考时间:“Mon Jan 2 03:04:05PM 2006 UTC-0700”
- 并发的Echo:
- io.Copy(c, c) #c net.Conn
- 1*time.Second //time.Duration类型; ==> time.Sleep(delay)
- ch = make(chan int) //Unbuffered, 仅仅用于同步?ch <- 1; <-ch
- ch <- struct{}{}
- 如果sender知道没有更多数据发送,它可以close(ch)以通知接受者:msg, ok := <-ch
- 单向的Channel类型
- chan<- int和<-chan int
- 缓冲的Channels
- ch = make(chan string, 10) //满时发送阻塞,空时接受者阻塞
- 示例:返回DNS查询最快的结果(代码略)
- sync.WaitGroup:对goroutine的启动/结束进行计数
- wg.Add(1) wg.Done() wg.Wait() //哈,就是个信号量嘛, Add(1)必须在go启动worker前调用,不然不能保证Add(1)在主Wait()前执行
- 可用于限制并发的IO操作数
- 示例:并发的Web爬虫
- too parallel ==> 计数chan可用于代表资源限制
- select
- tick := time.Tick(10*time.Second) ==> <-tick //倒计时 ==> time.NewTicker?可Stop()
- select {
- case <-ch1: ...
- case x := <-ch2: ...a
- case ch3 <- y: ...
- case <-time.After(10*time.Second) //time.After返回一个channel,同时内部启动一个goroutine向其发送超时通知
- 示例:并发的目录遍历
- 嗯?可以直接对一个chan做for range迭代?
- *取消
- select里的default:分支代表什么意思?没有任何case channel就绪?
- **调试:利用panic dump
- 示例:Chat Server
- 每种特定类型的消息就是一个channel?coming、leaving、msg
[编辑]并发with共享变量
- 并发安全:竞争条件、死锁、活锁、饥饿
- 共享变量的monitor goroutine ==> Share by communicating
- 互斥:sync.Mutex
- 保护对共享变量的访问,‘临界区域’
- defer m.Unlock() //宁可临界区域大一些,避免panic导致的问题?
- *Mutex不可重入
- *读写锁:sync.RWMutex
- 内存同步
- CPU/Compiler乱序执行调度带来的额外问题
- sync.Once
- loadIconsOnce.Do( loadIcons )
- 竞争检测
- go build/run/test -race
- *示例:并发非阻塞Cache(???)
- Goroutines与线程
- 可增长的栈:2KB --> 1GB
- m:n调度
- GOMAXPROCS
- Goroutines没有Identity:Simpler设计,函数参数直接影响行为
[编辑]包与Go工具
- Import路径
- 域名指定全称?Go Tool负责解析处理?
- package声明
- main()
- _test.go
- 重命名import
- 空import
- import _ "image/png" //仅仅为了执行其init()?Register PNG Decode();
- 包与命名
- Go工具
- $ export GOPATH=$HOME/gobook
- $ go get gopl.io/...
- $ go env
- $ go build ./src/gopl.io/ch1/helloworld //本地路径需要以./开始
- *_linux.go
- // +buils linux darwin
- *Documenting
- $ go doc ...
- 内部包
- net/http/internal/chunked仅可被net/http或net/http/httputil导入
- $ go list ...
- $ go list -f '模板:.ImportPath -> 模板:Join .Imports " "' compress/...
[编辑]Testing
- func TestNameOrFeature(t *testing.T) { ... t.Error(`...`) ...
- 代码覆盖
- $ go test -v -run=Coverage -cover-profile=c.out ...
- $ go tool cover -html=c.out
- Benchmark函数
- Profiling
- $ go test -cpuprofile=cpu.out -memprofile=mem.out --blockprofile=block.out
- *Example函数
[编辑]反射
- reflect.Type/Value
- 示例:编码S-表达式
- *访问struct域的tag(让我想起了JAXB)
[编辑]底层编程
- unsafe.Sizeof / Alignof / Offsetof
- unsafe.Pointer
- 可将*float64转换为*uint64,以查看浮点数的位模式
- 虽然当前Go并不使用moving GC...
- **用cgo来调用C语言代码(略)