文档章节

Go 之旅四: 方法与接口篇

好刚
 好刚
发布于 2017/02/24 12:57
字数 2242
阅读 60
收藏 0
点赞 0
评论 0

原文链接 http://ironxu.com/701

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

1. 方法

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

/**
 * go 语言 方法
 */

package main

import (
	"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 float64

func (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 float64
func (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 Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK

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

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

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

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

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

2. 接口

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

/**
 * go 接口
 */

package main

import (
	"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("<nil>")
		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() // <nil>
}

type I interface {
    M()
}

type T struct {
    S string
}
func (t *T) M() {
    if t == nil {
        fmt.Println("<nil>")
        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 的类型为 T
case S:
    // v 的类型为 S
default:
    // 没有匹配,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 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{"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 main

import (
	"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 main

import (
    "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 错误。

参考

可以关注我的微博了解更多信息: @刚刚小码农

© 著作权归作者所有

共有 人打赏支持
好刚
粉丝 5
博文 27
码字总数 30189
作品 0
武汉
程序员
Android OpenGL开发目录

Android OpenGL开发目录 Android OpenGL开发1--VS2017+OpenGL环境的配置 to be continued... 其它目录 Android NDK开发之旅 目录 Android NDK开发之旅1--NDK介绍 Android NDK开发之旅2--C语言...

香沙小熊 ⋅ 01/07 ⋅ 0

Android NDK开发之旅 目录

Android NDK开发之旅 目录 Android NDK开发之旅1--NDK介绍 Android NDK开发之旅2--C语言--基本数据类型 Android NDK开发之旅3--C语言--指针 Android NDK开发之旅4--C语言--动态内存分配 Andr...

香沙小熊 ⋅ 2017/12/31 ⋅ 0

Android 网络编程 目录

Android 网络编程 目录 Android 网络编程1 Http协议 to be continued... Android 架构师之路 目录 Android 架构师之路1 UML图之用例图 Android 架构师之路2 UML图之类图 Android 架构师之路3...

香沙小熊 ⋅ 前天 ⋅ 0

博客导航——一站式搜索(所有博客的汇总帖)

博客导航——一站式搜索 以后博客肯定会越来越多的,所以这做一个整理,方便各位朋友能快速的锁定自己想要的资源 课程 巧用第三方快速开发Android App 热门第三方SDK及框架 Android Studio G...

qq_26787115 ⋅ 2016/01/08 ⋅ 0

圣殿骑士博文索引

“圣殿骑士”技术博客,书写自己对技术的理解。天道酬勤、坚持不懈! 圣殿骑士很荣幸入住博客园和51CTO写技术博客,目前主要在一家外资企业从事项目管理、技术架构及企业技术培训工作。由于工...

晨曦之光 ⋅ 2012/03/09 ⋅ 0

六月/SilverSpi

SPI框架 本项目工程主要是实现了一个spi的使用框架,借此可以方便的利用spi的思想,实现一些业务场景的区分 设计思路 下图围绕 为中心,描述了三个主要的流程: load所有的spi实现 初始化选择...

六月 ⋅ 2017/07/30 ⋅ 0

F#系列随笔索引

循着我的Google笔记本,我看到第一条与F#相关的笔记发生在4月7日,到今天刚好6个月整。 为何要学习F#?这是个首当其冲的问题,跟当初“Java还是C#”这样的问题不同,现在是在学习一门新语言。...

长征3号 ⋅ 2017/12/21 ⋅ 0

《详解PHP面向对象》系列技术文章整理收藏

《详解PHP面向对象》系列技术文章整理收藏 1PHP面向对象之旅:类和对象 http://www.lai18.com/content/425094.html 2PHP面向对象之旅:类的属性 http://www.lai18.com/content/425093.html 3...

开元中国2015 ⋅ 2015/06/27 ⋅ 0

NDK开发笔记—ndk环境安装及其搭建

ndk环境安装及其搭建 软件下载 链接:pan.baidu.com/s/1cev9FK 密码:7yab 当然其他版本的也可以,建议不要用最新的 注意:执行Javah的时候生成头文件是对Java文件所生成的.class进行处理的命...

codeGoogle ⋅ 2017/09/26 ⋅ 0

JAVA之旅(十二)——Thread,run和start的特点,线程运行状态,获取线程对象和名称,多线程实例演示,使用Runnable接口

JAVA之旅(十二)——Thread,run和start的特点,线程运行状态,获取线程对象和名称,多线程实例演示,使用Runnable接口 开始挑战一些难度了,线程和I/O方面的操作了,继续坚持 一.Thread 如...

刘桂林 ⋅ 2016/06/03 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

大数据工程师需要精通算法吗,要达到一个什么程度呢?

机器学习是人工智能的一个重要分支,而机器学习下最重要的就是算法,本文讲述归纳了入门级的几个机器学习算法,加大数据学习群:716581014一起加入AI技术大本营。 1、监督学习算法 这个算法由...

董黎明 ⋅ 40分钟前 ⋅ 0

Kylin 对维度表的的要求

1.要具有数据一致性,主键值必须是唯一的;Kylin 会进行检查,如果有两行的主键值相同则会报错。 2.维度表越小越好,因为 Kylin 会将维度表加载到内存中供查询;过大的表不适合作为维度表,默...

无精疯 ⋅ 44分钟前 ⋅ 0

58到家数据库30条军规解读

军规适用场景:并发量大、数据量大的互联网业务 军规:介绍内容 解读:讲解原因,解读比军规更重要 一、基础规范 (1)必须使用InnoDB存储引擎 解读:支持事务、行级锁、并发性能更好、CPU及...

kim_o ⋅ 47分钟前 ⋅ 0

代码注释中顺序更改 文件读写换行

`package ssh; import com.xxx.common.log.LogFactory; import com.xxx.common.log.LoggerUtil; import org.apache.commons.lang3.StringUtils; import java.io.*; public class DirErgodic ......

林伟琨 ⋅ 55分钟前 ⋅ 0

linux实用操作命令

参考 http://blog.csdn.net/qwe6112071/article/details/50806734 ls [选项] [目录名 | 列出相关目录下的所有目录和文件 -a 列出包括.a开头的隐藏文件的所有文件-A 同-a,但不列出"."和"...

简心 ⋅ 今天 ⋅ 0

preg_match处理中文符号 url编码方法

之前想过直接用符号来替换,但失败了,或者用其他方式,但有有些复杂,这个是一个新的思路,亲测可用 <?php$str='637朗逸·超速新风王(300)(白光)'; $str=iconv("UTF-8","GBK",$s...

大灰狼wow ⋅ 今天 ⋅ 0

DevOps 资讯 | PostgreSQL 的时代到来了吗 ?

PostgreSQL是对象-关系型数据库,BSD 许可证。拼读为"post-gress-Q-L"。 作者: Tony Baer 原文: Has the time finally come for PostgreSQL?(有删节) 近30年来 PostgreSQL 无疑是您从未听...

RiboseYim ⋅ 今天 ⋅ 0

github太慢

1:用浏览器访问 IPAddress.com or http://tool.chinaz.com 使用 IP Lookup 工具获得github.com和github.global.ssl.fastly.net域名的ip地址 2:/etc/hosts文件中添加如下格式(IP最好自己查一...

whoisliang ⋅ 今天 ⋅ 0

非阻塞同步之 CAS

为解决线程安全问题,互斥同步相当于以时间换空间。多线程情况下,只有一个线程可以访问同步代码。这种同步也叫阻塞同步(Blocking Synchronization). 这种同步属于一种悲观并发策略。认为只...

长安一梦 ⋅ 今天 ⋅ 0

云计算的选择悖论如何对待?

人们都希望在工作和生活中有所选择。但心理学家的调查研究表明,在多种选项中进行选择并不一定会使人们更快乐,甚至不会产生更好的决策。心理学家Barry Schwartz称之为“选择悖论”。云计算为...

linux-tao ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部