##复杂类型: struct、slice 和 map 学习如何基于已有类型定义新的类型:本课涵盖了结构体、数组、slice 和 map。
指针
- Go 具有指针。 指针保存了变量的内存地址。
- 类型 *T 是指向类型 T 的值的指针。其零值是 nil 。
var p *int
&
符号会生成一个指向其作用对象的指针。
i := 42
p = &i
*
符号表示指针指向的底层的值。
fmt.Println(*p) //通过指针 p 读取 i
*p = 32 //通过指针 p 设置 i
- 完整代码例如:
package main
import "fmt"
func main() {
i , j := 56, 128
p := &i // point to i
fmt.Println(*p) // read i through the pointer
*p =21 // set i through the pointer
fmt.Println(i) // see the new value of i
p = &j // point to j
*p = *p/24 // divide j through the pointer
fmt.Println(j) // see the new value of j
}
- 这也就是通常所说的“间接引用”或“非直接引用”。
与 C
不同,Go
没有指针运算。
结构体
- 一个结构体(
struct
)就是一个字段的集合。
package main
import "fmt"
type Coordinate struct {
x int
y int
}
func main() {
fmt.Println(Coordinate{1,2})
}
- 而
type
的含义跟其字面意思相符。
结构体字段
- 结构体字段使用点号来访问。
package main
import "fmt"
type CoordinateFiled struct {
X int
Y int
}
func main() {
v := CoordinateFiled{1,6}
v.X =4
fmt.Println(v.X)
}
结构体指针
- 结构体字段可以通过结构体指针来访问。
package main
import (
"fmt"
"../hello"
)
func main() {
v := hello.Coordinate{2, 6}
p := &v
p.X = 1e9
fmt.Println(v)
}
通过指针间接的访问是透明的。
结构体文法
- 结构体文法表示通过结构体字段的值作为列表来新分配一个结构体。
- 使用 Name: 语法可以仅列出部分字段。(字段名的顺序无关。)
- 特殊的前缀 & 返回一个指向结构体的指针。
package main
import (
"fmt"
"../hello"
)
var (
v1 = hello.Vertex{1,2}
v2 = hello.Vertex{X:1}
v3 = hello.Vertex{}
p = &hello.Vertex{1,2}
)
func main() {
fmt.Println(v1,p,v2,v3)
}
数组
- 类型
[n]T
是一个有n
个类型为T
的值的数组。 - 表达式
var a [10]int
定义变量 a 是一个有十个整数的数组。
- 数组的长度是其类型的一部分,因此数组不能改变大小。 这看起来是一个制约,但是请不要担心; Go 提供了更加便利的方式来使用数组。
package main
import "fmt"
func main() {
var a[2] string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0],a[1])
fmt.Println(a)
}
slice
- 一个 slice 会指向一个序列的值,并且包含了长度信息。
- []T 是一个元素类型为 T 的 slice。
- len(s) 返回 slice s 的长度。
package main
import "fmt"
func main() {
s := [] int{2,3,5,7,9,11}
fmt.Println("s ==",s)
for i := 0;i<len(s);i++{
fmt.Printf("s[%d] == %d\n",i,s[i])
}
}
slice 的 slice
- slice 可以包含任意的类型,包括另一个 slice。
package main
import (
"fmt"
"strings"
)
func main() {
game := [][]string{
[]string {"-","-","-"},
[]string {"-","-","-"},
[]string {"-","-","-"},
}
// The players take turns.
game[0][0] = "X"
game[2][2] = "O"
game[2][0] = "X"
game[1][0] = "O"
game[0][2] = "X"
printBoard(game)
}
func printBoard(s [][]string) {
for i := 0;i < len(s);i++{
fmt.Printf("%s\n",strings.Join(s[i]," "))
}
}
对 slice 切片
- slice 可以重新切片,创建一个新的 slice 值指向相同的数组。(跟python的切片类似) 表达式
s[lo:hi]
表示从 lo 到 hi-1 的 slice 元素,含前端,不包含后端。因此
s[lo:lo]
是空的
s[lo:lo+1]
有一个元素
package main
import "fmt"
func main() {
s := []int{3,5,7,9,11,13,15,17}
fmt.Println("s ==",s)
fmt.Println("s[1:4] ==",s[1:4])
//省略下标代表从0开始
fmt.Println("s[:4] ==",s[:4])
//省略上标到len(s)结束
fmt.Println("s[4:] ==",s[4:])
}
构造 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
完整代码例如:
package main
import "fmt"
func main() {
a := make([]int,5)
printSlice("a",a)
b := make([]int,0,5)
printSlice("b",b)
c := b[:3]
printSlice("c",c)
d := b[3:5]
printSlice("d",d)
}
func printSlice(s string, x [] int) {
fmt.Printf("%s len=%d cap=%d %v\n",s,len(x),cap(x),x)
}
nil slice
slice
的零值是nil
。- 一个
nil
的slice
的长度和容量是 0。
例如:
package main
import "fmt"
func main() {
var s []int
fmt.Println(s,len(s),cap(s))
if s == nil {
fmt.Println("nil!")
}
}
向 slice 添加元素
- 向
slice
的末尾添加元素是一种常见的操作,因此Go
提供了一个内建函数append
。 内建函数的文档对 append 有详细介绍。
func append(s []T, vs ...T) []T
append
的第一个参数s
是一个元素类型为T
的slice
,其余类型为T
的值将会附加到该slice
的末尾。append
的结果是一个包含原slice
所有元素加上新添加的元素的slice
。- 如果
s
的底层数组太小,而不能容纳所有值时,会分配一个更大的数组。 返回的slice
会指向这个新分配的数组。 (了解更多关于 slice 的内容,参阅文章Go 切片:用法和本质。)
例如:
package main
import (
"../hello"
)
func main() {
var a []int
hello.PrintSlice("a",a)
// append works on nil slices.
a = append(a,0)
hello.PrintSlice("a",a)
// the slice grows as needed.
a = append(a,1)
hello.PrintSlice("a",a)
// we can add more than one element at a time.
a = append(a,2,3,4,5,6,7,8,)
hello.PrintSlice("a",a)
}
range
for
循环的range
格式可以对slice
或者map
进行迭代循环。- 当使用 for 循环遍历一个 slice 时,每次迭代 range 将返回两个值。 第一个是当前下标(序号),第二个是该下标所对应元素的一个拷贝。
例如:
package main
import "fmt"
var pow1 = []int{1,2,4,8,16,32,64,128}
func main() {
for i,v := range pow1{
fmt.Printf("2**%d = %d\n", i, v)
}
}
range(续)
- 可以通过赋值给 _ 来忽略序号和值。
- 如果只需要索引值,去掉 “ , value ” 的部分即可。
例如:
package main
import "fmt"
func main() {
pow := make([]int,10)
for i := range pow{
pow[i] = 1<<uint(i)
}
for _,value := range pow{
fmt.Printf("%d\n", value)
}
}
练习:slice
- 实现
Pic
。它返回一个长度为dy
的slice
,其中每个元素是一个长度为dx
且元素类型为8位无符号整数的slice
。当你运行这个程序时, 它会将每个整数作为对应像素的灰度值(好吧,其实是蓝度)并显示这个slice
所对应的图像。 - 计算每个像素的灰度值的方法由你决定;几个有意思的选择包括 (x+y)/2、x*y 和 x^y 。
例如;
package main
import (
"golang.org/x/tour/pic"
)
func Pic(dx, dy int) [][]uint8 {
var array = [][]uint8{}
for i := 0;i < dy;i++{
array = append(array, make([]uint8,dx))
}
return array
}
func main() {
pic.Show(Pic)
}
提示 1. (需要使用循环来分配 [][]uint8 中的每个 []uint8 。) 2. (使用 uint8(intValue) 来在类型之间进行转换。)
map
- map 映射键到值。
- map 在使用之前必须用 make 来创建;值为 nil 的 map 是空的,并且不能对其赋值。
例如;
package main
import (
"fmt"
"hello-go/hello"
)
var m map[string] hello.VertexLL
func main() {
m = make(map[string] hello.VertexLL)
m["Bell Labs"] = hello.VertexLL{40.68433, -74.39967,}
fmt.Println(m["Bell Labs"])
}
map 的文法
- map 的文法跟结构体文法相似,不过必须有键名。
例如:
package main
import (
"fmt"
"hello-go/hello"
)
var m1 = map[string]hello.VertexLL{
"Bell Labs":{40.68433, -74.39967},
"Google":hello.VertexLL{37.42202, -122.08408,},
}
func main() {
fmt.Println(m1)
}
map 的文法(续)
- 若顶级类型只是一个类型名,你可以在文法的元素中省略它。
例如:
package main
import (
"fmt"
"hello-go/hello"
)
var m2 = map[string]hello.VertexLL{
"Bell Labs":{40.68433, -74.39967},
"Google":{37.42202, -122.08408,},
}
func main() {
fmt.Println(m2)
}
修改 map
- 在 map m 中插入或修改一个元素:
m[key] = elem
- 获得元素:
elem = m[key]
- 删除元素:
delete(m, key)
- 通过双赋值检测某个键存在:
elem, ok = m[key]
1. 如果 `key` 在 `m` 中, ok 为 true。否则, ok 为 false,并且 elem 是 map 的元素类型的零值。
2. 同样的,当从 map 中读取某个不存在的键时,结果是 map 的元素类型的零值。
例如:
package main
import "fmt"
func main() {
m := make(map[string]int)
m["Answer"] = 34
fmt.Println("The value:",m["Answer"])
m["Answer"] = 48
fmt.Println("The value:",m["Answer"])
delete(m,"Answer")
fmt.Println("The value:",m["Answer"])
v,ok := m["Answer"]
fmt.Println("The value:", v, "Present?", ok)
}
练习:map
- 实现 WordCount。它应当返回一个含有 s 中每个 “词” 个数的 map。函数 wc.Test 针对这个函数执行一个测试用例,并输出成功还是失败。
提示:你会发现 strings.Fields 很有帮助。
代码:
package main
import (
"golang.org/x/tour/wc"
"strings"
)
func WordCount(s string) map[string]int {
strArray := strings.Fields(s)
count := make(map[string]int)
for i := 0;i < len(strArray); i++{
str := strArray[i]
_,ok := count[str]
if !ok{
count[str] = 1
} else {
count[str] += 1
}
}
return count
}
func main() {
wc.Test(WordCount)
}
函数值
- 函数也是值。他们可以像其他值一样传递,比如,函数值可以作为函数的参数或者返回值。
例如:
package main
import (
"fmt"
"math"
)
func compute(fn func(float64,float64)float64)float64 {
return fn(3,4)
}
func main() {
hypot := func(x,y float64)float64{
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(6,8))
fmt.Println(compute(hypot))
fmt.Println(compute(math.Pow))
}
结果:
10
5
81
函数的闭包
- Go 函数可以是一个闭包。闭包是一个函数值,它引用了函数体之外的变量。 这个函数可以对这个引用的变量进行访问和赋值;换句话说这个函数被“绑定”在这个变量上。
- 例如,函数 adder 返回一个闭包。每个返回的闭包都被绑定到其各自的 sum 变量上。
例如:
package main
import "fmt"
func adder()func(int)int {
sum := 0
return func(x int)int {
sum += x
return sum
}
}
func main() {
pos,neg := adder(),adder()
for i:=0;i < 10;i++{
fmt.Println(
pos(i),
neg(-2 * i),
)
}
}
输出结果:
0 0
1 -2
3 -6
6 -12
10 -20
15 -30
21 -42
28 -56
36 -72
45 -90
简单分析:
1. adder()
返回的是函数
2. 循环中多次调用pos
,可见pos
的sum相对pos
是全局的
练习:斐波纳契闭包
- 现在来通过函数做些有趣的事情。
- 实现一个 fibonacci 函数,返回一个函数(一个闭包)可以返回连续的斐波纳契数。
F(0)=0,F(1)=1, F(n)=F(n-1)+F(n-2)(n>=2,n∈N*)
1、1、2、3、5、8、13、21、34
代码如下:
package main
import "fmt"
// fibonacci 函数会返回一个返回 int 的函数。
func fibonacci() func() int {
i ,j := 0,1
return func() int {
tmp := i
i , j = j ,i + j
return tmp
}
}
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
本文由 zealzhangz 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为:
2018/02/14 21:53