第一章节、入门
1.1一个简单的程序
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
命令行
$ go run helloworld.go
$ go build helloworld.go
Go语言不需要在语句或者声明的末尾添加分号,除非一行上有多条语句。
Go语言在代码格式上采取了很强硬的态度。gofmt工具把代码格式化为标准格式。
很多文本编辑器都可以配置为保存文件时自动执行gofmt,这样你的源代码总会被恰当地格式化。还有个相关的工具,goimports,可以根据代码需要, 自动地添加或删除import声明。
$ go get golang.org/x/tools/cmd/goimports
1.2命令行参数
os.Args变量是一个字符串(string)的切片(slice)
用s[i]访问单个元素,用s[m:n]获取子序列
序列的元素数目为len(s)
Go言里也采用左闭右开形式,比如s[m:n]这个切片,0 ≤ m ≤ n ≤ len(s),包含n-m个元素。
os.Args[1:len(os.Args)]切片中。如果省略切片表达式的m或n,会默认传入0或len(s),因此前面的切片可以简写成os.Args[1:]。
第二章节、程序结构
2.1命名
- 在习惯上,Go语言程序员推荐使用 驼峰式 命名,当名字有几个单词组成的时优先使用大小写分隔
2.2声明
- 一个函数的声明由一个函数名字、参数列表(由函数的调用者提供参数变量的具体值)、一个可选的返回值列表和包含函数定义的函数体组成。如果函数没有返回值,那么返回值列表是省略的。
2.3变量
var 变量名字 类型 = 表达式
var i, j, k int // int, int, int
var b, f, s = true, 2.3, "four" // bool, float64, string
- 可以用:= 声明或者初始化局部变量
- 请记住“:=”是一个变量声明语句加上赋值,而“=”是一个变量赋值操作。
- 在下面的代码中,第一个语句声明了in和err两个变量。在第二个语句只声明了out一个变量,然后对已经声明的err进行了赋值操作。
in, err := os.Open(infile)
// ...
out, err := os.Create(outfile)
p := new(int)
2.4赋值
count[x] = count[x] * scale // 数组、slice或map的元素赋值x, y = y, x a[i], a[j] = a[j], a[i]
_, ok = x.(T) // 只检测类型,忽略具体值```
###2.5类型
1. 类型重定义:```type 类型名字 底层类型```
2. 类型转换:```类型名字(参数)```
3. 只有底层类型相同,或者底层类型可以互相转换的类,才可以做类型转换。
4. 类型不同(就算底层类型相同)也不能做比较运算
5. 下面的声明语句,Celsius类型的参数c出现在了函数名的前面,表示声明的是Celsius类型的一个名叫String的方法,该方法返回该类型对象c带着°C温度单位的字符串:
```func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }```
<div class="se-preview-section-delimiter"></div>
###2.6包和文件
1. 在Go语言中,一个简单的规则是:如果一个名字是大写字母开头的,那么该名字是导出的。也就是说别的包,只要引用了改包就可以使用的参数。
``fmt.Printf("Brrrr! %v\n", tempconv.AbsoluteZeroC) // "Brrrr! -273.15°C"``
2. 包内函数的使用:```fmt.Println(tempconv.CToF(tempconv.BoilingC)) // "212°F"```,注意只有引用包的文件可以调用函数。
3. 在Go语言程序中,每个包都有一个全局唯一的导入路径。导入语句中类似"gopl.io/ch2/tempconv"的字符串对应包的导入路径。
<div class="se-preview-section-delimiter"></div>
```go
import(
"gopl.io/ch2/tempconv"
)
func init() {/*...*/}
//使用init初始化
package popcount
// pc[i] is the population count of i.
var pc [256]byte
func init() {
for i := range pc {
pc[i] = pc[i/2] + byte(i&1)
}
}
//或者使用匿名函数初始化
// pc[i] is the population count of i.
var pc [256]byte = func() (pc [256]byte) {
for i := range pc {
pc[i] = pc[i/2] + byte(i&1)
}
return
}()
2.7作用域
- 当编译器遇到一个名字引用时,如果它看起来像一个声明,它首先从最内层的词法域向全局的作用域查找。如果查找失败,则报告“未声明的名字”这样的错误。如果该名字在内部和外部的块分别声明过,则内部块的声明首先被找到。在这种情况下,内部声明屏蔽了外部同名的声明,让外部的声明的名字无法被访问。
- 在函数中词法域可以深度嵌套,因此内部的一个声明可能屏蔽外部的声明。还有许多语法块是if或for等控制流语句构造的。下面的代码有三个不同的变量x,因为它们是定义在不同的词法域(这个例子只是为了演示作用域规则,但不是好的编程风格)。
func main() {
x := "hello!"
for i := 0; i < len(x); i++ {
x := x[i]
if x != '!' {
x := x + 'A' - 'a'
fmt.Printf("%c", x) // "HELLO" (one letter per iteration)
}
}
}
- 会在for、if、switch语句等条件部分创建隐式词法域。
- if语句嵌套在第一个内部,因此第一个if语句条件初始化词法域声明的变量在第二个if中也可以访问。
if x := f(); x == 0 {
fmt.Println(x)
} else if y := g(x); x == y {
fmt.Println(x, y)
} else {
fmt.Println(x, y)
}
fmt.Println(x, y) // compile error: x and y are not visible here
- 如果一个变量或常量递归引用了自身,则会产生编译错误。
- 变量f的作用域只有在if语句内,因此后面的语句将无法引入它,这将导致编译错误。你可能会收到一个局部变量f没有声明的错误提示,具体错误信息依赖编译器的实现。
if f, err := os.Open(fname); err != nil { // compile error: unused: f
return err
}
f.ReadByte() // compile error: undefined f
f.Close() // compile error: undefined f
第三章、基础数据类型
3.5字符串
- 字符串的值是不可变的:一个字符串包含的字节序列永远不会被改变。
- 因为字符串是不可修改的,因此尝试修改字符串内部数据的操作也是被禁止的。
- 一个原生的字符串面值形式是“,使用反引号代替双引号。
- Go语言的range循环在处理字符串的时候,会自动隐式解码UTF8字符串。
- 将[]rune类型转换应用到UTF8编码的字符串,将返回字符串编码的Unicode码点序列:
// "program" in Japanese katakana
s := "プログラム"
fmt.Printf("% x\n", s) // "e3 83 97 e3 83 ad e3 82 b0 e3 83 a9 e3 83 a0"
r := []rune(s)
fmt.Printf("%x\n", r) // "[30d7 30ed 30b0 30e9 30e0]"
如果是将一个[]rune类型的Unicode字符slice或数组转为string,则对它们进行UTF8编码:
fmt.Println(string(r)) // "プログラム"
- 将一个整数转型为字符串意思是生成以只包含对应Unicode码点字符的UTF8字符串:
fmt.Println(string(65)) // "A", not "65"
fmt.Println(string(0x4eac)) // "京"
- 标准库中有四个包对字符串处理尤为重要:bytes、strings、strconv和unicode包.
- bytes包也提供了很多类似功能的函数,但是针对和字符串有着相同结构的[]byte类型。
- strconv包提供了布尔型、整型数、浮点数和对应字符串的相互转换,还提供了双引号转义相关的转换。
- unicode包提供了IsDigit、IsLetter、IsUpper和IsLower等类似功能,它们用于给字符分类。
3.6常量
- 对常量的类型转换操作或以下函数调用都是返回常量结果。
- 如果是批量声明的常量,除了第一个外其它的常量右边的初始化表达式都可以省略,如果省略初始化表达式则表示使用前面常量的初始化表达式写法,对应的常量类型也一样的。例如:
const (
a = 1
b
c = 2
d
)
fmt.Println(a, b, c, d) // "1 1 2 2"
- 常量声明可以使用iota常量生成器初始化,iota将会被置为0,然后在每一个有常量声明的行加一。
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
- 许多常量并没有一个明确的基础类型。这里有六种未明确类型的常量类型,分别是无类型的布尔型、无类型的整数、无类型的字符、无类型的浮点数、无类型的复数、无类型的字符串。
- 常量的形式将隐式决定变量的默认类型,无类型整数常量转换为int,它的内存大小是不确定的,但是无类型浮点数和复数常量则转换为内存大小明确的float64和complex128。
第四章、复合数据类型
4.1数组
- 数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。
var a [3]int // array of 3 integers
fmt.Println(a[0]) // print the first element
fmt.Println(a[len(a)-1]) // print the last element, a[2]
- 默认情况下,数组的每个元素都被初始化为元素类型对应的零值,对于数字类型来说就是0。
- 如果一个数组的元素类型是可以相互比较的,那么数组类型也是可以相互比较的,这时候我们可以直接通过==比较运算符来比较两个数组。
4.2Slice切片
- 多个slice之间可以共享底层的数据,并且引用的数组部分区间可能重叠。
- 数据结构:
- 如果切片操作超出cap(s)的上限将导致一个panic异常,但是超出len(s)则是意味着扩展了slice,因为新slice的长度会变大:
fmt.Println(summer[:20]) // panic: out of range
endlessSummer := summer[:5] // extend a slice (within capacity)
fmt.Println(endlessSummer) // "[June July August September October]"
- 和数组不同的是,slice之间不能比较,因此我们不能使用==操作符来判断两个slice是否含有全部相等元素。标准库提供了高度优化的bytes.Equal函数来判断两个字节型slice是否相等([]byte),但是对于其他类型的slice,我们必须自己展开每个元素进行比较。
- slice唯一合法的比较操作是和nil比较。
- 如果你需要测试一个slice是否是空的,使用len(s) == 0来判断,而不应该用s == nil来判断。
- 在底层,make创建了一个匿名的数组变量,然后返回一个slice;只有通过返回的slice才能引用底层匿名的数组变量。
make([]T, len)
make([]T, len, cap) // same as make([]T, cap)[:len]
- 内置的append函数用于向slice追加元素:
var runes []rune
for _, r := range "Hello, 世界" {
runes = append(runes, r)
}
fmt.Printf("%q\n", runes) // "['H' 'e' 'l' 'l' 'o' ',' ' ' '世' '界']"
runes = append(runes, r)stack = append(stack, v) // push vtop := stack[len(stack)-1] // top of stackstack = stack[:len(stack)-1] // pop
4.3Map
ages := make(map[string]int) // mapping from strings to intsnames := make([]string, 0, len(ages))
age, ok := ages["bob"]
if !ok { /* "bob" is not a key in this map; age == 0. */ }
map[string]bool
4.4结构体
- 通常一行对应一个结构体成员,成员的名字在前类型在后,不过如果相邻的成员类型如果相同的话可以被合并到一行。
type Employee struct {
ID int
Name, Address string
DoB time.Time
Position string
Salary int
ManagerID int
}
- 如果结构体成员名字是以大写字母开头的,那么该成员就是导出的。其他包只能访问结构体内导出的参数。
- 一个命名为S的结构体类型将不能再包含S类型的成员:因为一个聚合的值不能包含它自身。但是S类型的结构体可以包含*S指针类型的成员
- 二叉树实现插入排序(这个写法很有意思):
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
}
- 结构体字面值可以指定每个成员的值。
type Point struct{ X, Y int }
p := Point{1, 2}
p := Point{a:1, b:2}
pp := &Point{1, 2}//这里pp是指针
pp := new(Point)
*pp = Point{1, 2}
- 你不能企图在外部包中用第一种顺序赋值的技巧来偷偷地初始化结构体中未导出的成员。
- 如果考虑效率的话,较大的结构体通常会用指针的方式传入和返回。在Go语言中,所有的函数参数都是值拷贝传入的,函数参数将不再是函数调用时的原始变量。
- 如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的,那样的话两个结构体将可以使用==或!=运算符进行比较。
- Go语言有一个特性让我们只声明一个成员对应的数据类型而不指名成员的名字;这类成员就叫匿名成员。我们可以直接访问叶子属性而不需要给出完整的路径:
type Circle struct {
Point
Radius int
}
type Wheel struct {
Circle
Spokes int
}
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{8, 8, 5, 20} // compile error: unknown fields
w = Wheel{X: 8, Y: 8, Radius: 5, Spokes: 20} // compile error: unknown fields
- 不能同时包含两个类型相同的匿名成员,这会导致名字冲突。
第五章、函数
5.1函数声明
- 实参通过值的方式传递,因此函数的形参是实参的拷贝。对形参进行修改不会影响实参。但是,如果实参包括引用类型,如指针,slice(切片)、map、function、channel等类型,实参可能会由于函数的间接引用被修改。
5.2递归
- 大部分编程语言使用固定大小的函数调用栈,常见的大小从64KB到2MB不等。固定大小栈会限制递归的深度,当你用递归处理大量数据时,需要避免栈溢出;除此之外,还会导致安全性问题。与相反,Go语言使用可变栈,栈的大小按需增加(初始时很小)。这使得我们使用递归时不必考虑溢出和安全问题。
5.3多返回值
- 如果一个函数将所有的返回值都显示的变量名,那么该函数的return语句可以省略操作数。这称之为bare return。
/ CountWordsAndImages does an HTTP GET request for the HTML
// document url and returns the number of words and images in it.
func CountWordsAndImages(url string) (words, images int, err error) {
resp, err := http.Get(url)
if err != nil {
return
}
doc, err := html.Parse(resp.Body)
resp.Body.Close()
if err != nil {
err = fmt.Errorf("parsing HTML: %s", err)
return
}
words, images = countWordsAndImages(doc)
return
}
func countWordsAndImages(n *html.Node) (words, images int) { /* ... */ }
- bare return 可以减少代码的重复,但是使得代码难以被理解。不宜过度使用bare return。
5.4错误
return nil, fmt.Errorf("parsing %s as HTML: %v", url,err)
// WaitForServer attempts to contact the server of a URL.
// It tries for one minute using exponential back-off.
// It reports an error if all attempts fail.
func WaitForServer(url string) error {
const timeout = 1 * time.Minute
deadline := time.Now().Add(timeout)
for tries := 0; time.Now().Before(deadline); tries++ {
_, err := http.Head(url)
if err == nil {
return nil // success
}
log.Printf("server not responding (%s);retrying…", err)
time.Sleep(time.Second << uint(tries)) // exponential back-off
}
return fmt.Errorf("server %s failed to respond after %s", url, timeout)
}
3) 输出错误信息并结束程序。
4)可以通过log包提供函数,或者标准错误流输出错误信息。
if err := Ping(); err != nil {
log.Printf("ping failed: %v; networking disabled",err)
}
if err := Ping(); err != nil {
fmt.Fprintf(os.Stderr, "ping failed: %v; networking disabled\n", err)
}
5) 直接忽略
5.5函数值
- 函数类型的零值是nil。调用值为nil的函数值会引起panic错误。
- 函数值之间是不可比较的,也不能用函数值作为map的key。
5.6匿名函数(比较重要)
- 匿名函数可以在函数中被定义,并且这种方式定义的函数可以访问完整的词法环境,这意味着在函数中定义的内部函数可以引用该函数的变量。
// squares返回一个匿名函数。
// 该匿名函数每次被调用时都会返回下一个数的平方。
func squares() func() int {
var x int
return func() int {
x++
return x * x
}
}
func main() {
f := squares()
fmt.Println(f()) // "1"
fmt.Println(f()) // "4"
fmt.Println(f()) // "9"
fmt.Println(f()) // "16"
}
- 匿名函数内使用递归的话,就一定要先声明函数,再赋值函数。下面
var visitAll func(items []string)//第一步声明
visitAll = func(items []string) {//第二步定义
...}
}
5.7可变参数
- 在声明可变参数函数时,需要在参数列表的最后一个参数类型之前加上省略符号“…”,这表示该函数会接收任意数量的该类型参数。
func sum(vals...int) int {
total := 0
for _, val := range vals {
total += val
}
return total
}
values := []int{1, 2, 3, 4} fmt.Println(sum(values...)) // "10"
5.8Deferred函数
defer resp.Body.Close()
func bigSlowOperation() {
defer trace("bigSlowOperation")() // don't forget the
extra parentheses
// ...lots of work…
time.Sleep(10 * time.Second) // simulate slow
operation by sleeping
}
func trace(msg string) func() {
start := time.Now()
log.Printf("enter %s", msg)
return func() {
log.Printf("exit %s (%s)", msg,time.Since(start))
}
}
$ go build gopl.io/ch5/trace
$ ./trace
2015/11/18 09:53:26 enter bigSlowOperation
2015/11/18 09:53:36 exit bigSlowOperation (10.000589217s)
- 对匿名函数采用defer机制,可以使其观察函数的返回值。
func double(x int) int {
return x + x
}
5.9Panic异常
panic(err)
5.10Recover捕获异常
- 如果在deferred函数中调用了内置函数recover,并且定义该defer语句的函数发生了panic异常,recover会使程序从panic中恢复,并返回panic value。导致panic异常的函数不会继续运行,但能正常返回。在未发生panic时调用recover,recover会返回nil。
- 尽量不要使用,因为异常可能会导致资源管理有漏洞,尽量只针对少数几种异常恢复,如下:
// soleTitle returns the text of the first non-empty title element
// in doc, and an error if there was not exactly one.
func soleTitle(doc *html.Node) (title string, err error) {
type bailout struct{}
defer func() {
switch p := recover(); p {
case nil: // no panic
case bailout{}: // "expected" panic
err = fmt.Errorf("multiple title elements")
default:
panic(p) // unexpected panic; carry on panicking
}
}()
// Bail out of recursion if we find more than one nonempty title.
forEachNode(doc, func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "title" &&
n.FirstChild != nil {
if title != "" {
panic(bailout{}) // multiple titleelements
}
title = n.FirstChild.Data
}
}, nil)
if title == "" {
return "", fmt.Errorf("no title element")
}
return title, nil
}
- 有些情况下,我们无法恢复。某些致命错误会导致Go在运行时终止程序,如内存不足。
第六章、方法
6.1方法声明
- 举例:
type Point struct{ X, Y float64 }
// same thing, but as a method of the Point type
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
- 由于方法和字段都是在同一命名空间,所以如果我们在这里声明一个X方法的话,编译器会报错,因为在调用p.X时会有歧义.
6.2基于指针对象方法
func (p *Point) ScaleBy(factor float64) { p.X *= factor p.Y *= factor }type P *int func (P) f() { /* ... */ } // compile error: invalid receiver type(&p).ScaleBy(2)p.ScaleBy(2)Point{1, 2}.ScaleBy(2) // compile error: can't take address of Point literalpptr.Distance(q) (*pptr).Distance(q)
6.3通过嵌入结构体来扩展类型
- 举例:
import "image/color"
type Point struct{ X, Y float64 }
type ColoredPoint struct {
Point
Color color.RGBA
}
p.Distance(q.Point)p.Distance(q)
type ColoredPoint struct {
*Point
Color color.RGBA
}
p := ColoredPoint{&Point{1, 1}, red}
q := ColoredPoint{&Point{5, 4}, blue}
fmt.Println(p.Distance(*q.Point)) // "5"
q.Point = p.Point // p and q now share the same Point
p.ScaleBy(2)
fmt.Println(*p.Point, *q.Point) // "{2 2} {2 2}"
var cache = struct { sync.Mutex mapping map[string]string }{ mapping: make(map[string]string), }
6.4方法值和方法表达式
distanceFromP := p.Distance // method value fmt.Println(distanceFromP(q)) // "5"
p := Point{1, 2}
q := Point{4, 6}
distance := Point.Distance // method expression
fmt.Println(distance(p, q)) // "5"
fmt.Printf("%T\n", distance) // "func(Point, Point) float64"
scale := (*Point).ScaleBy
scale(&p, 2)
fmt.Println(p) // "{2 4}"
fmt.Printf("%T\n", scale) // "func(*Point, float64)"
// 译注:这个Distance实际上是指定了Point对象为接收器的一个方法func (p Point) Distance(),
// 但通过Point.Distance得到的函数需要比实际的Distance方法多一个参数,
// 即其需要用第一个额外参数指定接收器,后面排列Distance方法的参数。
// 看起来本书中函数和方法的区别是指有没有接收器,而不像其他语言那样是指有没有返回值。
- 下面就是一个比较有意思的例子,会根据不同的输入绑定不同的方法
ype Point struct{ X, Y float64 }
func (p Point) Add(q Point) Point { return Point{p.X + q.X, p.Y + q.Y} }
func (p Point) Sub(q Point) Point { return Point{p.X - q.X, p.Y - q.Y} }
type Path []Point
func (path Path) TranslateBy(offset Point, add bool) {
var op func(p, q Point) Point
if add {
op = Point.Add
} else {
op = Point.Sub
}
for i := range path {
// Call either path[i].Add(offset) or path[i].Sub(offset).
path[i] = op(path[i], offset)
}
}
第七章、接口
7.2接口类型
- 普通接口:
package io
type Reader interface {
Read(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
- 内嵌型:
type ReadWriter interface {
Reader
Writer
}
- 上面两种类型可以混合
7.3实现接口的条件
var _ = IntSet{}.String()compile error: String requires *IntSet receivervar s IntSet;var _ = s.String()
7.4flag.Value接口
f := 实现接口类型{value参数};flag.CommandLine.Var(&f, name, usage)
type Value interface {
String() string
Set(string) error
}
flag.Parse()
7.5. 接口值
var w io.Writerw = os.Stdoutvar buf *bytes.Buffer
7.6sort.Interface接口
- 用来排序的接口,需要完成一下接口的定义才能使用:
package sort
type Interface interface {
Len() int
Less(i, j int) bool // i, j are indices of sequence elements
Swap(i, j int)
}
举例:
type StringSlice []string
func (p StringSlice) Len() int { return len(p) }
func (p StringSlice) Less(i, j int) bool { return p[i] < p[j] }
func (p StringSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
sort.Sort(StringSlice(names))//执行排序
- sort.Reverse函数是逆向排序,注意!结构体reverse嵌入了一个sort.Interface,这样就可以直接调用interface中的方法,对自己的接口方法重构
package sort
type reverse struct{ Interface } // that is, sort.Interface
func (r reverse) Less(i, j int) bool { return r.Interface.Less(j, i) }
func Reverse(data Interface) Interface { return reverse{data} }
sort.Sort(sort.Reverse(接口值))
7.7. http.Handler接口
- 一个标准的http服务端程序
func main() {
db := database{"shoes": 50, "socks": 5}
log.Fatal(http.ListenAndServe("localhost:8000", db))
}
type dollars float32
func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) }
type database map[string]dollars
func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {
switch req.URL.Path {
case "/list":
for item, price := range db {
fmt.Fprintf(w, "%s: %s\n", item, price)
}
case "/price":
item := req.URL.Query().Get("item")
price, ok := db[item]
if !ok {
w.WriteHeader(http.StatusNotFound) // 404
fmt.Fprintf(w, "no such item: %q\n", item)
return
}
fmt.Fprintf(w, "%s\n", price)
default:
w.WriteHeader(http.StatusNotFound) // 404
fmt.Fprintf(w, "no such page: %s\n", req.URL)
}
}
- 使用ServeMux:
func main() {
db := database{"shoes": 50, "socks": 5}
mux := http.NewServeMux()
mux.Handle("/list", http.HandlerFunc(db.list))
mux.Handle("/price", http.HandlerFunc(db.price))
log.Fatal(http.ListenAndServe("localhost:8000", mux))
}
type database map[string]dollars
func (db database) list(w http.ResponseWriter, req *http.Request) {
for item, price := range db {
fmt.Fprintf(w, "%s: %s\n", item, price)
}
}
func (db database) price(w http.ResponseWriter, req *http.Request) {
item := req.URL.Query().Get("item")
price, ok := db[item]
if !ok {
w.WriteHeader(http.StatusNotFound) // 404
fmt.Fprintf(w, "no such item: %q\n", item)
return
}
fmt.Fprintf(w, "%s\n", price)
}
中间db.list和db.price都是方法值,语句http.HandlerFunc(db.list)是一个转换而非一个函数调用,因为http.HandlerFunc是一个类型
package http
type HandlerFunc func(w ResponseWriter, r *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
注意这里HandlerFunc类型是一个函数,这种方法很巧妙,可以把函数值强制转换为HandlerFunc再执行
4. 但是在大多数程序中,一个web服务器就足够了。此外,在一个应用程序的多个文件中定义HTTP handler也是非常典型的,如果它们必须全部都显示的注册到这个应用的ServeMux实例上会比较麻烦。所以net/http包提供了一个全局的ServeMux实例DefaultServerMux和包级别的http.Handle和http.HandleFunc函数。
7.8. error接口
- 接口:
type error interface {
Error() string
}
fmt.Println(err.Error())
7.9. 示例: 表达式求值
//TODO
7.10. 类型断言
- 可以通过断言获取类型T的具体类型
var w io.Writer
w = os.Stdout
f := w.(*os.File) // success: f == os.Stdout
c := w.(*bytes.Buffer) // panic: interface holds *os.File, not *bytes.Buffer
- 返回两个参数的断言,,这个操作不会在失败的时候发生panic但是代替地返回一个额外的第二个结果
var w io.Writer = os.Stdout
f, ok := w.(*os.File) // success: ok, f == os.Stdout
b, ok := w.(*bytes.Buffer) // failure: !ok, b == nil
- 断言不仅可以断言类型,也可以断言该类型的一个方法是否存在
var w io.Writer
w = os.Stdout
rw := w.(io.ReadWriter) // success: *os.File has both Read and Write
w = new(ByteCounter)
rw = w.(io.ReadWriter) // panic: *ByteCounter has no Read method
- 如果断言操作的对象是一个nil接口值,那么不论被断言的类型是什么这个类型断言都会失败。
7.11. 基于类型断言区别错误类型
- 判断err类型是不是*PathError
func IsNotExist(err error) bool {
if pe, ok := err.(*PathError); ok {
err = pe.Err
}
return err == syscall.ENOENT || err == ErrNotExist
}
7.12. 通过类型断言询问行为
- 通过断言可以判断方法是否存在的特性,golang可以根据不同类型的方法是否实现,来选择调用方法
func writeString(w io.Writer, s string) (n int, err error) {
type stringWriter interface {
WriteString(string) (n int, err error)
}
if sw, ok := w.(stringWriter); ok {
return sw.WriteString(s) // avoid a copy
}
return w.Write([]byte(s)) // allocate temporary copy
}
上面的代码,判断类型是否实现了stringWriter方法,如果实现了调用这个方法,如果没有就调用Write方法
7.13. 类型开关
- 就是根据断言结果,分类操作
switch x.(type) {
case nil: // ...
case int, uint: // ...
case bool: // ...
case string: // ...
default: // ...
}
7.14. 示例: 基于标记的XML解码
//TODO(主要是上个章节类型开关的实际使用)
7.15. 一些建议
- 接口只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要。
- 当一个接口只被一个单一的具体类型实现时有一个例外,就是由于它的依赖,这个具体类型不能和这个接口存在在一个相同的包中。这种情况下,一个接口是解耦这两个包的一个好方式。
- 3.