Browse Month: 二月 2017

Go 之旅四: 方法与接口篇

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

1. 方法

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

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

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

1.1 非结构体类型声明方法

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

1.2 指针接收者

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

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

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

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

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

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

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

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

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

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

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

2. 接口

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

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

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

2.1 接口值

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

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

2.2 底层值为 nil 的接口值

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

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

2.3 空接口

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

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

2.4 类型断言

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

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

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

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

2.5 类型选择

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

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

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

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

3. Stringer

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

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

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

4. 错误

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

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

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

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

5. Reader

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

Reader 接口表示读取数据。Go 标准库包含了该接口的许多实现,包括文件、网络连接、压缩和加密等等,示例代码是字符串的实现。

Reader 接口有一个 Read 方法:

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

参考

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

Go 之旅三: 复杂类型

本文是学习 A Tour of Go (中文参考 Go 之旅中文 ) 整理的笔记,介绍Go 语言的指针,结构体,数组,切片,映射和闭包的基本概念和使用。

1. 指针

$GOPATH/src/go_note/gotour/advancetype/pointer/pointer.go 源码如下:

Go 具有指针。 指针保存了变量的内存地址。类型 *T 是指向 T 类型值的指针,其零值为 nil 。

& 操作符会生成一个指向其操作数的指针。

* 操作符表示指针指向的底层值。

2. 结构体

$GOPATH/src/go_note/gotour/advancetype/struct/struct.go 源码如下:

一个结构体( struct )就是一个字段的集合,结构体字段使用点号来访问。

2.1 结构体指针

结构体字段可以通过结构体指针来访问。

如果我们有一个指向结构体的指针 pt ,那么可以通过 (*pt).X 来访问其字段 X,也可以使用隐式间接引用,直接写 pt.X

2.2 结构体语法

结构体文法通过直接列出字段的值来新分配一个结构体。

使用 Name: 语法可以仅列出部分字段。(字段名的顺序无关。)

特殊的前缀 & 返回一个指向结构体的指针。

3. 数组

$GOPATH/src/go_note/gotour/advancetype/array/array.go 源码如下:

类型 [n]T 表示拥有 nT 类型的值的数组。

表达式

数组的长度是其类型的一部分,因此数组不能改变大小

4. 切片

$GOPATH/src/go_note/gotour/advancetype/slice/slice.go 源码如下:

每个数组的大小都是固定的,而切片则提供动态数组,类型 []T 表示一个元素类型为 T 的切片。切片类似不限定长度的数组。

4.1 切片是数组的引用

切片并不存储任何数据,它只是描述了底层数组中的一段。更改切片的元素会修改其底层数组中对应的元素,并且与它共享底层数组的切片都会观测到这些修改。

如下创建一个切片时,会先创建数组,然后构建一个引用了它的切片

4.2 切片的默认行为

在进行切片时,切片有默认上下界。切片下界的默认值为 0 ,上界则是该切片的长度。

对于数组

来说,以下切片是等价的:

4.3 切片的长度与容量

切片拥有 长度容量

  • 长度就是它所包含的元素个数。
  • 容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。

切片 s 的长度和容量可通过表达式 len(s)cap(s) 来获取。

切片的零值是 nil, nil 切片的长度和容量为 0 且没有底层数组。

4.4 make 创建切片

切片可以用内建函数 make 来创建,这也是你创建动态数组的方式。make 函数会分配一个元素为零值的数组并返回一个引用了它的切片:

要指定它的容量,需向 make 传入第三个参数:

4.5 切片的切片

切片可包含任何类型,甚至包括其它的切片

4.6 append 向切片追加元素

为切片追加新的元素是种常用的操作,为此 Go 提供了内建的 append 函数

append 的第一个参数 s 是一个元素类型为 T 的切片, 其余类型为 T 的值将会追加到该切片的末尾。append 的结果是一个包含原切片所有元素加上新添加元素的切片。当 s 的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。 返回的切片会指向这个新分配的数组。

5. range

$GOPATH/src/go_note/gotour/advancetype/range/range.go 源码如下:

for 循环的 range 形式可遍历切片或映射。

当使用 for 循环遍历切片时,每次迭代都会返回两个值。 第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。

可以将下标或值赋予 _ 来忽略它, 若你只需要索引,去掉 , value 的部分即可。

6. 映射

$GOPATH/src/go_note/gotour/advancetype/map/map.go 源码如下:

映射将键映射到值, 映射的零值为 nil, nil 映射既没有键,也不能添加键。make 函数会返回给定类型的映射,并将其初始化备用。

6.1 修改映射

在映射 m 中插入或修改元素:

获取元素:

删除元素:

通过双赋值检测某个键是否存在:

keym 中, oktrue ;否则, okfalse

key 不在映射中,那么 elem 是该映射元素类型的零值。同样的,当从映射中读取某个不存在的键时,结果是映射的元素类型的零值。

7. 闭包

$GOPATH/src/go_note/gotour/advancetype/closure/closure.go 源码如下:

函数也是值,它们可以像其它值一样传递,函数值可以用作函数的参数或返回值。

Go 函数可以是一个闭包。闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋值其引用的变量的值,也即该函数被“绑定”在了这些变量上。

参考

Go 之旅二: 流程控制语句

学习 A Tour of Go (中文参考 Go 之旅中文 ) 整理的笔记。介绍流程控制语句 for, if, else, switchdefer 的基本概念和使用。

1. for

$GOPATH/src/go_note/gotour/flowcontrol/for.go 源码如下:

Go 只有一种循环结构:for 循环。基本的 for 循环由三部分组成,用分号隔开:

  • 初始化语句:在第一次迭代前执行
  • 条件表达式:在每次迭代前求值
  • 后置语句:在每次迭代的结尾执行

初始化语句通常为一句短变量声明,该变量声明仅在 for 语句的作用域中可见。Go 的 for 语句后面没有小括号,大括号 { } 则是必须的。

1.1 for 是 Go 中的 while

初始化语句和后置语句是可选的,此时你可以去掉分号 ;, for 是 Go 中的 while

1.2 无限循环

如果省略循环条件,该循环就不会结束,循环成为一个无线循环。

2. if else

$GOPATH/src/go_note/gotour/flowcontrol/if/if.go 源码如下:

Go 的 if 语句与 for 循环类似,表达式外无需小括号 ( ) ,而大括号 { } 则是必须的。

2.1 if 的简短语句

for 一样, if 语句可以在条件表达式前执行一个简单的语句,该语句声明的变量作用域仅在 if 和对应的 else 块中使用。

3. switch

$GOPATH/src/go_note/gotour/flowcontrol/switch/switch.go 源码如下:

Go 语言switch 除非以 fallthrough 语句结束,否则分支会自动终止。switchcase 语句从上到下顺次执行,直到匹配成功时停止。

没有条件的 switchswitch true 一样,这种形式能将一长串 if-then-else 写得更加清晰。

4. defer

$GOPATH/src/go_note/gotour/flowcontrol/defer/defer.go 源码如下:

defer 语句会将函数推迟到外层函数返回之后执行。推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。推迟的函数调用会被压入一个栈中。 当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。

参考

@刚刚小码农


©2019 | 鄂ICP备18002823号-2 |鄂公网安备 42018502000885号