Go 具有指针。 指针保存了变量的内存地址。
类型 *T
是指向类型 T
的值的指针。其零值是 `nil`。
var p *int
&
符号会生成一个指向其作用对象的指针。
i := 42
p = &i
*
符号表示指针指向的底层的值。
fmt.Println(*p) // 通过指针 p 读取 i
*p = 21 // 通过指针 p 设置 i
这也就是通常所说的“间接引用”或“非直接引用”。
与 C 不同,Go 没有指针运算。
例子:
package main
import "fmt"
func main() {
i, j := 42, 2701
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 / 37 // divide j through the pointer
fmt.Println(j) // see the new value of j
}
一个结构体(`struct`)就是一个字段的集合。可用于表示不同数据类型的集合。
结构体是由零个或多个任意类型的值聚合成的实体。
(而 type
的含义跟其字面意思相符。)
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
fmt.Println(Vertex{1, 2})
}
结构体字段使用点号来访问。
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
v.X = 4
fmt.Println(v.X)
}
结构体字段也可以通过结构体指针来访问。
ps:官方对此提到一句“通过指针间接的访问是透明的”,对此句中的“透明的”暂不知该如何解释,网上暂时也没找到解释。我最初误会以为可以通过指针访问私有变量,但经过尝试,事实并不可以。
结构体文法表示通过结构体字段的值作为列表来新分配一个结构体。
使用 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)
}
输出结果:
结构体内嵌匿名成员
声明一个成员对应的数据类型而不指名成员的名字;这类成员就叫匿名成员。
package main
import "fmt"
type Person struct {
Age int
Name string
}
type Student struct {
Person
}
func main() {
per := Person{
Age: 18,
Name: "name",
}
stu := Student{Person: per}
fmt.Println("stu.Age: ", stu.Age)
fmt.Println("stu.Name: ", stu.Name)
}
Go的面向对象中通常用匿名成员实现继承。
类型 [n]T
是一个有 n
个类型为 T
的值的数组。
表达式
var a [10]int
定义变量 a
是一个有十个整数的数组。
package main
import "fmt"
func main() {
// 定义长度为 5 的数组
var arr1 [5]int
for i := 0; i < 5; i++ {
arr1[i] = i
}
printHelper("arr1", arr1)
// 以下赋值会报类型不匹配错误,因为数组长度是数组类型的一部分
// arr1 = [3]int{1, 2, 3}
arr1 = [5]int{2, 3, 4, 5, 6} // 长度和元素类型都相同,可以正确赋值
// 简写模式,在定义的同时给出了赋值
arr2 := [5]int{0, 1, 2, 3, 4}
printHelper("arr2", arr2)
// 数组元素类型相同并且数组长度相等的情况下,数组可以进行比较
fmt.Println(arr1 == arr2)
// 也可以不显式定义数组长度,由编译器完成长度计算
var arr3 = [...]int{0, 1, 2, 3, 4}
printHelper("arr3", arr3)
// 定义前四个元素为默认值 0,最后一个元素为 -1
var arr4 = [...]int{4: -1}
printHelper("arr4", arr4)
// 多维数组
var twoD [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("twoD: ", twoD)
}
func printHelper(name string, arr [5]int) {
for i := 0; i < 5; i++ {
fmt.Printf("%v[%v]: %v\n", name, i, arr[i])
}
// len 获取长度
fmt.Printf("len of %v: %v\n", name, len(arr))
// cap 也可以用来获取数组长度
fmt.Printf("cap of %v: %v\n", name, cap(arr))
fmt.Println()
}
一个 slice 会指向一个序列的值,并且包含了长度信息。
[]T
是一个元素类型为 T
的 slice。
切片组成要素:
常见初始化方式
make
初始化slice := make([]int, 5) // 初始化长度和容量都为 5 的切片
slice := make([]int, 5, 10) // 初始化长度为 5, 容量为 10 的切片
slice := []int{1, 2, 3, 4, 5}
注意:与数组的隐式长度定义是有区别
package main
import "fmt"
func main() {
var arr = [5]int{0, 1, 2, 3, 4}
var arr1 = [...]int{0, 1, 2, 3, 4}
var arr2 = []int{0, 1, 2, 3, 4}
fmt.Printf("%T(%v)\n",arr,arr)
fmt.Printf("%T(%v)\n",arr1,arr1)
fmt.Printf("%T(%v)\n",arr2,arr2)
}
输出结果:
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[0:3] // 左闭右开区间,最终切片为 [1,2,3]
sliceA := []int{1, 2, 3, 4, 5}
sliceB := sliceA[0:3] // 左闭右开区间,sliceB 最终为 [1,2,3]
长度和容量
package main
import (
"fmt"
)
func main() {
slice := []int{1, 2, 3, 4, 5}
fmt.Println("len: ", len(slice))//len()获得长度
fmt.Println("cap: ", cap(slice))//len()获得容量
//改变切片长度
slice = append(slice, 6)
fmt.Println("after append operation: ")
fmt.Println("len: ", len(slice))
fmt.Println("cap: ", cap(slice)) //注意,底层数组容量不够时,会重新分配数组空间,通常为两倍
}
输出结果:
注意:底层数组容量不够时,会重新分配数组空间,通常为两倍,返回的 slice 会指向这个新分配的数组。
func append(s []T, vs ...T) []T
append
的第一个参数 s
是一个类型为 T
的数组,其余类型为 T
的值将会添加到 slice。
append
的结果是一个包含原 slice 所有元素加上新添加的元素的 slice。
多个切片共享一个底层数组时,对底层数组的修改,将影响上层多个切片的值
package main
import (
"fmt"
)
func main() {
slice := []int{1, 2, 3, 4, 5}
newSlice := slice[0:3]
fmt.Println("before modifying underlying array:")
fmt.Println("slice: ", slice)
fmt.Println("newSlice: ", newSlice)
fmt.Println()
newSlice[0] = 6
fmt.Println("after modifying underlying array:")
fmt.Println("slice: ", slice)
fmt.Println("newSlice: ", newSlice)
}
输出结果:
使用 copy
方法可以避免共享同一个底层数组
package main
import (
"fmt"
)
func main() {
slice := []int{1, 2, 3, 4, 5}
newSlice := make([]int, len(slice))
copy(newSlice, slice)
fmt.Println("before modifying underlying array:")
fmt.Println("slice: ", slice)
fmt.Println("newSlice: ", newSlice)
fmt.Println()
newSlice[0] = 6
fmt.Println("after modifying underlying array:")
fmt.Println("slice: ", slice)
fmt.Println("newSlice: ", newSlice)
}
输出结果:
slice 的零值是 `nil`。一个 nil 的 slice 的长度和容量是 0。
package main
import "fmt"
func main() {
var z []int
fmt.Println(z, len(z), cap(z))
if z == nil {
fmt.Println("nil!")
}
}
输出结果:
Go 中切片扩容的策略是这样的(https://halfrost.com/go_slice/):
在 Go 语言里面,map 一种无序的键值对, 它是数据结构 hash 表的一种实现方式,类似 Python 中的字典。
map 在使用之前必须用 make
而不是 new
来创建;值为 nil
的 map 是空的,并且不能赋值。
使用关键字 map 来声明形如:
map[KeyType]ValueType
注意点:
==
进行比较。package main
import "fmt"
type Vertex struct {
Lat, Long float64
}
var m map[string]Vertex
func main() {
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])
}
声明和初始化
var cMap map[string]int // 只定义, 此时 cMap 为 nil
fmt.Println(cMap == nil)
cMap["北京"] = 1 // 报错,因为 cMap 为 nil
make
cMap := make(map[string]int)
cMap["北京"] = 1
// 指定初始容量
cMap = make(map[string]int, 100)
cMap["北京"] = 1
说明:在使用 make 初始化 map 的时候,可以指定初始容量,这在能预估 map key 数量的情况下,减少动态分配的次数,从而提升性能。
cMap := map[string]int{"北京": 1}
基本操作:
package main
import "fmt"
func main() {
cMap := map[string]int{}
cMap["北京"] = 1 //写
code := cMap["北京"] // 读
fmt.Println(code)
code = cMap["广州"] // 读不存在 key
fmt.Println(code)
code, ok := cMap["广州"] // 检查 key 是否存在
if ok {
fmt.Println(code)
} else {
fmt.Println("key not exist")
}
delete(cMap, "北京") // 删除 key
fmt.Println(cMap["北京"])
}
输出结果:
如果 key
在 cMap
中,`ok` 为 true,code为对应的value 。否则, ok
为 `false`,并且 code 是 map 的元素类型的零值。
循环和无序性
package main
import "fmt"
func main() {
cMap := map[string]int{"北京": 1, "上海": 2, "广州": 3, "深圳": 4}
for city, code := range cMap {
fmt.Printf("%s:%d", city, code)
fmt.Println()
}
}
第一次输出结果:
第二次输出结果:
map 嵌套
provinces := make(map[string]map[string]int)
provinces["北京"] = map[string]int{
"东城区": 1,
"西城区": 2,
"朝阳区": 3,
"海淀区": 4,
}
fmt.Println(provinces["北京"])
一个小练习:
实现 `WordCount`。它应当返回一个含有 s
中每个 “词” 个数的 map。
package main
import (
"fmt"
"strings"
)
func WordCount(s string) map[string]int{
rs := map[string]int{}
s = strings.ToLower(s) //小写格式化
s = strings.Replace(s,".","",-1)//去除所有"."
s = strings.Replace(s,",","",-1)//去除所有","
words := strings.Fields(s)
for _,v := range words{
item,ok := rs[v]
if ok {
rs[v] = item+1
} else {
rs[v] = 1
}
}
return rs
}
func main() {
s :="A boy lives near my house and I never know his name. Every day, he will bring some " +
"food to the homeless dogs and cats. Sometimes when I am passing through, I see what " +
"he does. He is so kind that I decide to make friends with him. He is talkative and " +
"have a lot of funny things to share. What a lovely boy."
m := WordCount(s)
//for word,count := range m{
// fmt.Println(word,":",count)
//}
fmt.Println(m)
}
输出结果:
用循环输出的话,结果是无序的。
在 Go 语言里面,还可以通过自定义类型来表示一些特殊的数据结构和业务逻辑。
使用关键字 type
来声明:
type NAME TYPE
声明语法
type City string
type (
B0 = int8
B1 = int16
B2 = int32
B3 = int64
)
type (
A0 int8
A1 int16
A2 int32
A3 int64
)
简单示例
package main
import (
"fmt"
"time"
)
type (
Country string
Year int
)
func main() {
country := Country("China")
fmt.Println("I'm come from " + country)//字符串拼接
fmt.Println(len(country))
t := time.Now()
year := Year(2020)
thisYear := Year(t.Year())
if year == thisYear {
fmt.Println("This year is ",year)
}
}
自定义类型对应原始类型的所有操作其同样适用。
注意:自定义类型与其对应原始类型是不同的类型,故thisYear := Year(t.Year())需要显示类型转换(t.Year()返回int值,year == thisYear该比较两个变量类型需一致)
package main
import "fmt"
type Age int
type Height int
func main() {
age := Age(12)
height := Height(175)
fmt.Println(height / age)
}
运行如上代码会出现./main.go:12:21: invalid operation: height / age (mismatched types Height and Age)
错误,修复方法使用显式转换:
fmt.Println(int(height) / int(age))