指针 Go 具有指针。 指针保存了变量的内存地址。
类型 *T
是指向类型 T
的值的指针。其零值是 `nil`。
var p *int
&
符号会生成一个指向其作用对象的指针。i := 42
p = &i
*
符号表示指针指向的底层的值。fmt.Println(*p) // 通过指针 p 读取 i
*p = 21// 通过指针 p 设置 i
这也就是通常所说的“间接引用”或“非直接引用”。
与 C 不同,Go 没有指针运算。
例子:
package mainimport "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 mainimport "fmt"type Vertex struct {
X int
Y int
}func main() {
fmt.Println(Vertex{1, 2})
}
结构体字段使用点号来访问。
package mainimport "fmt"type Vertex struct {
X int
Y int
}func main() {
v := Vertex{1, 2}
v.X = 4
fmt.Println(v.X)
}
结构体字段也可以通过结构体指针来访问。
ps:官方对此提到一句“通过指针间接的访问是透明的”,对此句中的“透明的”暂不知该如何解释,网上暂时也没找到解释。我最初误会以为可以通过指针访问私有变量,但经过尝试,事实并不可以。
结构体文法
结构体文法表示通过结构体字段的值作为列表来新分配一个结构体。
使用
Name:
语法可以仅列出部分字段。(字段名的顺序无关。)特殊的前缀
&
返回一个指向结构体的指针。package mainimport "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 mainimport "fmt"type Person struct {
Ageint
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的面向对象中通常用匿名成员实现继承。
数组
- 定义:由若干相同类型的元素组成的序列
- 数组的长度是固定的,声明后无法改变
- 数组的长度是数组类型的一部分,eg:元素类型相同但是长度不同的两个数组是不同类型的
- 需要严格控制程序所使用内存时,数组十分有用,因为其长度固定,避免了内存二次分配操作
[n]T
是一个有 n
个类型为 T
的值的数组。表达式
var a [10]int
定义变量
a
是一个有十个整数的数组。package mainimport "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 一个 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 mainimport "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 mainimport (
"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 mainimport (
"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 mainimport (
"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 mainimport "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/):
- 首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)
- 否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap)
- 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的 1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)
- 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)
map 在使用之前必须用
make
而不是 new
来创建;值为 nil
的 map 是空的,并且不能赋值。使用关键字 map 来声明形如:
map[KeyType]ValueType
注意点:
- 必须指定 key, value 的类型,插入的纪录类型必须匹配。
- key 具有唯一性,插入纪录的 key 不能重复。
- KeyType 可以为基础数据类型(例如 bool, 数字类型,字符串), 不能为数组,切片,map,它的取值必须是能够使用
==
进行比较。 - ValueType 可以为任意类型。
- 无序性。
- 线程不安全, 一个 goroutine 在对 map 进行写的时候,另外的 goroutine 不能进行读和写操作,Go 1.6 版本以后会抛出 runtime 错误信息。
package mainimport "fmt"type Vertex struct {
Lat, Long float64
}var m map[string]Vertexfunc main() {
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])
}
声明和初始化
- 使用 var 声明
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 mainimport "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 mainimport "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 mainimport (
"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|Go进阶之路——复杂类型】用循环输出的话,结果是无序的。
自定义类型 在 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 mainimport (
"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 mainimport "fmt"type Age int
type Height intfunc 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))
推荐阅读
- Go|Docker后端部署详解(Go+Nginx)
- GO|GO,GO,GO!
- 【golang】leetcode中级-字母异位词分组&无重复字符的最长子串
- 彻底理解Golang Map
- kratos线上开源年会它来啦~
- 深入浅出 Golang 资源嵌入方案(go-bindata篇)
- 深入浅出 Golang 资源嵌入方案(前篇)
- golang 经典案例总结
- Go实战 | 基于有向无环图的并发执行流的实现