当前位置: 首页 > news >正文

go tour方法和接口

go tour方法和接口

方法

方法就是一类带特殊的 接收者 参数的函数。

  • 方法接收者在它自己的参数列表内,位于 func 关键字和方法名之间。
type Vertex struct {X, Y float64
}
//Abs 方法拥有一个名字为 v,类型为 Vertex 的接收者。
func (f MyFloat) Abs() float64 {if f < 0 {return float64(-f)}return float64(f)
}func main() {f := MyFloat(-math.Sqrt2)fmt.Println(f.Abs())q := MyFloat(-5.0)fmt.Println(q.Abs())
}
1.4142135623730951
5

接收者的类型定义和方法声明必须在同一包内

  • 也就是说在该包内定义了 MyFloat,可以为它定义方法。
  • 如果尝试为其他包中的类型(比如内置的 int 或其他包自定义的类型)声明方法,Go 编译器会报错。
  • 确保了类型的封装性和一致性。
type MyFloat float64func (f MyFloat) Abs() float64 {if f < 0 {return float64(-f)}return float64(f)
}func main() {f := MyFloat(-math.Sqrt2)fmt.Println(f.Abs())
}//错误的
func (x int) SomeMethod() {} // 错误:不能为 int 类型声明方法1.4142135623730951

指针类型的接收者

  • 对于某类型 T,接收者的类型可以用 *T 的文法。
  • 指针接收者的方法可以修改接收者指向的值,指针接收者比值接收者更常用。
type Vertex struct {X, Y float64
}func (v Vertex) Abs() float64 {return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
//删除了*后输出的是5
//使用值接收者,那么 Scale 方法会对原始 Vertex 值的副本进行操作。
func (v *Vertex) Scale(f float64) {v.X = v.X * fv.Y = v.Y * f
}func main() {v := Vertex{3, 4}v.Scale(10)fmt.Println(v.Abs())
}50
//副本接收
func (v Vertex) Scale(f float64) Vertex{v.X = v.X * fv.Y = v.Y * freturn v
}func main() {v := Vertex{3, 4}p:=v.Scale(10)fmt.Println(p.Abs())
}
//输出
50

方法与指针重定向

  • 到带指针参数的函数必须接受一个指针
  • 而接收者为指针的的方法被调用时,接收者既能是值又能是指针
type Vertex struct {X, Y float64
}//接收者为指针的方法,调用时接收者既能是值又能是指针:
func (v *Vertex) Scale(f float64) {v.X = v.X * fv.Y = v.Y * f
}//普通函数
func ScaleFunc(v *Vertex, f float64) {v.X = v.X * fv.Y = v.Y * f
}func main() {v := Vertex{3, 4}v.Scale(2)//接受者是值 //等价于 (&v).Scale(5)ScaleFunc(&v, 10)p := &Vertex{4, 3}p.Scale(3)//接收者是指针ScaleFunc(p, 8)fmt.Println(v, p)
}
//
{60 80} &{96 72}
  • 以值为接收者的方法被调用时,接收者既能为值又能为指针:
type Vertex struct {X, Y float64
}func (v Vertex) Abs() float64 {return math.Sqrt(v.X*v.X + v.Y*v.Y)
}func AbsFunc(v Vertex) float64 {return math.Sqrt(v.X*v.X + v.Y*v.Y)
}func main() {v := Vertex{3, 4}fmt.Println(v.Abs())fmt.Println(AbsFunc(v))//注意pp := &Vertex{4, 3}fmt.Println(p.Abs())//等价于(*p).Abs()fmt.Println(AbsFunc(*p))
}

使用指针接收者的好处

  • 方法能够修改其接收者指向的值
  • 这样可以避免在每次调用方法时复制该值。若值的类型为大型结构体时,这样会更加高效
  • 注意:值和指针接收者不应该混用

接口

接口类型 的定义为一组方法签名。

接口类型的变量可以持有任何实现了这些方法的值。

type Abser interface {Abs() float64
}
func main() {var a Abserf := MyFloat(-math.Sqrt2)v := Vertex{3, 4}a = f  // a MyFloat 实现了 Abserfmt.Println(a.Abs())a = &v // a *Vertex 实现了 Abser// 下面一行,v 是一个 Vertex(而不是 *Vertex)// 所以没有实现 Abser。//a = vfmt.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)
}
1.4142135623730951
5

接口隐式定义

  • 类型通过实现一个接口的所有方法来实现该接口。
type I interface {M()
}type T struct {S string
}// 此方法表示类型 T 实现了接口 I,并不需要显式声明这一点。
func (t T) M() {fmt.Println(t.S)
}func main() {
//等价于	var i I = T{"hello"}var i Ii=T{"hello"}i.M()
}
hello

接口值

  • 接口也是值。它们可以像其它值一样传递。
  • 接口值可以用作函数的参数或返回值。
  • 在内部,接口值可以看做包含值和具体类型的元组:
(value, type)
  • 接口值保存了一个具体底层类型的具体值。
  • 接口值调用方法时会执行其底层类型的同名方法。
package mainimport ("fmt""math"
)type I interface {M()
}type T struct {S string
}func (t *T) M() {fmt.Println(t.S)
}type F float64func (f F) M() {fmt.Println(f)
}func main() {var i Ii = &T{"Hello"}describe(i)i.M()i = F(math.Pi)describe(i)i.M()
}func describe(i I) {fmt.Printf("(%v, %T)\n", i, i)
}
(&{Hello}, *main.T)
Hello
(3.141592653589793, main.F)
3.141592653589793

底层值为 nil 的接口值

  • 即便接口内的具体值为 nil,方法仍然会被 nil 接收者调用
  • 在一些语言中,这会触发一个空指针异常【java】,但在 Go 中通常会写一些方法来优雅地处理它(如本例中的 M 方法)。
package mainimport "fmt"type I interface {M()
}type T struct {S string
}func (t *T) M() {if t == nil {fmt.Println("<nil>")return}fmt.Println(t.S)
}func main() {var i Ivar t *Ti = tdescribe(i)i.M()i = &T{"hello"}describe(i)i.M()
}func describe(i I) {fmt.Printf("(%v, %T)\n", i, i)
}(<nil>, *main.T)
<nil>
(&{hello}, *main.T)
hello

nil 接口值

  • nil 接口值既不保存值也不保存具体类型。
  • 运行时会产生运行错误
    • 因为接口的元组内并未包含能够指明该调用哪个 具体 方法的类型。
type I interface {M()
}func main() {var i Idescribe(i)i.M()
}(<nil>, <nil>)
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x492bb9]

空接口

  • 【不会产生运行错误】空接口可保存任何类型的值。(因为每个类型都至少实现了零个方法。)
func main() {var i interface{}describe(i)i = 42describe(i)i = "hello"describe(i)
}func describe(i interface{}) {fmt.Printf("(%v, %T)\n", i, i)
}
(<nil>, <nil>)
(42, int)
(hello, string)

类型断言

  • 提供了访问接口值底层具体值的方式。
//该语句断言接口值 i 保存了具体类型 T,并将其底层类型为 T 的值赋予变量 t。
//若 i 并未保存 T 类型的值,该语句就会触发一个 panic。
t := i.(T)//返回其底层值以及一个报告断言是否成功的布尔值。
t, ok := i.(T)

举例

func main() {var i interface{} = "hello"s := i.(string)fmt.Println(s)s, ok := i.(string)fmt.Println(s, ok)
//ok 将为 false 而 t 将为 T 类型的零值,程序并不会产生 panic。f, ok := i.(float64)fmt.Println(f, ok)f = i.(float64) // panicfmt.Println(f)
}
hello
hello true
0 false
panic: interface conversion: interface {} is string, not float64

类型选择

按顺序从几个类型断言中选择分支的结构。

  • 类型选择中的声明与类型断言 i.(T) 的语法相同,只是具体类型 T 被替换成了关键字 type
//case都是类型,针对给定接口值所存储的值的类型进行比较。
switch v := i.(type) {
case T:// v 的类型为 T
case S:// v 的类型为 S
default:// 没有匹配,v 与 i 的类型相同
}

举例

func do(i interface{}) {switch v := i.(type) {case int:fmt.Printf("二倍的 %v 是 %v\n", v, v*2)case string:fmt.Printf("%q 长度为 %v 字节\n", v, len(v))default:fmt.Printf("我不知道类型 %T!\n", v)}
}func main() {do(21)do("hello")do(true)
}
二倍的 2142
"hello" 长度为 5 字节
我不知道类型 bool!

Stringer

  • 打印的时候调用,用字符串描述自己的类型。
  • 感觉很像是重写了Println方法
type Person struct {Name stringAge  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)
}
Arthur Dent (42 years) Zaphod Beeblebrox (9001 years)

*练习:Stringer

问题:

通过让 IPAddr 类型实现 fmt.Stringer 来打印点号分隔的地址。

例如,IPAddr{1, 2, 3, 4} 应当打印为 "1.2.3.4"

实现
package mainimport "fmt"type IPAddr [4]byte// TODO: 为 IPAddr 添加一个 "String() string" 方法。
func (p IPAddr) String() string {return fmt.Sprintf("%v.%v.%v.%v", p[0], p[1],p[2],p[3])}func main() {hosts := map[string]IPAddr{"loopback":  {127, 0, 0, 1},"googleDNS": {8, 8, 8, 8},}for name, ip := range hosts {fmt.Printf("%v: %v\n", name, ip)}
}
loopback: 127.0.0.1
googleDNS: 8.8.8.8

错误

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

  • error为nil时成功
  • 非 nil 的 error 表示失败。
import ("fmt""time"
)type MyError struct {When time.TimeWhat 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)}
}at 2009-11-10 23:00:00 +0000 UTC m=+0.000000001, it didn't work

*练习:错误

问题

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

实现
package mainimport ("fmt""math"
)type ErrNegativeSqrt float64func (e ErrNegativeSqrt) Error() string{//return fmt.Sprintf("cannot Sqrt negative number: %v",e)//为什么上面的会死循环?return fmt.Sprintf("cannot Sqrt negative number: %v",float64(e))}func Sqrt(x float64) (float64, error) {if x<0.0 {return x, ErrNegativeSqrt(x)}z := 1.0 // 修正了变量声明t := math.Abs(z*z - x)for i := 0; i < 10; i++ {if z*z > x { z -= (z*z - x) / (2 * z)} else {z += (z*z - x) / (2 * z)}t = math.Abs(z*z - x)if t==0.0 {break}}return z,nil
}func main() {fmt.Println(Sqrt(2))fmt.Println(Sqrt(-2))}
  • Error 方法内调用 fmt.Sprint(e) 会让程序陷入死循环。可以通过先转换 e 来避免这个问题:fmt.Sprint(float64(e))。这是为什么呢?
  • 接口转换和递归调用
    • 你实现了 Error() string 方法,所以 ErrNegativeSqrt 类型实现了 error 接口。
    • 当你在 Error() 方法中写 fmt.Sprint(e)fmt.Sprint 会尝试将 e 格式化为字符串。
    • 因为 eErrNegativeSqrt 类型,并且实现了 Error() 方法,fmt.Sprint(e) 会自动调用 e.Error(),以获得字符串表示。
  • 递归的无限循环
    • 结果就是:Error() 调用 fmt.Sprint(e),而 fmt.Sprint(e) 又会调用 e.Error(),于是再次进到 Error() 方法,然后又 fmt.Sprint(e),如此反复,导致死循环直到栈溢出。

Readers

  • io 包指定了 io.Reader 接口
  • 表示数据流的读取端。

read方法

  • Read 用数据填充给定的字节切片并返回填充的字节数和错误值。在遇到数据流的结尾时,它会返回一个 io.EOF 错误。
func (T) Read(b []byte) (n int, err error)
//示例代码创建了一个 strings.Reader 并以每次 8 字节的速度读取它的输出。
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}}
}n = 8 err = <nil> b = [72 101 108 108 111 44 32 82]
b[:n] = "Hello, R"
n = 6 err = <nil> 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

问题

实现一个 Reader 类型,它产生一个 ASCII 字符 'A' 的无限流。

实现
package mainimport ("golang.org/x/tour/reader"
)type MyReader struct{}// Read 用于产生无限个 ASCII 字符 'A'
func (MyReader) Read(b []byte) (int, error) {for i := range b {b[i] = 'A'}return len(b), nil // 始终填满整个切片
}func main() {reader.Validate(MyReader{})
}

*练习:rot13Reader

问题

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

rot13Reader 类型已经提供。实现 Read 方法以满足 io.Reader

实现
  • 两种实现
  • 很奇怪,else if前一行加注释会报错
package mainimport ("io""os""strings"
)type rot13Reader struct {r io.Reader
}// Read 用于产生无限个 ASCII 字符 'A'
func (rr rot13Reader) Read(b []byte) (int,error) {n, err := rr.r.Read(b)for i := range b {// a-mif b[i] >= 'a' && b[i] <= 'm' {b[i] = b[i] + 13} else if b[i] >= 'A' && b[i] <= 'M' {b[i] = b[i] + 13}else if b[i] >= 'n' && b[i] <= 'z' {b[i] = b[i] - 13} else if b[i] >= 'N' && b[i] <= 'Z' {b[i] = b[i] - 13}}return n,err// 始终填满整个切片
}/*
func (rr rot13Reader) Read(b []byte) (int, error) {n, err := rr.r.Read(b)for i := 0; i < n; i++ {switch {case b[i] >= 'A' && b[i] <= 'Z':b[i] = 'A' + (b[i]-'A'+13)%26case b[i] >= 'a' && b[i] <= 'z':b[i] = 'a' + (b[i]-'a'+13)%26}}return n, err
}*/func main() {s := strings.NewReader("Lbh penpxrq gur pbqr!")r := rot13Reader{s}io.Copy(os.Stdout, &r)
}

图像

image 包定义了 Image 接口:

type Image interface {ColorModel() color.ModelBounds() RectangleAt(x, y int) color.Color
}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 的实现而非一个数据切片。

  • 定义你自己的 Image 类型,实现必要的方法并调用 pic.ShowImage
  • Bounds 应当返回一个 image.Rectangle ,例如 image.Rect(0, 0, w, h)
  • ColorModel 应当返回 color.RGBAModel
  • At 应当返回一个颜色。上一个图片生成器的值 v 对应于此次的 color.RGBA{v, v, 255, 255}
实现
package mainimport ("image""image/color""golang.org/x/tour/pic"
)type Image struct {Width, Height int
}// Bounds 返回图像的边界
func (img Image) Bounds() image.Rectangle {return image.Rect(0, 0, img.Width, img.Height)
}// ColorModel 返回颜色模型
func (img Image) ColorModel() color.Model {return color.RGBAModel
}// At 返回指定坐标的颜色
func (img Image) At(x, y int) color.Color {// 生成颜色值,v 对应于 color.RGBA{v, v, 255, 255}v := uint8((x + y) % 256) t := uint8((x * y) % 256)return color.RGBA{v, t, 255, 255}
}func main() {m := Image{256, 256} // 设定图像宽高pic.ShowImage(m)
}

相关文章:

  • Mobaxterm解锁Docker
  • OpenCV CUDA模块图像处理------颜色空间处理之颜色空间转换函数cvtColor()
  • 高效多线程图像处理实战
  • 知识图谱:AI时代语义认知的底层重构逻辑
  • ASP.NET Web Forms框架识别
  • WPF【11_4】WPF实战-重构与美化(MVVM 架构)
  • ArcGIS Pro 3.4 二次开发 - 知识图谱
  • 飞牛fnNAS手机相册备份及AI搜图
  • 私服 nexus 之间迁移 npm 仓库
  • 融智学“新五常”框架:五维方式的重构与协同
  • 银河麒麟V10×R²AIN SUITE:用AI重构安全,以国产化生态定义智能未来
  • libvirt设置虚拟机mtu实现原理
  • 强大的免费工具,集合了30+功能
  • Ansible 配置Playbook文件格式、关键字和语法详解
  • WPF【11_8】WPF实战-重构与美化(UI 与视图模型的联动,实现INotifyPropertyChanged)
  • Leetcode-5 好数对的数目
  • AI Agents执行流程和决策流程学习
  • 多相电机驱动控制学习(1)——基于双dq坐标系的六相PMSM驱动控制
  • 基于物联网(IoT)的电动汽车(EVs)智能诊断
  • Unsupervised Learning-Word Embedding
  • 做网站有送企业邮箱吗/seo的理解
  • 南京网站公司/广州seo推广营销
  • 看看铜陵新闻/优化百度百科
  • 南京网站建站公司/网络推广是什么职位
  • 做房产中介需要有内部网站吗/yoast seo教程
  • 设计类专业网站有哪些/营销培训视频课程免费