您的位置:首页 > 编程语言 > Go语言

go 指南--接口方法篇(接口以及方法的运用)

2017-09-22 18:08 246 查看
方法篇
方法

方法续

接收者为指针的方法

接口篇
接口

隐式接口

Stringers内建接口包含String string 方法
练习Stringers

接口方法篇
错误 error接口实现

练习错误

Readers ioreader调用系统实现
练习Reader实现read方法

练习rot13Reader 通过类修改流

图片 image接口

练习图片 输出 颜色块

http篇
Web 服务器

练习HTTP 处理

方法篇

go指南地址:https://tour.go-zh.org/methods/1

1.方法

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

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

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())
}


输出:

5

2.方法续

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

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

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.4142135623730951

3.接收者为指针的方法

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

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

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

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

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

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}
fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
v.Scale(5)  //结构体的值被Scale方法修改了,这里类的实现者Vertex传入的是指针; 如果是值拷贝,结构体内数据不会发生变化
fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
}


输出:

Before scaling: &{X:3 Y:4}, Abs: 5

After scaling: &{X:15 Y:20}, Abs: 25

接口篇

接口

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

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

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

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(MyFloat(-math.Sqrt2).Abs()) //若方法不是通过调用指针实现的,可直接初始化类并且调用方法,结构体同上:fmt.Println(Vertex{3, 4}.Abs()) (如果Vertex不是通过指针调用的)
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 {    //这里的方法的实现类的类型是指针,所以调用该方法时,也只能通过指针调用:v := Vertex{3, 4}; var a Abser = &v; fmt.Println(a.Abs())
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}


输出:

1.4142135623730951

5

隐式接口

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

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

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

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

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")
}


输出:

hello, writer

Stringers(内建接口,包含String() string 方法)

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

type Stringer interface {

String() string

}

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

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}
fmt.Printf("Person 类型: %T\n", a)
z := Person{"Zaphod Beeblebrox", 9001}
fmt.Println(a,"|", z)
}


输出:

Person 类型: main.Person

Arthur Dent (42 years) | Zaphod Beeblebrox (9001 years)

练习:Stringers

让 IPAddr 类型实现 fmt.Stringer 以便用点分格式输出地址。

例如,IPAddr{1, 2, 3, 4} 应当输出 “1.2.3.4”。

package main

import "fmt"

type IPAddr [4]byte

// TODO: Add a "String() string" method to IPAddr.
func (ip IPAddr) String() string{
return fmt.Sprintf("%v,%v.%v.%v", ip[0], ip[1], ip[2], ip[3])    //Sprintf:格式化返回数据
}

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)
}
}


输出:

loopback: 127,0.0.1

googleDNS: 8,8.8.8

接口方法篇

错误 (error接口实现)

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

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

type error interface {

Error() string

}

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

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

i, err := strconv.Atoi(“42”)

if err != nil {

fmt.Printf(“couldn’t convert number: %v\n”, err)

return

}

fmt.Println(“Converted integer:”, i)

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

package main

import (
"fmt"
"time"
"errors"
"strconv"
)

type MyError struct {
When time.Time
What string
}

func (e MyError) Error() string {
str := fmt.Sprintf("at %v, %s",
e.When, e.What)
fmt.Printf("1:%T\n", str)
return str
}

func run() error{
fmt.Println("0")
str := MyError{
time.Now(),
"it didn't work",
}
fmt.Printf("2:%T\n", str)
//fmt.Println(str.Error())
fmt.Println(MyError{
time.Now(),
"it didn't work",
})
return str
}

func test() error{
return errors.New("test err")
}

func main() {
if err := run(); err != nil {
fmt.Printf("3:%T\n", err)
fmt.Println(err)
}
//以下为测试其他类实现的Error()接口,这里为*strconv.NumError类型
errtest := test()
if errtest != nil{
fmt.Printf("4:%T\n", errtest)
fmt.Println(errtest)
}

i, errtest2 := strconv.Atoi("as")   //构造错误信息
if errtest2 != nil {
fmt.Printf("5:%T\n", errtest2)
fmt.Printf("couldn't convert number: %v\n", errtest2)   //代码执行此处
return
}
fmt.Println("Converted integer:", i)

}


输出:

0

2:main.MyError

1:string

at 2009-11-10 23:00:00 +0000 UTC m=+0.000000000, it didn’t work

3:main.MyError

1:string

at 2009-11-10 23:00:00 +0000 UTC m=+0.000000000, it didn’t work

4:*errors.errorString

test err

5:*strconv.NumError

couldn’t convert number: strconv.Atoi: parsing “as”: invalid syntax

练习:错误

从先前的练习中复制 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))。请思考这是为什么呢?

修改 Sqrt 函数,使其接受一个负数时,返回 ErrNegativeSqrt 值。

package main

import (
"fmt"
"math"
)
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 {
y := z - (z*z-x)/(2*z)
if math.Abs(y-z) < 1e-10 {
return y, nil
}
z = y
}
return z, nil
}

func main() {
fmt.Println(Sqrt(2))
fmt.Println(Sqrt(-2))
}


输出:

1.4142135623730951

0 cannot Sqrt negative number:-2

Readers (io.reader调用系统实现)

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)    //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
}
}
}


输出:

n = 8 err = b = [72 101 108 108 111 44 32 82]

b[:n] = “Hello, R”

n = 6 err = b = [101 97 100 101 114 33 32 82]

b[:n] = “eader!”

n = 0 err = EOF b = [101 97 100 101 114 33 32 82]

b[:n] = “”

练习:Reader(实现read()方法)

实现一个 Reader 类型,它不断生成 ASCII 字符 ‘A’ 的流。

package main

import (
"golang.org/x/tour/reader"
"time"
"fmt"
_"strings"
)

type MyReader struct{}

// TODO: Add a Read([]byte) (int, error) method to MyReader.
func (r MyReader) Read(b []byte) (int, error){
b[0] = 'A'
return 1, nil
}

func main() {
reader.Validate(MyReader{})    //

var myre MyReader
b := make([]byte, 1)
//for{
//r := strings.NewReader(b)
myre.Read(b)
fmt.Printf("%c\n", b[0])
time.Sleep(1 *time.Second)
myre.Read(b)
fmt.Println(b[0])
//}

}


输出:

OK!

A

65

练习:rot13Reader (通过类修改流) [???]

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

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

编写一个实现了 io.Reader 的 rot13Reader, 并从一个 io.Reader 读取, 利用 rot13 代换密码对数据流进行修改。

已经帮你构造了 rot13Reader 类型。 通过实现 Read 方法使其匹配 io.Reader。

package main

import (
"io"
"os"
"strings"
"errors"
"fmt"
)

type rot13Reader struct {
r io.Reader
}
//需要实现io.Reader 类型的方法:Read([]byte) (int, error)
func (rot rot13Reader) Read(buf []byte) (int, error){
fmt.Println(1)

l, err := rot.r.Read(buf)    //???
if err!= nil{
return 0, errors.New("some wrong")
}

//rot.r.Read(buf)
for k, v := range buf{
if v == byte(0){
return k, nil
}
buf[k] = v+'a'
}

return l, nil
}

func main() {
s := strings.NewReader("Lbh penpxrq gur pbqr!")
r := rot13Reader{s}
//fmt.Printf("%v\n", r)
io.Copy(os.Stdout, r)

buf := make([]byte, 30)
**_, err := r.Read(buf)**   //切片是以指针形式赋值的???
fmt.Println(err, buf)

}


输出:

1

��Ɂ������ҁ��Ӂ���ӂ1 //这是修改后的流

1

some wrong [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] //测试[]byte

图片 (image接口)

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())
}


输出:

(0,0)-(100,100)

0 0 0 0

练习:图片 (输出 颜色块)

还记得之前编写的图片生成器吗?现在来另外编写一个,不过这次将会返回 image.Image 来代替 slice 的数据。

自定义的 Image 类型,要实现必要的方法,并且调用 pic.ShowImage。

Bounds 应当返回一个 image.Rectangle,例如
image.Rect(0, 0, w, h)


ColorModel 应当返回 color.RGBAModel。

At 应当返回一个颜色;在这个例子里,在最后一个图片生成器的值 v 匹配
color.RGBA{v, v, 255, 255}


package main

import (
"golang.org/x/tour/pic"
"image"
"image/color"
//"fmt"
)
type Image struct{
weight int
height int

}

func (c Image) ColorModel() color.Model{
return color.RGBAModel
}

func (b *Image) Bounds() image.Rectangle{
return image.Rect(0, 0, b.weight, b.height)
}

func (a *Image) At(x, y int) color.Color{
//fmt.Println(x, y)
return color.RGBA{uint8(x), uint8(y), 255, 255}
}

func main() {
m := &Image{700,50}
//m.At(225, 0)
pic.ShowImage(m) //m.At(x, y)的参数由pic传入,传入了所有情况
}


输出:


http篇

Web 服务器

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

package http

type Handler interface {

ServeHTTP(w ResponseWriter, r *Request)

}

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

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

注意: 这个例子无法在基于 web 的指南用户界面运行。为了尝试编写 web 服务器,可能需要安装 Go。

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)
}
}


执行curl localhost:4000

输出:Hello!

练习: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!”})

在启动你的 http 服务器后,你将能够访问: http://localhost:4000/stringhttp://localhost:4000/struct.

注意: 这个例子无法在基于 web 的用户界面下运行。 为了尝试编写 web 服务,你可能需要 安装 go

package main

import (
"log"
"net/http"
"fmt"
)

type String string

type Struct struct{
greet string
comma string
who string
}

func (str String) ServeHTTP(
w http.ResponseWriter,
r *http.Request){

fmt.Fprint(w, str)
}

func (stuc Struct) ServeHTTP(
w http.ResponseWriter,
r *http.Request){

fmt.Fprint(w, stuc.greet, stuc.comma, stuc.who)
}

func main() {
//赋值类结构体返回的是对象,可直接调用对象实现的接口的方法
//string1 := String("I'm a frayed knot.")
//struct1 := &Struct{"Hello", ":", "Gophers!"}
//http.Handle("/string", string1)
//http.Handle("/struct", struct1)

//可直接给类结构体赋值顺便执行方法,可省去再返回利用实体对象再调用方法
http.Handle("/string", String("I'm a frayed knot."))
http.Handle("/struct", &Struct{"Hello", ":", "Gophers!"})
// your http.Handle calls here
log.Fatal(http.ListenAndServe("localhost:4000", nil)) //必须在绑定处理路径之后

}


curl localhost:4000/string

输出:I’m a frayed knot.

curl localhost:4000/struct

输出:Hello”, “:”, “Gophers!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  服务器 golang
相关文章推荐