博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Go 之旅四: 方法与接口篇
阅读量:5966 次
发布时间:2019-06-19

本文共 5956 字,大约阅读时间需要 19 分钟。

hot3.png

本文是学习 (中文参考 ) 整理的笔记,介绍Go 语言方法,接口,类型的基本概念和使用。

1. 方法

$GOPATH/src/go_note/gotour/methods/method/method.go 源码如下:

/** * go 语言 方法 */package mainimport (	"fmt"	"math")type Vertex struct {	X, Y float64}/** * 方法名: Abs_method * 方法接收者: Vertex */func (v Vertex) Abs_method() float64 {	return math.Sqrt(v.X*v.X + v.Y*v.Y)}// 传指针func (v *Vertex) Scale(f float64) {	v.X = v.X * f	v.Y = v.Y * f}// 函数func Abs_function(v Vertex) float64 {	return math.Sqrt(v.X*v.X + v.Y*v.Y)}// 为非结构体声明方法type MyFloat float64func (f MyFloat) Abs_myfloat() float64 {	if f < 0 {		return float64(-f)	}	return float64(f)}func main() {	v := Vertex{3, 4}	v.Scale(10) // 此时 v.Scale(10) 会隐式转为 (&v).Scale(10)	fmt.Println(v.Abs_method())	fmt.Println(Abs_function(v)) // 方法只是个带接收者参数的函数	f := MyFloat(-2)	fmt.Println(f.Abs_myfloat())}

Go 没有类。不过你可以为结构体类型定义方法。

方法是一类带特殊的 接收者 参数的函数,方法接收者位于方法 func 关键字和方法名之间。

1.1 非结构体类型声明方法

只能为在同一包内定义的类型添加方法, 而不能为其它包内定义的类型(包括 int 之类的内建类型)的接收者声明方法。即接收者的类型定义和方法声明必须在同一包内;不能为内建类型声明方法。

type MyFloat float64func (f MyFloat) Abs_myfloat () float64 {    if f < 0 {        return float64(-f)    }    return float64(f)}

1.2 指针接收者

为指针接收者声明方法:对于某类型 T ,指针接收者的类型可以用 *T 表示。(T 不能是像 *int 这样的指针。)

指针接收者的方法可以修改接收者指向的值。 由于方法经常需要修改它的接收者,指针接收者比值接收者更常用。若使用值接收者,方法只会对原始值的副本进行操作。

1.3 方法与指针重定向(隐式转换)

以指针为接收者的方法被调用时,接收者既能为值又能为指针:

func (v *Vertex) Scale(f float64) {}v.Scale(5)(&v).Scale(5)

由于 Scale 方法有一个指针接收者,为方便起见,Go 会将语句 v.Scale(5) 解释为 (&v).Scale(5)

而以值为接收者的方法被调用时,接收者既能为值又能为指针:

var v Vertexfmt.Println(v.Abs()) // OKp := &vfmt.Println(p.Abs()) // OK

这种情况下,方法调用 p.Abs() 会被解释为 (*p).Abs()。函数必须接受与定义相同的类型,不会隐式转换

1.4 选择值或指针作为接收者

使用指针接收者的原因有二:

  • 方法能够修改接收者指向的值。
  • 避免在每次调用方法时复制该值,若值的类型为大型结构体时,这样做会更加高效。

通常来说,所有给定类型的方法都应该有值或指针接收者,但并不应该二者混用。

2. 接口

$GOPATH/src/go_note/gotour/methods/interface/interface.go 源码如下:

/** * go 接口 */package mainimport (	"fmt"	"math")// 定义接口type Abser interface {	Abs() float64}func main() {	// 使用接口	var a Abser	f := MyFloat(-math.Sqrt2)	a = f	fmt.Println(a.Abs())	v := Vertex{3, 4}	a = &v	fmt.Println(a.Abs())	var i I	var t *T	i = t	i.M()	i = &T{"hello"}	i.M()	// 空接口	var inter_empty interface{}	inter_empty = 42	fmt.Printf("%v, %T\n", inter_empty, inter_empty)	inter_empty = "hello"	fmt.Printf("%v, %T\n", inter_empty, inter_empty)	// 类型断言	var j interface{} = "hello"	s := j.(string)	fmt.Println(s)	s, ok := j.(string)	fmt.Println(s, ok)	inter_float, ok := j.(float64)	fmt.Println(inter_float, ok)	// 类型选择	do(21)	do("hello")	do(true)}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)}type I interface {	M()}type T struct {	S string}func (t *T) M() {	if t == nil {		fmt.Println("
") return } fmt.Println(t.S)}

接口类型 是由一组方法签名定义的集合, 接口类型的值可以保存任何实现了这些方法的值。

类型通过实现一个接口的所有方法来实现该接口, 既然无需专门显式声明,也就没有“implements“关键字。隐式接口将接口的实现与定义解耦,这样接口的实现可以出现在任何包中,无需提前定义。

2.1 接口值

在内部,接口值可以看做包含值和具体类型的元组:

(value, type)

接口值保存了一个具体底层类型的具体值,接口值调用方法时会调用具体类型的的同名方法。

2.2 底层值为 nil 的接口值

即便接口内的具体值为 nil,方法仍然会被 nil 接收者调用。保存了 nil 具体值的接口其自身并不为 nil

但是接口值nil时,由于此时接口值既不保存值也不保存具体类型,调用方法会产生运行时错误,因为接口的元组内并未包含能够指明该调用哪个具体类型的方法。

func main() {    var i I    i.M() // panic: runtime error        var t *T    i = t    i.M() // 
}type I interface { M()}type T struct { S string}func (t *T) M() { if t == nil { fmt.Println("
") return } fmt.Println(t.S)}

2.3 空接口

指定了零个方法的接口值被称为空接口:

interface{}

因为每个类型都至少实现了零个方法,空接口可保存任何类型的值。

2.4 类型断言

类型断言提供了访问接口值底层具体值的方式。

t := i.(T)

该语句断言接口值 i 保存了具体类型 T ,并将其底层类型为 T 的值赋予变量 t 。如果 i 并未保存 T 类型的值,该语句就会触发一个错误。

为了判断一个接口值是否保存了一个特定的类型, 类型断言可返回两个值:其底层值和判断断言是否成功的布尔值。

t, ok := i.(T)

i 保存了一个 T ,那么 t 将会是其底层值,而 ok 为 true 。否则, ok 将为 falset 将为 T 类型的零值,程序并不会产生错误。

2.5 类型选择

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

类型选择与一般的 switch 语句相似,不过类型选择中的 case 为类型(而非值),它们针对给定接口值所存储值的类型进行比较

switch v := i.(type) {case T:    // v 的类型为 Tcase S:    // v 的类型为 Sdefault:    // 没有匹配,v 与 i 的类型相同}

类型选择中的声明与类型断言 i.(T) 的语法相同,只是具体类型 T 被替换成了关键字 type

此选择语句判断接口值 i 保存的值类型是 T 还是 S。 在 TS 的情况下,变量 v 会分别按 TS 类型取保存在 i 中的值。在默认(没有匹配)的情况下,变量 vi 的接口类型和值相同。

3. Stringer

$GOPATH/src/go_note/gotour/methods/stringer/stringer.go 源码如下:

/** * go String */package mainimport (	"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{"Author", 42}	z := Person{"Modifier", 1989}	fmt.Println(a, z)}

fmt 包中定义的 Stringer 是最普遍的接口之一。

type Stringer interface {    String() string}

Stringer 是一个可以用字符串描述自己的类型。fmt 包(还有很多包)都通过此接口来打印值。

4. 错误

$GOPATH/src/go_note/gotour/methods/error/error.go 源码如下:

/** * go语言 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)	}}

Go 程序使用 error 值来表示错误状态。与 fmt.Stringer 类似, error 类型是一个内建接口:

type error interface {    Error() string}

通常函数会返回一个 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)

errornil 时表示成功;非 nilerror 表示失败。

5. Reader

$GOPATH/src/go_note/gotour/methods/reader/reader.go 源码如下:

/** * go read */package mainimport (    "fmt"    "io"    "strings")func main() {    r := strings.NewReader("Hello, workd!")    b := make([]byte, 4)    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        }    }}

io 包指定了 io.Reader 接口, 它表示从数据流的末尾进行读取。Go 标准库包含了该接口的许多实现,包括文件、网络连接、压缩和加密等等。

io.Reader 接口有一个 Read 方法:

func (T) Read(b []byte) (n int, err error)

Read 用数据填充给定的字节切片并返回填充的字节数和错误值。 在遇到数据流的结尾时,它会返回一个 io.EOF 错误。

参考

可以关注我的微博了解更多信息:

转载于:https://my.oschina.net/xgzx/blog/845432

你可能感兴趣的文章
Httpd2.4简介及CenOS6.6下编译安装
查看>>
解决思维导图软件Mindmanager Mindjet连接出错
查看>>
谷歌logo的“前世今生”
查看>>
Apache配置文件中的deny和allow的使用
查看>>
缓存java框架技术预研4:LazyUnsafeAllocator.java算法分析
查看>>
监控zabbix 服务并在异常时python 邮件报警
查看>>
【转】linux/unix下 pid文件作用浅析
查看>>
4.9.5 通用注释
查看>>
PXE无人值守系统安装配置简要说明
查看>>
冰点文库下载V2绿色版,无需积分自由下载百度,mbalib,豆丁,畅享,hp009,max.book118 文档...
查看>>
composer笔记
查看>>
关于CRM库存初始化的一点小总结
查看>>
IntelliJ IDEA 12 中用 Maven + Jetty 来开发Web项目
查看>>
asterisk远程注册
查看>>
03-备份压缩命令
查看>>
电子表格控件Spreadsheet 对象方法事件详细介绍
查看>>
我的友情链接
查看>>
zabbix-server 的安装-centos7
查看>>
注销其他用户
查看>>
软路由ros(MIKROTIK)安装教程:[3]ROS注册
查看>>