接口 接口类型是一种抽象类型,是方法的集合,其他类型实现了这些方法就是实现了这个接口。
接口类型的值可以存放实现这些方法的任何值。
/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}
简单示例:打印不同几何图形的面积和周长
package mainimport (
"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 mainimport (
"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 float64func (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`。接口中可以内嵌接口(隐式接口)
【Go进阶之路——接口】类型通过实现那些方法来实现接口。 没有显式声明的必要;所以也就没有关键字“implements“。
隐式接口解藕了实现接口的包和定义接口的包:互不依赖。
因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义。
对第一个例子做以下修改:
- 首先添加
tmp
接口,该接口定义了area()
方法 - 将
tmp
作为geometry
接口中的匿名成员,并且将geometry
接口中原本定义的area()
方法删除
完成以上两步后,geometry
接口将会拥有tmp
接口所定义的所有方法。运行结果和上述例子相同。
package mainimport (
"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 mainimport "fmt"type Person struct {
Name string
Ageint
}func main() {
a := Person{"Arthur Dent", 42}
z := Person{"Zaphod Beeblebrox", 9001}
fmt.Println(a, z)
}
输出结果:
文章图片
实现Stringer接口
package mainimport "fmt"type Person struct {
Name string
Ageint
}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 mainimport "fmt"type IPAddr [4]bytefunc (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 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)
}
}
输出结果:
文章图片
通常函数会返回一个
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 mainimport (
"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)
returncase 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 mainimport (
"fmt"
)
type ErrNegativeSqrt float64func (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
输出结果:
文章图片
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 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
}
}
}
输出结果:
文章图片
源码如下:
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 httptype Handler interface { ServeHTTP(w ResponseWriter, r *Request) }
在这个例子中,类型
Hello
实现了 `http.Handler`。访问 http://localhost:4000/ 会看到来自程序的问候。
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)
}
}
文章图片
注:
Fprint 采用默认格式将其参数格式化并写入w。如果两个相邻的参数都不是字符串,会在它们的输出之间添加空格。返回写入的字节数和遇到的任何错误。
Fatal 等同于Print(),然后调用os.Exit(1)。
练习:HTTP 处理
实现下面的类型,并在其上定义 ServeHTTP 方法。在 web 服务器中注册它们来处理指定的路径。
type String stringtype Struct struct { Greeting string Punctstring Whostring }
例如,可以使用如下方式注册处理方法:
http.Handle("/string", String("I'm a frayed knot.")) http.Handle("/struct", &Struct{"Hello", ":", "Gophers!"})
package mainimport (
"fmt"
"log"
"net/http"
)type String stringfunc (s String) ServeHTTP(
w http.ResponseWriter,
r *http.Request) {
fmt.Fprint(w, s)
}type Struct struct {
Greeting string
Punctstring
Whostring
}
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 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 包定义。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())
}
推荐阅读
- Go|Docker后端部署详解(Go+Nginx)
- GO|GO,GO,GO!
- Go成长之路|go中判断空字符串、nil和len(t)的用法
- go编译tools
- go grpc安装与使用
- goroutine 调度原理
- Go|Go进阶之路——复杂类型
- Go进阶之路——变量
- Go进阶之路——流程控制语句