GO语言基础2| 青训营笔记

101 阅读2分钟

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 在同一行;
  • 支持continuebreak

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)

切片是一种引用类型,它有三个属性:指针,长度和容量。

  1. 指针:指向 slice 可以访问到的第一个元素。
  2. 长度:slice 中元素个数。
  3. 容量:slice 起始元素到底层数组最后一个元素间的元素个数。 image.png 可以把切片理解为,可变长度的数组,其实它底层就是使用数组实现的,增加了自动扩容功能。
    多个切片如果表示同一个数组的片段,它们可以共享数据;因此一个切片和相关数组的其他切片是共享存储的。相反,不同的数组总是代表不同的存储。数组实际上是切片的构建块。

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]VK 和 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中存在该键值对,则oktrue,否则为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 是引用类型,所以在函数间传递时,也不会制造一个映射的副本,这点和切片类似,都很高效。