The Go Programming Language(读书笔记)

The Go Programming Language

目录

 [隐藏

[编辑]Tutorial

  1. os.Args, slice, ~JS ArrayView?, [begin, end) 这其实是Python语法吧?
  2. 如果变量没有显示初始化,则实际上初始化为改type的零值,对string就是""
  3. for i=0; i<len(os.Args); i++ { ... } //不带()的for循环有点不太适应
    1. 不合法:y=x++ 或 ++x
    2. for _, arg := range os.Args[1:] {
    3. => strings.Join(os.Args[1:], " ")
  4. counts := make(map[string]int) //寻找重复的行;
    1. input := bufio.NewScanner(os.Stdin) //想起了Java的StringTokenizer或C++的cin
    2. for input.Scan() { counts[input.Text()]++ }
  5. fmt.Printf:
    1.  %d %x %f ... %t(true/false)
    2.  %c Unicode码点
    3.  %q 双引号括起来的string
    4.  %v 自然格式
    5.  %T 类型
  6. f, err := os.Open(path) #注意,f的类型是*os.File
    1. 直接读取整个文件内容:data, err := ioutil.ReadFile(path)
  7. 图像编码的例子:Go类库太强大了
  8. Fetching a URL
    1. res, err := http.Get(url) #注意,这里能处理https连接
    2. b, err := ioutil.ReadAll(res.Body) #这里Body是一个流对象
    3. 并发化:
      1. ch := make(chan string)
      2. go fetch(url, ch) #启动一个goroutine
      3. <-ch #其实会在这里阻塞等待吧?
      4. func fetch(url string, ch chan<- string) {
        1. ch <- fmt.Sprint(err)
  9. Web服务器
    1. http.HandleFunc("/", handler)
    2. func handler(w http.ResponseWriter, r *http.Request) { //后者参数是一个*struct类型,而前者是个引用(复合类型?)
      1. r.URL.Path
    3. var mu sync.Mutex #用于保护goroutine's对共享变量的访问

[编辑]程序结构

  1. 名字
  2. 声明:var const type func
    1. package + import:借鉴Java的?
    2.  :=是声明,=是赋值
      1. i, j = j, i
      2. 重名的第2次:=是赋值(但是必须至少声明一个新变量,wtf??)
  3. 指针
    1. x := 1; p := &x; *p = 2
    2. nil
    3. 可安全地返回局部变量的地址 => Pointer aliasing
    4. *p++ //不改变指针p的值
    5. new函数
    6. 变量的生命周期(要求编译器进行‘逃逸分析’)
  4. 赋值
    1. v, ok = x.(T) //m[key], <-ch
    2. _, err = io.Copy(dst, src)
  5. 类型声明
    1. type Celsius float64 //每个类型T对应一个类型转换T(x),允许转换当且仅当类型兼容
      1. func (c Celsius) String() string { return fmt.Sprintf("%g C", c) }
  6. 包与文件
    1. 包级变量的初始化;多个.go文件的导入顺序;func init() {...}
  7. Scope(编译期的名字解析)
    1. shadow
    2. else if嵌套在if中,可看到前者声明的变量
    3. 一个奇怪的例子:var cwd string; func init() { cwd, err := os.Getwd() //错误,全局变量cwd在init里没有被初始化

[编辑]基本数据类型

  1. 整数
    1. int/uint{8,16,32,64}, int
    2. rune(=int32):Unicode码点
    3. byte(=uint8)
    4. uintptr(参考Low Level Programming章节)
    5. x &^ y(用y来bit clear x, 都为1则清除,否则保持不变, 与非?) vs 同或?
    6. len()返回int而不是uint是有一些额外的好处的。。。
    7. %#[1]x //[1]代表使用Printf的第一个操作数,#代表增加前缀0/0x
  2. 浮点数
    1. 使用float64
    2. fmt.Println(z, -z, 1/z, -1/z, z/z); //"0 -0 +Inf -Inf NaN"
  3. 复数
    1. import ("image", "image/color", "image/png", "math/complx", "os")
  4. Booleans
  5. 字符串
    1. UTF-内部编码:第i个字节不是第i个字符
    2. 无法修改:s[0]='L'; 但是可以重新引用:s+=", hi"
    3. "..."里的\xxx转义序列
    4. raw string literal:`...` (反引号,这里没C++11复杂,但是简单)多行时\r会被删除(留下\n?)
      1. 用于写正则表达式?
    5. Unicode
      1. UTF-8是Go语言的作者Ken Thompson和Rob Pike发明的:
        1. 使用1~4个字节,第1个字节的前缀分别为0 110 1110 11110,后面的都是10(最多可用21个比特的空间?不过还可以继续扩展)
        2. No rune's encoding is a substring of any other, so you can search by just bytes
        3. \uhhhh \Uhhhhhhhh
        4. "世界" = "\xe4\xb8\x96\xe7\x95\x8c" = "\u4e16\u754c"
        5. >=256的单个rune不能用"\xe4\xb8\x96"的形式表示
        6. utf8.RuneCountInString(s) 统计字符的个数
        7. 挨个解码:rune, size := utf8.DecodeRuneInString(s[i:]); i += size
          1. => for i, r := range s { ...
          2. => r := []rune(s)
        8. 如果遇到错误:产生一个替换字符, '\uFFFD'
    6. *Strings and Byte Slices
      1. bytes.Buffer的WriteRune方法?专用的序列化
  6. 常量
    1. type Weekday int; const ( Sunday Weekday = iota, Monday, ... ) //常量生成器
    2. * untyped constants:常量表达式?

[编辑]复合类型

  1. 数组
    1. q := [...]int{1,2,3} //类型是[3]int,数组的长度是类型的一部分
    2. r := [...]int{99: -1} //前99个元素都是0
    3. import "ctypto/sha256"; c1 = sha256.Sum256([]byte("x")); //%x可以16进制打印全部元素
  2. 切片
    1. s[i:j], where 0<=i<=j<=cap(s)
    2. 不可比较(可能包含自身?‘间接的’),bytes.Equal可用于比较2个[]byte,但其他的需要自己写
    3. *测试为空:len(s)==0 不是s==nil
    4. 创建切片:make([]T, len, cap)
    5. append(slice并不是纯粹的引用类型)
      1. x = append(x, x...)
  3. Maps
    1. map元素不是变量,不能用&获取地址
    2. for k, v := range m { ...
    3. in := bufio.NewReader(os.Stdin); for { r, n, err := in.ReadRune(); if err!=io.EOF && err==nil { ...
      1. if r==unicode.ReplacementChar && n==1 { continue }
  4. 结构
    1. p := &Point{1,2}
    2. 匿名域(相当于OOP里子类重用基类的成员)
      1. 进一步的,任何类型都可以作为匿名域嵌入,哪怕没有subfields(这个有点像traits/mixin的概念)
  5. JSON
    1. data, err := json.Marshal(obj) //内部使用了反射机制(编译型的语言支持运行时反射??)
    2. if err := json.Unmarshal(data, &obj); err!=nil { ...
  6. 文本与HTML模板
    1. const t = `... {{.Name}} ... {{range .Items}} ... {{end}}`
    2. tpl := template.Must(template.New("escape").Parse(t)) ==> tpl.Execute(os.Stdout, dataIn)

[编辑]函数

  1. 可变大小的栈?!(这样看来,递归不用担心栈溢出了)
  2. 多返回值
  3. 显示错误处理:os.Open不仅会报告错误的原因,还会输出文件名
  4. io.EOF
  5. 函数作为值
  6. 匿名函数
    1. os.MkdirAll(dirpath, 0755) //循环变量每次会被更新,重命名到一个新的局部变量(使用go, defer时会遇到)
  7. Variadic函数
    1. func sum(vals ...int) int { ... } //<== sum(values...)
      1. ...interface{}
      2. ==> func sum(vals ...int) (result int) { defer func() { fmt.Printf(result) }() ... } //AOP
  8. 推迟的函数调用
    1. defer resp.Body.Close()
  9. Panic
    1. var scheme = regexp.MustCompile(`^https?:`)
    2. n := runtime.Stack(buf[:], false); os.Stdout.Write(buf[:n])
  10. Recover
    1. 例如,net/http包可调用recover()从用户handler的panic中恢复(?)

[编辑]方法

  1. func (p *Point) ScaleBy(factor float64) { ... } //名称是(*Point).ScaleBy
    1. 隐式转换:var p Point; ...; p.ScaleBy(2); //等于(&p).ScaleBy(2)
      1. 不能用于结构成员或slice元素,因它们是‘临时变量’,不能&取地址
  2. nil
  3. 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)
  4. 方法的值与表达式
    1. f := (*Point).ScaleBy //selector语法,这实际上是一个独立的函数,有别与C++里的成员函数指针?
  5. 封装(struct的小写字母开头的域外部不可见)

[编辑]Interfaces

  1. var _ io.Writer = (*bytes.Buffer)(nil)
  2. 案例分析:flag.Value
  3. 接口的值(类型描述符)
    1. dynamic dispatch
  4. 接口类型不可比较,会panic
  5. sort.Interface
  6. http.Handler
    1. type Handler interface { ServeHTTP(w ResponseWriter, r *Request) }
    2. func ListenAndServe(address string, h Handler) error
    3. ServeMux(就是一个微型的Web框架?)
      1. Go语言不需要Web框架...
      2. mux.Handle("/list", http.HandlerFunc(db.list)) //注意,http.HandlerFunc是一个类型转换
        1. func (db database) list(w http.ResponseWriter, r *http.Request) { ... }
      3. => mux.HandleFunc("/list", db.list)
    4. 注意:每个handler在单独的goroutin中执行
  7. error
  8. 案例分析:表达式求值
  9. *类型断言
    1. if w, ok := w.(*os.File); ok { ...
  10. *类型开关

[编辑]Goroutines and Channels

  1. time.Now().Format("15:04:05") //基于实例的格式化,参考时间:“Mon Jan 2 03:04:05PM 2006 UTC-0700”
  2. 并发的Echo:
    1. io.Copy(c, c) #c net.Conn
  3. 1*time.Second //time.Duration类型; ==> time.Sleep(delay)
  4. ch = make(chan int) //Unbuffered, 仅仅用于同步?ch <- 1; <-ch
    1. ch <- struct{}{}
  5. 如果sender知道没有更多数据发送,它可以close(ch)以通知接受者:msg, ok := <-ch
  6. 单向的Channel类型
    1. chan<- int和<-chan int
  7. 缓冲的Channels
    1. ch = make(chan string, 10) //满时发送阻塞,空时接受者阻塞
    2. 示例:返回DNS查询最快的结果(代码略)
  8. sync.WaitGroup:对goroutine的启动/结束进行计数
    1. wg.Add(1) wg.Done() wg.Wait() //哈,就是个信号量嘛, Add(1)必须在go启动worker前调用,不然不能保证Add(1)在主Wait()前执行
    2. 可用于限制并发的IO操作数
  9. 示例:并发的Web爬虫
    1. too parallel ==> 计数chan可用于代表资源限制
  10. select
    1. tick := time.Tick(10*time.Second) ==> <-tick //倒计时 ==> time.NewTicker?可Stop()
    2. select {
      1. case <-ch1: ...
      2. case x := <-ch2: ...a
      3. case ch3 <- y: ...
      4. case <-time.After(10*time.Second) //time.After返回一个channel,同时内部启动一个goroutine向其发送超时通知
  11. 示例:并发的目录遍历
    1. 嗯?可以直接对一个chan做for range迭代?
  12. *取消
    1. select里的default:分支代表什么意思?没有任何case channel就绪?
    2. **调试:利用panic dump
  13. 示例:Chat Server
    1. 每种特定类型的消息就是一个channel?coming、leaving、msg

[编辑]并发with共享变量

  1. 并发安全:竞争条件、死锁、活锁、饥饿
  2. 共享变量的monitor goroutine ==> Share by communicating
  3. 互斥:sync.Mutex
    1. 保护对共享变量的访问,‘临界区域’
    2. defer m.Unlock() //宁可临界区域大一些,避免panic导致的问题?
    3. *Mutex不可重入
  4. *读写锁:sync.RWMutex
  5. 内存同步
    1. CPU/Compiler乱序执行调度带来的额外问题
  6. sync.Once
    1. loadIconsOnce.Do( loadIcons )
  7. 竞争检测
    1. go build/run/test -race
  8. *示例:并发非阻塞Cache(???)
  9. Goroutines与线程
    1. 可增长的栈:2KB --> 1GB
    2. m:n调度
    3. GOMAXPROCS
    4. Goroutines没有Identity:Simpler设计,函数参数直接影响行为

[编辑]包与Go工具

  1. Import路径
    1. 域名指定全称?Go Tool负责解析处理?
  2. package声明
    1. main()
    2. _test.go
  3. 重命名import
  4. 空import
    1. import _ "image/png" //仅仅为了执行其init()?Register PNG Decode();
  5. 包与命名
  6. Go工具
    1. $ export GOPATH=$HOME/gobook
    2. $ go get gopl.io/...
    3. $ go env
    4. $ go build ./src/gopl.io/ch1/helloworld //本地路径需要以./开始
      1. *_linux.go
      2. // +buils linux darwin
  7. *Documenting
    1. $ go doc ...
  8. 内部包
    1. net/http/internal/chunked仅可被net/http或net/http/httputil导入
  9. $ go list ...
    1. $ go list -f '模板:.ImportPath -> 模板:Join .Imports " "' compress/...

[编辑]Testing

  1. func TestNameOrFeature(t *testing.T) { ... t.Error(`...`) ...
  2. 代码覆盖
    1. $ go test -v -run=Coverage -cover-profile=c.out ...
    2. $ go tool cover -html=c.out
  3. Benchmark函数
  4. Profiling
    1. $ go test -cpuprofile=cpu.out -memprofile=mem.out --blockprofile=block.out
  5. *Example函数

[编辑]反射

  1. reflect.Type/Value
  2. 示例:编码S-表达式
  3. *访问struct域的tag(让我想起了JAXB)

[编辑]底层编程

  1. unsafe.Sizeof / Alignof / Offsetof
  2. unsafe.Pointer
    1. 可将*float64转换为*uint64,以查看浮点数的位模式
    2. 虽然当前Go并不使用moving GC...
  3. **用cgo来调用C语言代码(略)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值