接口
接口类型是一种抽象类型,是方法的集合,其他类型实现了这些方法就是实现了这个接口。
接口类型的值可以存放实现这些方法的任何值。
/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}
简单示例:打印不同几何图形的面积和周长
package main
import (
"fmt"
"math"
)
type geometry interface {
area() float32
perim() float32
}
type rect struct {
len, wid float32
}
func (r rect) area() float32 {
return r.len * r.wid
}
func (r rect) perim() float32 {
return 2 * (r.len + r.wid)
}
type circle struct {
radius float32
}
func (c circle) area() float32 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float32 {
return 2 * math.Pi * c.radius
}
func show(name string, param interface{}) {
switch param.(type) {
case geometry:
// 类型断言
fmt.Printf("area of %v is %v \n", name, param.(geometry).area())
fmt.Printf("perim of %v is %v \n", name, param.(geometry).perim())
default:
fmt.Println("wrong type!")
}
}
func main() {
rec := rect{
len: 1,
wid: 2,
}
show("rect", rec)
cir := circle{
radius: 1,
}
show("circle", cir)
show("test", "test param")
}
输出结果:
对于函数 show(name string, param interface{}),其传入的参数param可以是任意类型,只有实现了对应接口中的所有方法,才算是实现了该接口(即存在该类型为接收者的接口中对应的所有方法)。param.(type)获得接口类型名称。
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
fmt.Println(a.Abs())
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)
}
注意: 如上代码中, 由于 Vertex的 Abs
只定义在 *Vertex(指针类型)
上, 所以 Vertex(值类型)
不满足 `Abser`。
接口中可以内嵌接口(隐式接口)
类型通过实现那些方法来实现接口。 没有显式声明的必要;所以也就没有关键字“implements“。
隐式接口解藕了实现接口的包和定义接口的包:互不依赖。
因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义。
对第一个例子做以下修改:
- 首先添加
tmp
接口,该接口定义了area()
方法 - 将
tmp
作为geometry
接口中的匿名成员,并且将geometry
接口中原本定义的area()
方法删除
完成以上两步后,
geometry
接口将会拥有tmp
接口所定义的所有方法。运行结果和上述例子相同。
package main
import (
"fmt"
"math"
)
type tmp interface{
area() float32
}
type geometry interface {
// area() float32
tmp
perim() float32
}
type rect struct {
len, wid float32
}
func (r rect) area() float32 {
return r.len * r.wid
}
func (r rect) perim() float32 {
return 2 * (r.len + r.wid)
}
type circle struct {
radius float32
}
func (c circle) area() float32 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float32 {
return 2 * math.Pi * c.radius
}
func show(name string, param interface{}) {
switch param.(type) {
case geometry:
// 类型断言
fmt.Printf("area of %v is %v \n", name, param.(geometry).area())
fmt.Printf("perim of %v is %v \n", name, param.(geometry).perim())
default:
fmt.Println("wrong type!")
}
}
func main() {
rec := rect{
len: 1,
wid: 2,
}
show("rect", rec)
cir := circle{
radius: 1,
}
show("circle", cir)
show("test", "test param")
}
Stringers
一个普遍存在的接口: fmt
包中定义的 Stringer
。
type Stringer interface {
String() string
}
Stringer
是一个可以用字符串描述自己的类型。`fmt`包 (还有许多其他包)使用这个来进行输出。
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
a := Person{"Arthur Dent", 42}
z := Person{"Zaphod Beeblebrox", 9001}
fmt.Println(a, z)
}
输出结果:
实现Stringer接口
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)
}
输出结果:
如此实现了自定义的输出方式。
注:Sprintf 根据格式说明符设置格式,并返回结果字符串。
小练习:
让 IPAddr
类型实现 fmt.Stringer
以便用点分格式输出地址。
例如,`IPAddr{1,`2,`3,`4}` 应当输出 `"1.2.3.4"`。
package main
import "fmt"
type IPAddr [4]byte
func (a IPAddr)String() string{
return fmt.Sprintf("%v.%v.%v.%v",a[0],a[1],a[2],a[3])
}
func main() {
addrs := map[string]IPAddr{
"loopback": {127, 0, 0, 1},
"googleDNS": {8, 8, 8, 8},
}
for n, a := range addrs {
fmt.Printf("%v: %v\n", n, a)
}
}
错误error
Go 程序使用 error
值来表示错误状态。
与 fmt.Stringer
类似,`error` 类型是一个内建接口:
type error interface {
Error() string
}
(与 fmt.Stringer
类似,`fmt` 包在输出时也会试图匹配 `error`。)
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)
}
}
输出结果:
通常函数会返回一个 error
值,调用的它的代码应当判断这个错误是否等于 `nil`, 来进行错误处理。
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
表示错误。
注:strconv 包中结构体 NumError 实现了 error 接口,Atoi 函数将字符串转换为对应数字。
package main
import (
"fmt"
"strconv"
)
func main() {
i, err := strconv.Atoi("42")
if err != nil {
fmt.Printf("couldn't convert number: %v\n", err)
}
fmt.Println("Converted integer:", i)
i, err = strconv.Atoi("i")
if err != nil {
fmt.Printf("couldn't convert number: %v\n", err)
}
fmt.Println("Converted integer:", i)
}
输出结果:
小练习:
从之前的练习中复制 Sqrt
函数,并修改使其返回 error
值。
Sqrt
接收到一个负数时,应当返回一个非 nil 的错误值。复数同样也不被支持。
创建一个新类型
type ErrNegativeSqrt float64
为其实现
func (e ErrNegativeSqrt) Error() string
使其成为一个 `error`, 该方法就可以让 ErrNegativeSqrt(-2).Error()
返回 `"cannot Sqrt negative number: -2"`。
注意: 在 Error
方法内调用 fmt.Sprint(e)
将会让程序陷入死循环。可以通过先转换 e
来避免这个问题:`fmt.Sprint(float64(e))`。
解释:
因为 ErrNegativeSqrt 实现了 error 接口,e也是error类型,故 fmt.Sprint(e)
将调用e.Error()
将e
转换为字符串。如果Error()
方法调用fmt.Sprint(e)
,则程序将递归直到内存溢出。相当于:
func (e ErrNegativeSqrt) Error() string {
return fmt.Sprint(e.Error())
}
故通过将e
转换成一个非error类型(未实现Error接口)的值可以避免这种情况。
实际上在Error
方法中把error
值直接传递给fmt
包中Print相关的函数都会导致无限循环。
看源码:print.go 中,Sprint 方法和 Sprintf 方法都用到了 printArg 方法,printArg 方法中用到了 handleMethods方法,该方法部分源码如下:
else {
// If a string is acceptable according to the format, see if
// the value satisfies one of the string-valued interfaces.
// Println etc. set verb to %v, which is "stringable".
switch verb {
case 'v', 's', 'x', 'X', 'q':
// Is it an error or Stringer?
// The duplication in the bodies is necessary:
// setting handled and deferring catchPanic
// must happen before calling the method.
switch v := p.arg.(type) {
case error:
handled = true
defer p.catchPanic(p.arg, verb, "Error")
p.fmtString(v.Error(), verb)
return
case Stringer:
handled = true
defer p.catchPanic(p.arg, verb, "String")
p.fmtString(v.String(), verb)
return
}
}
}
可见 p.fmtString(v.Error(), verb),如果传入的接口是error类型,则会调用其Error方法。
修改 Sqrt
函数,使其接受一个负数时,返回 ErrNegativeSqrt
值。
package main
import (
"fmt"
)
type ErrNegativeSqrt float64
func (e ErrNegativeSqrt) Error() string{
return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
}
func Sqrt(x float64) (float64, error) {
if x<0 {
return 0, ErrNegativeSqrt(x)
}
z := float64(1)
for {
temp := z - (z*z-x)/(2*z)
if temp==z || (temp>z && temp-z<0.00000001) || (temp<z && z-temp<0.00000001) {
z = temp
break
}
z = temp
}
return z, nil
}
func main() {
fmt.Println(Sqrt(2))
fmt.Println(Sqrt(-2))
}
输出结果:
Readers
io
包指定了 io.Reader
接口, 它表示从数据流结尾读取。
Go 标准库包含了这个接口的许多实现, 包括文件、网络连接、压缩、加密等等。
io.Reader
接口有一个 Read
方法:
func (T) Read(b []byte) (n int, err error)
Read
用数据填充指定的字节 slice,并且返回填充的字节数和错误信息。 在遇到数据流结尾时,返回 io.EOF
错误。
例子代码创建了一个 strings.Reader
。 并且以每次 8 字节的速度读取它的输出。
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
}
}
}
输出结果:
源码如下:
func (r *Reader) Read(b []byte) (n int, err error) {
if r.i >= int64(len(r.s)) {
return 0, io.EOF
}
r.prevRune = -1
n = copy(b, r.s[r.i:])
r.i += int64(n)
return
}
Web 服务器
包 http 通过任何实现了 http.Handler
的值来响应 HTTP 请求:
package http type Handler interface { ServeHTTP(w ResponseWriter, r *Request) }
在这个例子中,类型 Hello
实现了 `http.Handler`。
访问 http://localhost:4000/ 会看到来自程序的问候。
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)
}
}
注:
Fprint 采用默认格式将其参数格式化并写入w。如果两个相邻的参数都不是字符串,会在它们的输出之间添加空格。返回写入的字节数和遇到的任何错误。
Fatal 等同于Print(),然后调用os.Exit(1)。
练习:HTTP 处理
实现下面的类型,并在其上定义 ServeHTTP 方法。在 web 服务器中注册它们来处理指定的路径。
type String string type Struct struct { Greeting string Punct string Who string }
例如,可以使用如下方式注册处理方法:
http.Handle("/string", String("I'm a frayed knot.")) http.Handle("/struct", &Struct{"Hello", ":", "Gophers!"})
package main
import (
"fmt"
"log"
"net/http"
)
type String string
func (s String) ServeHTTP(
w http.ResponseWriter,
r *http.Request) {
fmt.Fprint(w, s)
}
type Struct struct {
Greeting string
Punct string
Who string
}
func (s *Struct) ServeHTTP(
w http.ResponseWriter,
r *http.Request) {
fmt.Fprint(w, s.Greeting,s.Punct,s.Who)
}
func main() {
http.Handle("/string", String("I'm a frayed knot."))
http.Handle("/struct", &Struct{"Hello", ":", "Gophers!"})
//http.Handle calls here
log.Fatal(http.ListenAndServe("localhost:4000", nil))
}
图片
Package image 定义了 Image
接口:
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 包定义。
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())
}