这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
字跳的课真的好快啊,今天已经在讲进阶内容了,而我这个小菜鸟还得啃啃基础语法,今天继续更。。
数组
- 定义:由若干相同类型的元素组成的序列
- 数组的长度是固定的,声明后无法改变
- 数组的长度是数组类型的一部分,eg:元素类型相同但是长度不同的两个数组是不同类型的
- 需要严格控制程序所使用内存时,数组十分有用,因为其长度固定,避免了内存二次分配操作
示例
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()
}
切片
切片组成要素:
- 指针:指向底层数组
- 长度:切片中元素的长度,不能大于容量
- 容量:指针所指向的底层数组的总容量
常见初始化方式
- 使用
make初始化
slice := make([]int, 5) // 初始化长度和容量都为 5 的切片
slice := make([]int, 5, 10) // 初始化长度为 5, 容量为 10 的切片
- 使用简短定义
slice := []int{1, 2, 3, 4, 5}
- 使用数组来初始化切片
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))
fmt.Println("cap: ", cap(slice))
//改变切片长度
slice = append(slice, 6)
fmt.Println("after append operation: ")
fmt.Println("len: ", len(slice))
fmt.Println("cap: ", cap(slice)) //注意,底层数组容量不够时,会重新分配数组空间,通常为两倍
}
以上代码,预期输出如下:
len: 5
cap: 5
after append operation:
len: 6
cap: 12
注意点
- 多个切片共享一个底层数组的情况
对底层数组的修改,将影响上层多个切片的值
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)
}
以上代码预期输出如下:
before modify underlying array:
slice: [1 2 3 4 5]
newSlice: [1 2 3]
after modify underlying array:
slice: [6 2 3 4 5]
newSlice: [6 2 3]
- 使用
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)
}
以上代码预期输出如下:
before modifying underlying array:
slice: [1 2 3 4 5]
newSlice: [1 2 3 4 5]
after modifying underlying array:
slice: [1 2 3 4 5]
newSlice: [6 2 3 4 5]
Map
在 Go 语言里面,map 一种无序的键值对, 它是数据结构 hash 表的一种实现方式,类似 Python 中的字典。
语法
使用关键字 map 来声明形如:
map[KeyType]ValueType
注意点:
- 必须指定 key, value 的类型,插入的纪录类型必须匹配。
- key 具有唯一性,插入纪录的 key 不能重复。
- KeyType 可以为基础数据类型(例如 bool, 数字类型,字符串), 不能为数组,切片,map,它的取值必须是能够使用
==进行比较。 - ValueType 可以为任意类型。
- 无序性。
- 线程不安全, 一个 goroutine 在对 map 进行写的时候,另外的 goroutine 不能进行读和写操作,Go 1.6 版本以后会抛出 runtime 错误信息。
声明和初始化
- 使用 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}
map 基本操作
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 := map[string]int{"北京": 1, "上海": 2, "广州": 3, "深圳": 4}
for city, code := range cMap {
fmt.Printf("%s:%d", city, code)
fmt.Println()
}
线程不安全
cMap := make(map[string]int)
var wg sync.WaitGroup
wg.Add(2)
go func() {
cMap["北京"] = 1
wg.Done()
}()
go func() {
cMap["上海"] = 2
wg.Done()
}()
wg.Wait()
在 Go 1.6 之后的版本,多次运行此段代码,你将遇到这样的错误信息:
fatal error: concurrent map writes
goroutine x [running]:
runtime.throw(0x10c64b6, 0x15)
.....
解决之道:
- 对读写操作加锁
- 使用 security map, 例如
sync.map
map 嵌套
provinces := make(map[string]map[string]int)
provinces["贵阳"] = map[string]int{
"南明区": 1,
"花溪区": 2,
"云岩区": 3,
"乌当区": 4,
}
fmt.Println(provinces["北京"])
结构体
数组、切片和 Map 可以用来表示同一种数据类型的集合,但是当我们要表示不同数据类型的集合时就需要用到结构体。
结构体是由零个或多个任意类型的值聚合成的实体
关键字 type 和 struct 用来定义结构体:
type StructName struct{
FieldName type
}
简单示例:定义一个学生结构体
package main
import "fmt"
type Student struct {
Age int
Name string
}
func main() {
stu := Student{
Age: 18,
Name: "name",
}
fmt.Println(stu)
// 在赋值的时候,字段名可以忽略
fmt.Println(Student{20, "new name"})
return
}
通常结构体中一个字段占一行,但是类型相同的字段,也可以放在同一行,例如:
type Student struct{
Age int
Name, Address string
}
一个结构体中的字段名是唯一的,例如一下代码,出现了两个
Name字段,是错误的:
type Student struct{
Name string
Name string
}
结构体中的字段如果是小写字母开头,那么其他 package 就无法直接使用该字段,例如:
// 在包 pk1 中定义 Student 结构体
package pk1
type Student struct{
Age int
name string
}
// 在另外一个包 pk2 中调用 Student 结构体
package pk2
func main(){
stu := Student{}
stu.Age = 18 //正确
stu.name = "name" // 错误,因为`name` 字段为小写字母开头,不对外暴露
}
结构体中可以内嵌结构体
但是需要注意的是:如果嵌入的结构体是本身,那么只能用指针。请看以下例子。
package main
import "fmt"
type Tree struct {
value int
left, right *Tree
}
func main() {
tree := Tree{
value: 1,
left: &Tree{
value: 1,
left: nil,
right: nil,
},
right: &Tree{
value: 2,
left: nil,
right: nil,
},
}
fmt.Printf(">>> %#v\n", tree)
}
结构体是可以比较的
前提是结构体中的字段类型是可以比较的
package main
import "fmt"
type Tree struct {
value int
left, right *Tree
}
func main() {
tree1 := Tree{
value: 2,
}
tree2 := Tree{
value: 1,
}
fmt.Printf(">>> %#v\n", tree1 == tree2)
}
结构体内嵌匿名成员
声明一个成员对应的数据类型而不指名成员的名字;这类成员就叫匿名成员
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)
}
今天就先写到这吧