这是我参与「第五届青训营 」伴学笔记创作活动的第4天
GO语言圣经笔记
1. 入门
1.3更多类型
指针
Go 拥有指针。指针保存了值的内存地址。
类型*T是指向T类型值的指针。其零值为nil。var p *int
& 操作符会生成一个指向其操作数的指针。
* 操作符表示指针指向的底层值。
与 C 不同,Go 没有指针运算。
package main
import "fmt"
func main() {
i, j := 42, 2701
p := &i // 指向 i
fmt.Println(*p) // 通过指针读取 i 的值
fmt.Println(p) // 读取指针变量p存储的 i 的地址
fmt.Println(&i) // i 的地址
fmt.Println(&p) // 指针变量p的存储地址
*p = 21 // 通过指针设置 i 的值
fmt.Println(i) // 查看 i 的值
p = &j // 指向 j
*p = *p / 37 // 通过指针对 j 进行除法运算
fmt.Println(j) // 查看 j 的值
}
* 操作符表示指针指向的底层值。
结构体
一个结构体(struct)就是一组字段(field),结构体字段使用点号来访问。
结构体字段可以通过结构体指针来访问。如果我们有一个指向结构体的指针p,那么可以通过(*p).X来访问其字段 X。不过这么写太啰嗦了,所以语言也允许我们使用隐式间接引用,直接写 p.X 就可以。
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2} //定义v为结构体变量
fmt.Println(v)
v.X = 4
fmt.Println(v.X)
p := &v
p.X = 1e9
fmt.Println(v)
}
C语言的结构体通常使用的是如下结构
struct 结构体名 {
成员表列
}变量名表列;
在C语言中,为了避免结构体指针(*p).X的麻烦,使用p->X方法替代。
其中fmt.Println(v)的输出为{1 2},对于为什么会出现花括号这种输出格式,目前猜测为结构体的默认返回值是花括号加上参数值,如同后文输出数组和切片时为中括号加参数值,是一种固定的输出方法。
结构体语法
结构体文法通过直接列出字段的值来新分配一个结构体。
使用 Name: 语法可以仅列出部分字段。(字段名的顺序无关。)
特殊的前缀 & 返回一个指向结构体的指针。
package main
import "fmt"
type Vertex struct {
X, Y int
}
var (
v1 = Vertex{1, 2} // 创建一个 Vertex 类型的结构体
v2 = Vertex{X: 1} // Y:0 被隐式地赋予
v3 = Vertex{} // X:0 Y:0
p = &Vertex{1, 2} // 创建一个 *Vertex 类型的结构体(指针)
)
func main() {
fmt.Println(v1, p, v2, v3)
}
数组
类似于C。
类型[n]T表示拥有n个T类型的值的数组。
表达式var a [10]int,会将变量a声明为拥有10个整数的数组。下标从0开始
数组的长度是其类型的一部分,因此数组不能改变大小。
切片(slice)
每个数组的大小都是固定的。而切片则为数组元素提供动态大小的、灵活的视角。在实践中,切片比数组更常用。
数组和slice之间有着紧密的联系,slice的语法和数组很像,只是没有固定长度而已。
slice提供了访问数组子序列(或者全部)元素的功能,而且slice的底层确实引用一个数组对象。
切片语法
类型[]T表示一个元素类型为T的切片。
数组的语法:[3]bool{true, true, false}
切片的语法:[]bool{true, true, false},这样则会创建一个和上面相同的数组,然后构建一个引用了它的切片
切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:a[low : high]
它是左闭右开区间,包括第一个元素,但排除最后一个元素。
例:以下表达式创建了一个切片,它包含a中下标从1到3的元素:a[1:4]
切片并不存储任何数据,它只是描述了底层数组中的一段。更改切片的元素会修改其底层数组中对应的元素。
package main
import "fmt"
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}
fmt.Println("primes数组的值:", primes)
var s []int = primes[1:4]
fmt.Println("[1:4]切片s的值:", s)
s[2] = 1818
fmt.Println("s[2]=1818后切片s的值:", s)
fmt.Println("primes数组的值:", primes)
p := []struct {
i int
b bool
}{
{2, true},
{3, false},
{5, true},
{7, true},
{11, false},
{13, true},
}
fmt.Println(p)
}
切片的默认行为
在进行切片时,可以利用它的默认行为来忽略上下界。切片下界的默认值为 0,上界则是该切片的长度。
数组var a [10]int与这些切片等价a[0:10]、 a[:10]、 a[0:]、 a[:]。
切片的构成
一个slice由三个部分构成:指针、长度和容量。
指针:指针指向第一个slice元素对应的底层数组元素的地址(不一定就是数组的第一个元素)。
长度:slice中元素的数目,长度不能超过容量。
容量:从slice的开始位置到底层数据的结尾位置。内置的len(s)和cap(s)函数分别返回slice的长度和容量。
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)
// nil切片
var p []int
fmt.Println(p, len(p), cap(p))
if p == nil {
fmt.Println("nil!")
}
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
nil 切片
切片的零值是nil。
nil切片的长度和容量为0且没有底层数组。
用 make 创建切片
切片可以用内建函数make来创建,这也是创建动态数组的方式。
make函数会分配一个元素为零值的数组并返回一个引用了它的切片:
package main
import "fmt"
func main() {
a := make([]int, 5) //输出:a len=5 cap=5 [0 0 0 0 0]
printSlice("a", a)
b := make([]int, 0, 5)
printSlice("b", b) //输出:b len=0 cap=5 []
//b = b[:cap(b)] len(b)=5, cap(b)=5
c := b[:2]
printSlice("c", c) //输出:c len=2 cap=5 [0 0]
d := c[2:5]
printSlice("d", d) //输出:d len=3 cap=3 [0 0 0]
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %v\n",
s, len(x), cap(x), x)
}
切片的切片
切片可包含任何类型,甚至包括其它的切片。
package main
import (
"fmt"
"strings"
)
func main() {
// 创建一个井字板(经典游戏)
board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
// 两个玩家轮流打上 X 和 O
board[0][0] = "X"
board[2][2] = "O"
board[1][2] = "X"
board[1][0] = "O"
board[0][2] = "X"
for i := 0; i < len(board); i++ {
fmt.Printf("%s\n", strings.Join(board[i], " "))
}
}
向切片追加元素(apped)
append函数:为切片追加新的元素是种常用的操作,内建函数的文档对此函数有详细的介绍。
func append(s []T, vs ...T) []T
第一个参数 s 是一个元素类型为T的切片,其余类型为 T 的值将会追加到该切片的末尾。
append的结果是一个包含原切片所有元素加上新添加元素的切片。
当s的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。
(要了解关于切片的更多内容,请阅读文章 Go 切片:用法和本质。)
package main
import "fmt"
func main() {
var s []int
printSlice(s) //输出:len=0 cap=0 []
// 添加一个空切片
s = append(s, 0)
printSlice(s) //输出:len=1 cap=1 [0]
// 这个切片会按需增长
s = append(s, 1)
printSlice(s) //输出:len=2 cap=2 [0 1]
// 可以一次性添加多个元素
s = append(s, 2, 3, 4)
printSlice(s) //输出:len=5 cap=6 [0 1 2 3 4]
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
Range
for循环的range形式可遍历切片或映射。
当使用 for 循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。
可以将下标或值赋予 _ 来忽略它。
for i, _ := range pow等价于for i := range pow(若需要索引,忽略第二个变量即可)
for _, value := range pow
package main
import "fmt"
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
//range遍历切片pow,i是下标,v是对应值
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
pow := make([]int, 10)
for i := range pow {
pow[i] = 1 << uint(i) // == 2**i
}
for _, value := range pow {
fmt.Print("value = ")
fmt.Printf("%d\n", value)
}
}
package main
import "golang.org/x/tour/pic"
func Pic(dx, dy int) [][]uint8 {
ss := make([][]uint8, dy)
for y := 0; y < dy; y++ {
s := make([]uint8, dx)
for x := 0; x < dx; x++ {
s[x] = uint8((x + y) / 2)
}
ss[y] = s
}
return ss
}
func main() {
pic.Show(Pic)
}
注意点
-
该文件需要导入外部包,需要执行一下命令获取该包
go get golang.org/x/tour/pic若提示
go: added golang.org/x/tour v0.1.0则导入成功,会在目录里的go.mod中记录,并生成go.sum文件 -
go.mod文件用来记录导入包,所以必须要有。若没有需要执行以下命令生成go.mod文件,注意,一个go.mod目录下只能有一个main函数go mod init 目录名 -
由于pic包没有main包,所以不能用
go install -
该文件是base64编码,若想查看需要将
IMAGE:更改为data:image/png;base64,,复制到在线解析网站即可观看。或者将编码放在在html中<img src="">的src里查看。
映射(map)
哈希表是一种巧妙并且实用的数据结构。它是一个无序的key/value对的集合,其中所有的key都是不同的,然后通过给定的key可以在常数时间复杂度内检索、更新或删除对应的value。
在Go语言中,一个map就是一个哈希表的引用。
map的类型
map[K][V],K和V分别对应key和value。
映射的零值为nil。nil映射既没有键,也不能添加键。
map中所有的key都有相同的类型,所有的value也有着相同的类型,但是key和value之间可以是不同的数据类型。
K不要用浮点数类型,因为需要key支持= =比较运算,通过key是否相等判断是否存在。V没有数据类型限制
内置的make函数可以创建一个map,同时还可以指定一些最初的key/value
map中的元素通过key对应的下标语法访问
package main
import "fmt"
type Vertex struct {
Lat, Long float64
}
func main() {
//用make函数可以创建一个map
m := make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])
//用map字面值的语法创建map,同时还可以指定一些最初的key/value:
ages := map[string]int{ //创建了一个由string到int的映射(map)
"alice": 31,
"charlie": 34,
}
ages["alice"] = 32
fmt.Println(ages["alice"]) // "32"
}
修改映射
-
在映射
m中插入或修改元素:m[key] = elem -
获取元素:
elem = m[key] -
删除元素:
delete(m,key) -
通过双赋值检测某个
key是否存在:elem ,ok = m[key]- 若
key在m中,ok为true;否则,ok为false - 若
key不在映射中,那么elem是该映射元素类型的零值。 - 注:若
elem或ok还未声明,可以使用短变量声明:elem ,ok := m[key]
- 若
- 同样的,当从映射中读取或删除某个不存在的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)
}