方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "fmt" "math" ) type Abser interface { Abs() float64 } func main () { var a Abser f := MyFloat(-math.Sqrt2) v := Vertex{3 , 4 } a = f a = &v 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 mainimport ( "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 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 mainimport "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 mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "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 httptype 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 mainimport ( "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 imagetype 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 包定义。