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

本文详细介绍了Go语言中的方法和接口。方法可以定义在结构体或任意类型上,指针接收者允许修改值。接口是方法集合,类型通过实现接口的方法来实现接口。Go的错误处理常常涉及error接口,而io.Reader接口则用于读取数据流。此外,文章还涵盖自定义错误、HTTP服务器和处理等实践应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

方法篇

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!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值