基础
一开始,将学习关于语言的所有基础内容。学习如何基于已有类型定义新的类型:盖了结构体、数组、slice 和 map。
指针
Go 具有指针。 指针保存了变量的内存地址。
类型 *T 是指向类型 T 的值的指针。其零值是 nil 。
var p *int
& 符号会生成一个指向其作用对象的指针。
i := 42 p = &i
* 符号表示指针指向的底层的值。
fmt.Println(*p) // 通过指针 p 读取 i *p = 21 // 通过指针 p 设置 i
这也就是通常所说的“间接引用”或“非直接引用”。
与 C 不同,Go 没有指针运算。
结构体
一个结构体( struct )就是一个字段的集合。
(而 type 的含义跟其字面意思相符。)
结构体字段
结构体字段使用点号来访问。
结构体指针
结构体字段可以通过结构体指针来访问。
通过指针间接的访问是透明的。
结构体文法
结构体文法表示通过结构体字段的值作为列表来新分配一个结构体。
使用 Name: 语法可以仅列出部分字段。(字段名的顺序无关。)
特殊的前缀 & 返回一个指向结构体的指针。
数组
类型 [n]T 是一个有 n 个类型为 T 的值的数组。
表达式
var a [10]int
定义变量 a 是一个有十个整数的数组。
数组的长度是其类型的一部分,因此数组不能改变大小。 这看起来是一个制约,但是请不要担心; Go 提供了更加便利的方式来使用数组。
Slice
数组具有固定大小。另一方面,slice是对数组元素的特殊处理,动态大小,数组元素视图选取灵活。在实践中,切片比数组更常见。
类型[]T是类型的元素的slice T。本质上就是不指定大小。
这个表达式创建数组的第一个五行片a:
a [0:5]
Slice类似于数组的引用
slice不存储任何数据,它只是描述了底层数组的一部分。
更改切片的元素会修改其底层数组的相应元素。共享相同底层数组的其他slice将看到这些更改。
Slice文本
slice文本相当于数组文本去掉了指定长度。
下面是一个数组文本:
[3]bool{true, true, false}
下面是一个slice文本,这个slice文本先内部创建了一个如上的数组,然后创建了一个slice结构指向了内部的数组:
[]bool{true, true, false}
Slice 边界默认值
当选择slice的范围时,你可以忽略声明低边界或者高边界。低边界的默认值是0,高边界的默认值是数组的长度。
对下面的数组来说:
var a [10]int
以下的slice表达式是相等的:
a[0:10] a[:10] a[0:] a[:]
Slice的长度和容量
slice既有长度又有容量。
slice的长度是它包含的元素数。slice的容量是底层数组中元素的数量,从slice中的第一个元素计数。
slice的长度和容量可以使用表达式得到len(s)和cap(s)。
只要有足够的容量,就可以通过重新切片来延长slice的长度。如果slice调整后的长度,超出底层的数组的长度,则会报错:
panic: runtime error: slice bounds out of range
Nil Slice
slice 的零值是 nil 。
一个 nil 的 slice 的长度和容量是 0,并且没有底层相关联的array。
构造 slice
slice 由函数 make 创建,通过这个内置函数可以创建动态长度的数组。
这会分配一个全是零值的数组并且返回一个 slice 指向这个数组:
a := make([]int, 5) // len(a)=5
为了指定容量,可传递第三个参数到 make:
b := make([]int, 0, 5) // len(b)=0, cap(b)=5 b = b[:cap(b)] // len(b)=5, cap(b)=5 b = b[1:] // len(b)=4, cap(b)=4
Slices of slices
Slice能够包含任何类型,包括其他的slice.
向 slice 添加元素
向 slice 的末尾添加元素是一种常见的操作,因此 Go 提供了一个内建函数 append 。 内建函数的文档对 append 有详细介绍。
func append(s []T, vs ...T) []T
append 的第一个参数 s 是一个元素类型为 T 的 slice ,其余类型为 T 的值将会附加到该 slice 的末尾。
append 的结果是一个包含原 slice 所有元素加上新添加的元素的 slice。
如果 s 的底层数组太小,而不能容纳所有值时,会分配一个更大的数组。 返回的 slice 会指向这个新分配的数组。
(了解更多关于 slice 的内容,参阅文章https://blog.go-zh.org/go-slices-usage-and-internals)
Range
for 循环的 range 格式可以对 slice 或者 map 进行迭代循环。
当使用 for 循环遍历一个 slice 时,每次迭代 range 将返回两个值。 第一个是当前下标(序号),第二个是该下标所对应元素的一个拷贝。
可以通过赋值给 _ 来忽略索引或值。
如果只需要索引值,去掉 “ , value ” 的部分即可。
做一个练习,这个练习的代码我结合了指针一起。实现 Pic 。它返回一个长度为 dy 的 slice,其中每个元素是一个长度为 dx 且元素类型为8位无符号整数的 slice。当你运行这个程序时, 它会将每个整数作为对应像素的灰度值(好吧,其实是蓝度)并显示这个 slice 所对应的图像。
计算每个像素的灰度值的方法由你决定;几个有意思的选择包括 (x+y)/2、x*y 和 x^y 。
(需要使用循环来分配 [][]uint8 中的每个 []uint8 。)
(使用 uint8(intValue) 来在类型之间进行转换。)
Map
Map 映射键到值。Map的零值是nil. 一个nil的map没有键,也不能添加键。 map 在使用之前须用 make 来创建,会自动完成初始化;
Map 的文本
map 的文本跟结构体文本相似,不过必须有键名。
若顶级类型只是一个类型名,你可以在文法的元素中省略它。
修改 Map
在 map m 中插入或修改一个元素:
m[key] = elem
获得元素:
elem = m[key]
删除元素:
delete(m, key)
通过双赋值检测某个键存在:
elem, ok = m[key]
如果 key 在 m 中, ok 为 true。否则, ok 为 false,并且 elem 是 map 的元素类型的零值。
同样的,当从 map 中读取某个不存在的键时,结果是 map 的元素类型的零值。
如果elem和ok之前没有被声明,可以直接使用自动类型推导
elem, ok := m[key]
做一个练习,实现 WordCount。它应当返回一个含有 s 中每个 “词” 个数的 map。函数 wc.Test 针对这个函数执行一个测试用例,并输出成功还是失败。
函数值
函数也是值。他们可以像其他值一样传递,同样,函数值可以作为函数的参数或者返回值。
函数的闭包
Go 函数可以是一个闭包。闭包是一个函数值,它引用了函数体之外的变量。 这个函数可以对这个引用的变量进行访问和赋值;换句话说这个函数被“绑定”在这个变量上。
例如,函数 adder 返回一个闭包。每个返回的闭包都被绑定到其各自的 sum 变量上。
现在来通过函数做些有趣的事情。
实现一个 fibonacci 函数,返回一个函数(一个闭包)可以返回连续的斐波纳契数。
◆ ◆ ◆ ◆ ◆
来源:
作者介绍:张洋铭,投资人中最懂动漫的程序员,负责PlugandPlay早期科技类项目投资,个人关注动漫智能助理。
微信公众号:张洋铭Ocean(ocean_anidata)
BP请投递至:ocean.zhang@plugandplaychina.com