参考资料:

方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := &Vertex{3, 4}
fmt.Println(v.Abs())
}

Go 没有类。然而,仍然可以在结构体类型上定义方法。

方法接收者 出现在 func 关键字和方法名之间的参数中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main
import (
"fmt"
"math"
)
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
func main() {
f := MyFloat(-math.Sqrt2)
fmt.Println(f.Abs())
}

你可以对包中的 任意 类型定义任意方法,而不仅仅是针对结构体。

但是,不能对来自其他包的类型或基础类型定义方法。

接收者为指针的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := &Vertex{3, 4}
v.Scale(5)
fmt.Println(v, v.Abs())
}

方法可以与命名类型或命名类型的指针关联。

刚刚看到的两个 Abs 方法。一个是在 *Vertex 指针类型上,而另一个在 MyFloat 值类型上。 有两个原因需要使用指针接收者。首先避免在每个方法调用中拷贝值(如果值类型是大的结构体的话会更有效率)。其次,方法可以修改接收者指向的值。

尝试修改 Abs 的定义,同时 Scale 方法使用 Vertex 代替 *Vertex 作为接收者。

当 v 是 Vertex 的时候 Scale 方法没有任何作用。Scale 修改 v。当 v 是一个值(非指针),方法看到的是 Vertex 的副本,并且无法修改原始值。

Abs 的工作方式是一样的。只不过,仅仅读取 v。所以读取的是原始值(通过指针)还是那个值的副本并没有关系。

接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package main
import (
"fmt"
"math"
)
type Abser interface {
Abs() float64
}
func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}
a = f // a MyFloat 实现了 Abser
a = &v // a *Vertex 实现了 Abser
// 下面一行,v 是一个 Vertex(而不是 *Vertex)
// 所以没有实现 Abser。
a = v
fmt.Println(a.Abs())
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

接口类型是由一组方法定义的集合。

接口类型的值可以存放实现这些方法的任何值。

注意: 例子代码的 22 行存在一个错误。 由于 Abs 只定义在 *Vertex(指针类型)上, 所以 Vertex(值类型)不满足 Abser。

隐式接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main
import (
"fmt"
"os"
)
type Reader interface {
Read(b []byte) (n int, err error)
}
type Writer interface {
Write(b []byte) (n int, err error)
}
type ReadWriter interface {
Reader
Writer
}
func main() {
var w Writer
// os.Stdout 实现了 Writer
w = os.Stdout
fmt.Fprintf(w, "hello, writer\n")
}

类型通过实现那些方法来实现接口。没有显式声明的必要;所以也就没有关键字 implements

隐式接口解藕了实现接口的包和定义接口的包:互不依赖。

因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义。

包 io 定义了 Reader 和 Writer;其实不一定要这么做。

Stringers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main
import "fmt"
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}
func main() {
a := Person{"Arthur Dent", 42}
z := Person{"Zaphod Beeblebrox", 9001}
fmt.Println(a, z)
}

一个普遍存在的接口是 fmt 包中定义的 Stringer。

1
2
3
type Stringer interface {
String() string
}

Stringer 是一个可以用字符串描述自己的类型。fmt 包 (还有许多其他包)使用这个来进行输出。

错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main
import (
"fmt"
"time"
)
type MyError struct {
When time.Time
What string
}
func (e *MyError) Error() string {
return fmt.Sprintf("at %v, %s",
e.When, e.What)
}
func run() error {
return &MyError{
time.Now(),
"it didn't work",
}
}
func main() {
if err := run(); err != nil {
fmt.Println(err)
}
}

Go 程序使用 error 值来表示错误状态。

与 fmt.Stringer 类似, error 类型是一个内建接口:

1
2
3
type error interface {
Error() string
}

(与 fmt.Stringer 类似,fmt 包在输出时也会试图匹配 error。)

通常函数会返回一个 error 值,调用的它的代码应当判断这个错误是否等于 nil, 来进行错误处理。

1
2
3
4
5
i, err := strconv.Atoi("42")
if err != nil {
fmt.Printf("couldn't convert number: %v\n", err)
}
fmt.Println("Converted integer:", i)

error 为 nil 时表示成功;非 nil 的 error 表示错误。

下例演示了对 sqrt 函数进行负数情况的错误处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main
import (
"fmt"
"math"
)
type ErrNegativeSqrt float64
func (e ErrNegativeSqrt) Error() string {
return fmt.Sprintf("cannot Sqrt negative number: %f", e)
}
func Sqrt(x float64) (float64, error) {
if x < 0 {
return 0, ErrNegativeSqrt(x)
}
return math.Sqrt(x), nil
}
func main() {
ret1, err1 := Sqrt(2)
if err1 == nil {
fmt.Println(ret1)
} else {
fmt.Println(err1)
}
ret2, err2 := Sqrt(-2)
if err2 == nil {
fmt.Println(ret2)
} else {
fmt.Println(err2)
}
}

Readers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main
import (
"fmt"
"io"
"strings"
)
func main() {
r := strings.NewReader("Hello, Reader!")
b := make([]byte, 8)
for {
n, err := r.Read(b)
fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
fmt.Printf("b[:n] = %q\n", b[:n])
if err == io.EOF {
break
}
}
}

io 包指定了 io.Reader 接口, 它表示从数据流结尾读取。

一个常见模式是 io.Reader 包裹另一个 io.Reader,然后通过某种形式修改数据流。

例如,gzip.NewReader 函数接受 io.Reader(压缩的数据流)并且返回同样实现了 io.Reader*gzip.Reader(解压缩后的数据流)。

Go 标准库包含了这个接口的许多实现, 包括文件、网络连接、压缩、加密等等。

io.Reader 接口有一个 Read 方法:

1
func (T) Read(b []byte) (n int, err error)

Read 用数据填充指定的字节 slice,并且返回填充的字节数和错误信息。 在遇到数据流结尾时,返回 io.EOF 错误。

例子代码创建了一个 strings.Reader。 并且以每次 8 字节的速度读取它的输出。

Web 服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main
import (
"fmt"
"log"
"net/http"
)
type Hello struct{}
func (h Hello) ServeHTTP(
w http.ResponseWriter,
r *http.Request) {
fmt.Fprint(w, "Hello!")
}
func main() {
var h Hello
err := http.ListenAndServe("localhost:4000", h)
if err != nil {
log.Fatal(err)
}
}

包 http 通过任何实现了 http.Handler 的值来响应 HTTP 请求:

1
2
3
4
5
package http
type Handler interface {
ServeHTTP(w ResponseWriter, r *Request)
}

在这个例子中,类型 Hello 实现了 http.Handler

访问 http://localhost:4000/ 会看到来自程序的问候。

图片

1
2
3
4
5
6
7
8
9
10
11
12
package main
import (
"fmt"
"image"
)
func main() {
m := image.NewRGBA(image.Rect(0, 0, 100, 100))
fmt.Println(m.Bounds())
fmt.Println(m.At(0, 0).RGBA())
}

Package image 定义了 Image 接口:

1
2
3
4
5
6
7
package image
type Image interface {
ColorModel() color.Model
Bounds() Rectangle
At(x, y int) color.Color
}

注意:Bounds 方法的 Rectangle 返回值实际上是一个 image.Rectangle, 其定义在 image 包中。

color.Color 和 color.Model 也是接口,但是通常因为直接使用预定义的实现 image.RGBA 和 image.RGBAModel 而被忽视了。这些接口和类型由 image/color 包定义。

Comments