03流程控制
注意:Go 没有三目运算符,所以不支持? :形式的条件判断。
1.if-else
特点:
- 条件语句不需要使用小括号
()包起来; - 花括号
{}必须有,并且左花括号{必须和if或else在同一行; - 在
if之后,条件语句之前可以添加变量初始化语句,使用;分隔。
2.switch
特点:
- 左花括号
{必须和switch在同一行; - 条件表达式不限制常量或者整数;
switch后可添加变量初始化语句,使用;分割;- 可以不设定条件表达式,在此种情况下,整个
switch结构与多个if-else的逻辑作用等同; - 单个
case中可以出现多个结果选项; - 在
case中添加fallthrough关键字,程序会继续执行下一条case,且它不会去判断下一个case的条件语句; switch支持default语句,当所有case都不满足时,执行default语句。
3.for
Go 语言的 For 循环有 3 种形式,只有其中的一种使用分号。
for init; condition; post { }
for condition { }
for { }
特点:
- 条件表达式不需要使用小括号
()包起来; - 花括号
{}必须有,并且左花括号{必须和for在同一行; - 支持
continue和break。
4.goto,break,continue
goto
- 只能在函数内跳转,需要配合标签一起使用;
- 不能跳过内部变量声明语句;
- 只能跳到同级作用域或者上层作用域内,不能跳到内部作用域内。
goto label;
...
label: statement;
var a int = 10
LOOP: for a < 20 {
if a == 15 {
/* 跳过迭代 */
a = a + 1
goto LOOP
}
fmt.Printf("a的值为 : %d\n", a)
a++
}
break
- 循环语句中跳出当前所在的循环,并开始执行循环之后的语句。
- 在多重循环中,可以用标号 label 标出想 break 的循环。但标签和
continue必须在同一个函数内。
// 不使用标记
fmt.Println("---- break ----")
for i := 1; i <= 3; i++ {
fmt.Printf("i: %d\n", i)
for i2 := 11; i2 <= 13; i2++ {
fmt.Printf("i2: %d\n", i2)
break
}
}
// 使用标记
fmt.Println("---- break label ----")
re:
for i := 1; i <= 3; i++ {
fmt.Printf("i: %d\n", i)
for i2 := 11; i2 <= 13; i2++ {
fmt.Printf("i2: %d\n", i2)
break re
}
}
continue
continue 不是跳出循环,而是跳过当前循环执行下 一次循环语句。
for 循环中,执行 continue 语句会触发 for 增量语句的执行。 在多重循环中,可以用标号 label 标出想 continue 的循环。
// 不使用标记
fmt.Println("---- continue ---- ")
for i := 1; i <= 3; i++ {
fmt.Printf("i: %d\n", i)
for i2 := 11; i2 <= 13; i2++ {
fmt.Printf("i2: %d\n", i2)
continue
}
}
// 使用标记
fmt.Println("---- continue label ----")
re:
for i := 1; i <= 3; i++ {
fmt.Printf("i: %d\n", i)
for i2 := 11; i2 <= 13; i2++ {
fmt.Printf("i2: %d\n", i2)
continue re
}
}
04复合数据类型
1.数组
是具有相同唯一类型的一组已编号且长度固定的数据项序列。
数组元素可以通过索引(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索 引为 1,以此类推。不可访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic。
指针数组 [n]*T,数组指针 *[n]T。
1.1声明
var variable_name [SIZE] variable_type
数组初始值为元素类型零值,也可以用数组字面量初始化数组。
1.2初始化
var b [3]int = [3]int{1, 2, 3}
var c [3]int = [3]int{1, 2}
fmt.Println(b) // [1 2 3]
fmt.Println(c[2]) // 0
如果没有显示指定数组长度,而是用 ...,那么数组长度由实际的元素数量决定。
d := [...]int{1, 2, 3, 4, 5}
fmt.Printf("%T\n", d) // [5]int
还可以指定索引位置来初始化,如果没有指定数组长度,则长度由索引来决定。
e := [4]int{5, 2: 10}
f := [...]int{2, 4: 6}
fmt.Println(e) // [5 0 10 0]
fmt.Println(f) // [2 0 0 0 6]
初始化数组中 {} 中的元素个数不能大于 [] 中的数字。
1.3访问数组元素
可以通过索引(位置)来读取。格式为数组名后加中括号,中括号中为索引的值。
var val int = e[3]
对索引项为 i 的数组元素赋值可以这么操作: e[i] = value ,所以数组是可变的。
由于索引的存在,遍历数组的方法自然就是使用 for 结构:
for 循环中的条件非常重要: i < len(arr1) ,如果写成 i <= len(arr1) 的话会产生越界错误。
1.4多维数组
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type
声明多维的整型数组:
var twodim [3][4]float64
var threedim [5][10][4]int
sites := [2][2]string{}
二维数组中的元素可通过twodim[i][j]来访问。
多维数组可通过大括号来初始值。
a := [3][4]int{
{0, 1, 2, 3} , /* 第一行索引为 0 */
{4, 5, 6, 7} , /* 第二行索引为 1 */
{8, 9, 10, 11} } /* 第三行索引为 2 */
二维数组通过指定坐标来访问。
val := a[2][3]
var value int = a[2][3]
向函数传递数组
在函数定义时,声明形参为数组
- 方式一 形参设定数组大小:
void myFunction(param [10]int){} - 方式二 形参未设定数组大小:
void myFunction(param []int){}
把一个大数组传递给函数会消耗很多内存。有两种方法可以避免这种现象:
- 传递数组的指针
- 使用数组的切片
数组是值类型,赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值。
Go 语言中对数组的处理,一般采用 切片 的方式,切片包含对底层数组内容的引用,作为函数参数时, 类似于 指针传递,函数中的修改对调用者可见。
2.切片(Slice)
切片是一种引用类型,它有三个属性:指针,长度和容量。
- 指针:指向 slice 可以访问到的第一个元素。
- 长度:slice 中元素个数。
- 容量:slice 起始元素到底层数组最后一个元素间的元素个数。
可以把切片理解为,可变长度的数组,其实它底层就是使用数组实现的,增加了自动扩容功能。
多个切片如果表示同一个数组的片段,它们可以共享数据;因此一个切片和相关数组的其他切片是共享存储的。相反,不同的数组总是代表不同的存储。数组实际上是切片的构建块。
2.1定义
var slice_name []Type
var关键字用于声明变量;slice_name表示切片名;Type表示元素类型。
切片不需要说明长度。
方式一、基于数组创建
var array = [...]int{1, 2, 3, 4, 5, 6, 7, 8}
s1 := array[3:6]
fmt.Printf("s1: %v\n", s1) // s1: [4 5 6]
方式二、使用内置函数make创建
// len: 10, cap: 10
a := make([]int, 10)
// len: 10, cap: 15
b := make([]int, 10, 15)
fmt.Printf("a: %v, len: %d, cap: %d\n", a, len(a), cap(a))
fmt.Printf("b: %v, len: %d, cap: %d\n", b, len(b), cap(b))
这里 len 是数组的长度并且也是切片的初始长度。
这里 cap 是切片最长可以达到多少。
2.2len() cap() 函数
len 长度是当前元素个数 cap 长度是底层元素个数。
看开始的原理图即可理解len cap 的含义。
cap限制
超出原 slice.cap 限制,就会重新分配底层数组,即便原数组并未填满。
func main() {
data := [...]int{0, 1, 2, 3, 4, 10: 0}
s := data[:2:3]
fmt.Printf("len=%d cap=%d slice=%v\n", len(s), cap(s), s)
s = append(s, 100, 200) // 一次 append 两个值,超出 s.cap 限制。
fmt.Printf("len=%d cap=%d slice=%v\n", len(s), cap(s), s)
fmt.Println(&s[0], &data[0]) // 比对底层数组起始指针。
}
len=2 cap=3 slice=[0 1]
len=4 cap=6 slice=[0 1 100 200]
0xc000144030 0xc000102120 //两个数组的地址已经不同了
append 后的 s 重新分配了底层数组,并复制数据。如果只追加一个值,则不会超过 s.cap 限制,也就不会重新分配。通常以 2 倍容量重新分配底层数组。在大批量添加数据时,建议 一次性分配足够大的空间,以减少内存分配和数据复制开销。
2.3nil空切片
一个切片在未初始化之前默认为 nil,长度为 0。var numbers []int
2.4切片的遍历
可以使用for循环索引遍历,或者for range循环。
//1.for循环
for i := 0; i < len(s); i++ {
fmt.Printf("s[%d]: %v\n", i, s[i])
}
//2.for range循环
for k, v := range s {
fmt.Printf("s[%d]: %v\n", k, v)
}
2.5切片的截取
可以通过设置下限及上限来设置截取切片 [lower-bound:upper-bound]
默认下限为 0 默认上限为 len(s)
2.6添加append()、删除、拷贝copy()
append :向 slice 尾部添加数据,返回新的 slice 对象。
var numbers []int //定义了一个空切片
/* 允许追加空切片 */
numbers = append(numbers, 0)
/* 向切片添加一个元素 */
numbers = append(numbers, 1)
/* 同时添加多个元素 */
numbers = append(numbers, 2, 3, 4)
/* 添加另外一个切片 */
var a = []int{1, 2, 3}
var b = []int{4, 5, 6}
c := append(a, b...)
copy :函数 copy 在两个 slice 间复制数据,复制长度以 len 小的为准。两个 slice 可指向同一底层数 组,允许元素区间重叠。
data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println("array data : ", data)
s1 := data[8:]
s2 := data[:5]
fmt.Printf("slice s1 : %v\n", s1)
fmt.Printf("slice s2 : %v\n", s2)
copy(s2, s1)
fmt.Printf("copied slice s1 : %v\n", s1)
fmt.Printf("copied slice s2 : %v\n", s2)
fmt.Println("last array data : ", data)
array data : [0 1 2 3 4 5 6 7 8 9]
slice s1 : [8 9]
slice s2 : [0 1 2 3 4]
copied slice s1 : [8 9]
copied slice s2 : [8 9 2 3 4]
last array data : [8 9 2 3 4 5 6 7 8 9]
Go 并没有提供删除切片元素专用的语法或函数,需要使用切片本身的特性来删除元素。
截取法
利用对 slice 的截取删除指定元素。注意删除时,后面的元素会前移,所以下标 i 应该左移一位。
numbers := []int{1, 2, 3, 4, 5}
numbers = append(numbers[:2], numbers[2+1:]...)
number 变成 len=4 cap=5 slice=[1 2 4 5]
拷贝法
numbers := []int{1, 2, 3, 4, 5}
tmp := make([]int, 0, len(numbers))
for _, v := range numbers {
if v != 3 {
tmp = append(tmp, v)
}
}
number 变成 len=4 cap=5 slice=[1 2 4 5]
2.7切片作为函数参数
因为切片是引用,所以它们不需要使用额外的内存并且比使用数组更有效率。
2.8多维切片
var sliceName [][]...[]sliceType
sliceName 为切片的名字 sliceType 为切片的类型,每个[ ]代表着一个维度,切片有几个维度就需要几个[ ]。
3.字典map
关键词 map 表示,类型是 map[K]V。K 和 V 分别是字典的键和值的数据类型,其中键必须支持相等运算符,比如数字,字符串等。
Map 是无序的,我们无法决定 它的返回顺序,这是因为 Map 是使用 hash 表来实现的。
Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
Map 是引用类型的,必须初始化才能使用。
3.1定义map
第一种是直接使用字面量创建;第二种使用内置函数 make。
/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type
/* 使用 make 函数 */
var map_variable = make(map[key_data_type]valuetype)
/*或者简写为:*/
map_variable := make(map[key_data_type]value_data_type)
/* 还可以指定长度 */
map_variable := make(map[key_data_type]value_data_type, 10)
map_variable :map名称
key_data_type :key的数据类型
value_data_type :value值的数据类型
如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对。
字典的零值是 nil,对值是 nil 的字典赋值会报错。
3.2初始化及赋值
student := make(map[string]string)
/* map插入key - value对 */
student["name"] = "xlin"
student["year"] = "2023"
student["email"] = "buzhidao@163.com"
//直接赋值也是初始化,声明的时候填充元素
var teacher = map[string]string{"name": "xlin", "year": "2023", "email": "buzhidao@163.com"}
3.3基本使用
通过下标key,获得Map中的值,也可以进行赋值。
fmt.Println(student["name"])
student["name"] = linx
删除元素
delete(student, "year")
delete(student, "from") // key 不存在也不报错
获取长度
fmt.Println(len(student))
判断键是否存在
如果 Map中不存在 key,val ue就是一个值类型的空值。这就会给我们带来困惑了:现在我们没法区分到底是 key 不存在还 是它对应的 value 就是空值。
Go语言中有个判断map中键是否存在的特殊写法可以解决该问题,格式如下:
value, ok := map[key]
ok 返回一个 bool 值:如果 key 存在于 map,vallue 就是 key 对应的 value 值,并且 ok 为true;如果 key 不存在,value 就是一个空值,并且 ok 会返回 false。
if value, ok := student["name"]; ok {
fmt.Println(value) // linx
}
//也可以这样
if _, ok := map[key]; ok {
// ...
}
在该代码中,ok是一个布尔类型的变量,用于指示Map m 中是否存在键为 "d" 的键值对。如果Map中存在该键值对,则ok为true,否则为false。通过使用 ok 变量,我们可以避免在访问Map中不存在的键时引发运行时错误。
map的遍历
中使用for range遍历map。
for k, v := range m {
fmt.Println(k, v)
}
注意: 遍历map时的元素顺序与添加键值对的顺序无关。
3.4map的切片
元素为map类型的切片
var mapSlice = make([]map[string]string, 3)
值为切片类型的map
var sliceMap = make(map[string][]string, 3)
3.5引用类型
map 是引用类型,所以在函数间传递时,也不会制造一个映射的副本,这点和切片类似,都很高效。