## 指针——值的内存地址
Go 拥有指针。指针保存了值的内存地址。
*类型 T 是指向 T 类型值的指针。其零值为 nil。
var p *int //p是一个可以指向int类型值的指针
& 操作符会生成一个指向其操作数的指针。
k := 42
p = &k //p这时候就是一个指向k的指针
*操作符表示指针指向的底层值。
fmt.Println(*p) // 通过指针 p 读取 k
*p = 21 // 通过指针 p 设置 k
package main
import "fmt"
func main() {
i, j := 42, 2701
p := &i // 声明指针p指向 i
fmt.Println(*p) // 通过指针读取 i 的值,其实就是根据p里面的地址访问i的值
*p = 21 // 通过指针设置 i 的值
fmt.Println(i) // 查看 i 的值,此时已经是21了
p = &j // 指向 j
*p = *p / 37 // 通过指针对 j 进行除法运算
fmt.Println(j) // 查看 j 的值
}
结构体
一个结构体其实就是一组字段
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
fmt.Println(Vertex{1, 2})
}
// 输出——{1,2}
结构体字段
要访问或者修改结构体字段,通过.来访问
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
fmt.Println(v.X)//1
v.X = 4
fmt.Println(v.X)//4
}
结构体指针
结构体字段可以通过结构体指针来访问。
*如果我们有一个指向结构体的指针 p,那么可以通过 (p).X 来显式访问其字段 X。不过这么写太啰嗦了,所以语言也允许我们使用隐式间接引用,直接写 p.X 就可以。
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
p := &v
p.X = 1e9
fmt.Println(v)//{1000000000,2}
(*p).X = 1e7
fmt.Println(v)//{10000000,2}
}
结构体文法
结构体文法通过直接列出字段的值来新分配一个结构体。
使用 Name: 语法可以仅列出部分字段。(字段名的顺序无关。)
特殊的前缀 & 返回一个指向结构体的指针。
package main
import "fmt"
type Vertex struct {
X, Y int
}
var (
v1 = Vertex{1, 2} // 创建一个 Vertex 类型的结构体
v2 = Vertex{X: 1} // Y:0 被隐式地赋予
v4 = Vertex{Y: 2} // X:0 隐式赋予,无关顺序在这里体现出来
v3 = Vertex{} // X:0 Y:0
p = &Vertex{1, 2} // 创建一个 *Vertex 类型的结构体(指针)
)
func main() {
fmt.Println(v1, p, v2, v3)
}
// {1,2} &{1,2} {1,0} {0,0}
值得注意的是上面代码中的p指向的结构体也是{1,2},但是地址并不是v1的内存地址,所以通过p对结构体进行修改不会影响v1
数组
表达式
var a [10]int
会将变量 a 声明为拥有 10 个整数的数组。
数组的长度是其类型的一部分,因此数组不能改变大小。这看起来是个限制,不过没关系,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)
primes := [6]int{2, 3, 5, 7, 11, 13}// 结构体的参数个数不能超出数组长度,如果少的会在数组末尾补0
fmt.Println(primes)// [2,3,5,7,11,13]
}
切片
每个数组的大小都是固定的。而切片则为数组元素提供动态大小的、灵活的视角。在实践中,切片比数组更常用。切片因为比数组更灵活,所以在go里面广泛应用
切片不等同于数组!只是很类似,而且基于数组可以创建切片
类型 []T 表示一个元素类型为 T 的切片。
一般只要看创建的时候有没有指定长度就可以区分数组和切片。我
切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:
a[low : high]
它会选择一个半开区间,包括第一个元素,但排除最后一个元素。
以下表达式创建了一个切片,它包含 a 中下标从 1 到 3 的元素:
a[1:4]
package main
import "fmt"
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
fmt.Println(s)// [3 5 7]
}
注意!!!
切片就像是对数组的引用,假设b是a的子切片,如果改变了b中的元素,那么a中的元素也会发生改变,并且其他引用了a切片的地方也会观测到这些修改
package main
import "fmt"
func main() {
names := [4]string{
"John",
"Paul",
"George",
"Ringo",
}
fmt.Println(names)
a := names[0:2]
b := names[1:3]
fmt.Println(a, b)
b[0] = "XXX"// 此时b[0]对应的'Paul'变成了'XXX',导致a数组和names也同时发生改变
fmt.Println(a, b)
fmt.Println(names)
}
[John Paul George Ringo]
[John Paul] [Paul George]
[John XXX] [XXX George]
[John XXX George Ringo]
切片文法
切片文法类似于没有长度的数组文法。
这是一个数组文法:
[3]bool{true, true, false}
下面这样则会创建一个和上面相同的数组,然后构建一个引用了它的切片:
[]bool{true, true, false}
package main
import "fmt"
func main() {
q := []int{2, 3, 5, 7, 11, 13}
fmt.Println(q)
r := []bool{true, false, true, true, false, true}
fmt.Println(r)
s := []struct {
i int
b bool
}{
{2, true},
{3, false},
{5, true},
{7, true},
{11, false},
{13, true},
}
fmt.Println(s)
}
切片的默认行为
我们在对数组进行切片的时候,并不会改变底层数组(源数组的长度和容量)
长度:当前数组的元素个数
容量:当前数组的第一个元素到其底层数组的最后一个元素的距离
切片 s 的长度和容量可通过表达式 len(s) 和 cap(s) 来获取。
package main
import "fmt"
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
// 截取切片使其长度为 0
s = s[:0]
printSlice(s)//
// 拓展其长度
s = s[:4]
printSlice(s)
// 舍弃前两个值
s = s[2:]
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
len=6,cap=6,[2,3,5,7,11,13]
len=0,cap=6,[]
len=4,cap=6,[2,3,5,7]
len=2,cap=4,[5,7]
切片的零值是nil,nil 切片的长度和容量都为 0 且没有底层数组。
package main
import "fmt"
func main() {
var s []int
fmt.Println(s, len(s), cap(s))
if s == nil {
fmt.Println("nil!")
}
}
/*
0 0 []
nil!
*/
用make创建切片
切片可以用内建函数 make 来创建,这也是你创建动态数组的方式。
make 函数会分配一个元素为零值的数组并返回一个引用了它的切片:
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
向切片中添加元素
为切片追加新的元素是种常用的操作,为此 Go 提供了内建的 append 函数。内建函数的文档对此函数有详细的介绍。
func append(s []T, vs …T) []T
append 的第一个参数 s 是一个元素类型为 T 的切片,其余类型为 T 的值将会追加到该切片的末尾。
append 的结果是一个包含原切片所有元素加上新添加元素的切片。
当 s 的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。
package main
import "fmt"
func main() {
var s []int
printSlice(s)
// 添加一个空切片
s = append(s, 0)
printSlice(s)
// 这个切片会按需增长
s = append(s, 1)
printSlice(s)
// 可以一次性添加多个元素
s = append(s, 2, 3, 4)
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
使用append()添加元素时,如果此时切片长度超出容量的时候,会自动对切片扩容。
切片扩容机制:当原切片长度小于1024时,新切片的容量会直接翻倍。而当原切片的容量大于等于1024时,会反复地增加25%,直到新容量超过所需要的容量
注意点:像上述代码一次性添加多个元素的时候,切片容量本来是2,但是一次性添加3个元素后,翻倍也不够用,就会再加2,所以第三次添加的时候,cap就变成了2
Range
for 循环的 range 形式可遍历切片或映射。
当使用 for 循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。
package main
import "fmt"
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for i, v := range pow {
fmt.Printf("[%d %d]\n", i, v)
}
}
上述循环每次输出的结果是[index,num]形式的
但有时候我们并不需要两个值,当我们想省略其中一个的时候,使用_(下划线)即可
package main
import "fmt"
func main() {
//这是我们使用range去求一个slice的和。使用数组跟这个很类似
nums := []int{2, 3, 4}
sum := 0
for _, num := range nums {
sum += num
}
fmt.Println("sum:", sum)
//在数组上使用range将传入index和值两个变量。上面那个例子我们不需要使用该元素的序号,所以我们使用空白符"_"省略了。有时侯我们确实需要知道它的索引。
for i, num := range nums {
if num == 3 {
fmt.Println("index:", i)
}
}
//range也可以用在map的键值对上。
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
fmt.Printf("%s -> %s\n", k, v)
}
//range也可以用来枚举Unicode字符串。第一个参数是字符的索引,第二个是字符(Unicode的值)本身。
for i, c := range "go" {
fmt.Println(i, c)
}
}
sum: 9
index: 1
a -> apple
b -> banana
0 103
1 111
如果只是需要索引,我们甚至可以不使用空白符’_’
for i := range pow{
fmt.Println("index=%d\n",i)// %d是占位符
}
映射——Map
映射将键映射到值。
(其实就是Java里的Map和python的字典,存储的元素是key-value对)
映射的零值为 nil 。nil 映射既没有键,也不能添加键。
make 函数会返回给定类型的映射,并将其初始化备用。所以make()不仅可以创建切片,还可以用来创建Map
package main
import "fmt"
// 初始化一个结构体,里面是两个浮点数类型元素
type Vertex struct {
Lat, Long float64
}
var m map[string]Vertex
func main() {
// string是key的类型,Vertex是value的类型
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])// {40.68433 -74.39967}
}
映射的语法/文法
映射的文法与结构体相似,不过必须有键名。
package main
import "fmt"
type Vertex struct {
Lat, Long float64
}
var m = map[string]Vertex{
"Bell Labs": Vertex{40.68433, -74.39967},
"Google": Vertex{37.42202, -122.08408},
}
// Vertex可以省略
func main() {
fmt.Println(m)
}
映射的修改操作
1.在映射m中插入或修改某个元素
m[key] = elem
2.获取某个元素
elem = m[key]
3.删除元素
delete(m,key)
4.通过双赋值验证m中是否存在某个元素
elem, ok = m[key]
如果key在m里面,ok为true,否则是false
如果key不在m中,elem会被赋予value数据类型的零值
所以我们在读取某个不存在映射中的数据时,得到的是value数据类型的零值
如果elem或ok没有声明,可以用短变量声明
elem, ok := m[key]
package main
import "fmt"
func main() {
m := make(map[string]int)
m["Answer"] = 42
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)
}
The value: 42
The value: 48
The value: 0
The value: 0 Present? false
映射练习
实现 WordCount。它应当返回一个映射,其中包含字符串 s 中每个“单词”的个数。函数 wc.Test 会对此函数执行一系列测试用例,并输出成功还是失败。
**你会发现 strings.Fields 很有帮助。 **
package main
import (
"golang.org/x/tour/wc"
"strings"
)
func WordCount(s string) map[string]int {
//1.先初始化一个映射
var cnt = make(map[string]int)
//2.声明elem用来保存value值,也就是每个单词出现的次数
var elem = 0
//3.strings.Fields(s)会返回一个将s字符串按空格分割的字符串列表,我们使用range进行一个遍历
for i := range strings.Fields(s){
//4.首先看这个单词之前出现了几次
elem = cnt[strings.Fields(s)[i]]
//5.然后再给这个单词出现的次数加1
cnt[strings.Fields(s)[i]]=1+elem
}
return cnt
}
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(5, 12))
fmt.Println(compute(hypot))
fmt.Println(compute(math.Pow))
}
上述代码的hypot函数其实就是作为一个函数值在compute函数里面传递
输出1:hypot(5,12)返回13
输出2:compute(hypot)其实就是将3和4传入hypot中再返回结果5
Go 语言支持匿名函数,可作为闭包。匿名函数是一个"内联"语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。
以下实例中,我们创建了函数 getSequence() ,返回另外一个函数。该函数的目的是在闭包中递增 i 变量,代码如下:
package main
import "fmt"
func getSequence() func() int {
i:=0
return func() int {
i+=1
return i
}
}
func main(){
/* nextNumber 为一个函数,函数 i 为 0 */
nextNumber := getSequence()
/* 调用 nextNumber 函数,i 变量自增 1 并返回 */
fmt.Println(nextNumber())
fmt.Println(nextNumber())
fmt.Println(nextNumber())
/* 创建新的函数 nextNumber1,并查看结果 */
nextNumber1 := getSequence()
fmt.Println(nextNumber1())
fmt.Println(nextNumber1())
}
以上代码执行结果为
1
2
3
1
2
练习——实现斐波那契数列
package main
import "fmt"
// 返回一个“返回int的函数”,其实底部调用的是闭包函数,每次都返回tmp
func fibonacci() func() int {
num1,num2 := 0,1
// 我们使用函数闭包,无需声明也可以直接使用
return func() int{
var tmp = num1
num1,num2 = num2,num1+num2
return tmp
}
}
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}