Go Study Day02 - 复合数据类型与流程控制
# 数组
# 数组的声明
// 数组是存放元素的容器
// 必须指定存放的元素的类型和容量(长度)
// 数组的长度是数组类型的一部分
var a1 [3]bool // [true false true]
var a2 [4]bool // [true true false false]
fmt.Printf("a1:%T a2:%T\n", a1, a2)
2
3
4
5
6
7
# 数组的初始化
// 数组的初始化
// 如果不初始化:默认元素都是零值(布尔值:false, 整型和浮点型都是0, 字符串:"")
fmt.Println(a1, a2)
// 1. 初始化方式1
a1 = [3]bool{true, true, true}
fmt.Println(a1)
// 2. 初始化方式2:根据初始值自动推断数组的长度是多少
// a10 := [9]int{0, 1, 2, 3, 4, 4, 5, 6, 7}
a10 := [...]int{0, 1, 2, 3, 4, 4, 5, 6, 7}
fmt.Println(a10)
// 3. 初始化方式3:根据索引来初始化
a3 := [5]int{0: 1, 4: 2}
fmt.Println(a3)
2
3
4
5
6
7
8
9
10
11
12
13
# 数组的遍历
// 数组的遍历
citys := [...]string{"北京", "上海", "深圳"} // 索引:0~2 citys[0],citys[1],citys[2]
// 1. 根据索引遍历
for i := 0; i < len(citys); i++ {
fmt.Println(citys[i])
}
// 2. for range遍历
for i, v := range citys {
fmt.Println(i, v)
}
2
3
4
5
6
7
8
9
10
# 二维数组
// 多维数组
// [[1 2] [3 4] [5 6]]
var a11 [3][2]int
a11 = [3][2]int{
[2]int{1, 2},
[2]int{3, 4},
[2]int{5, 6},
}
fmt.Println(a11)
// 多维数组的遍历
for _, v1 := range a11 {
fmt.Println(v1)
for _, v2 := range v1 {
fmt.Println(v2)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
数组是值类型
// 数组是值类型
b1 := [3]int{1, 2, 3} // [1 2 3]
b2 := b1 // [1 2 3] Ctrl+C Ctrl+V => 把world文档从文件夹A拷贝到文件夹B
b2[0] = 100 // b2:[100 2 3]
fmt.Println(b1, b2) // b1:[1 2 3]
2
3
4
5
# 切片(slice)
切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。
切片是一个引用类型,它的内部结构包含地址
、长度
和容量
。切片一般用于快速地操作一块数据集合。
要点
切片指向了一个底层的数组。
切片的长度就是它元素的个数。
切片的容量是底层数组从切片的第一个元素到最后一个元素的数量。
# 切片的定义
// 切片的定义
var s1 []int // 定义一个存放int类型元素的切片
var s2 []string // 定义一个存放string类型元素的切片
fmt.Println(s1, s2)
fmt.Println(s1 == nil) // true
fmt.Println(s2 == nil) // true
2
3
4
5
6
# 切片的初始化
// 初始化
s1 = []int{1, 2, 3}
s2 = []string{"沙河", "张江", "平山村"}
fmt.Println(s1, s2)
fmt.Println(s1 == nil) // false
fmt.Println(s2 == nil) // false
2
3
4
5
6
# 切片的长度和容量
// 长度和容量
fmt.Printf("len(s1):%d cap(s1):%d\n", len(s1), cap(s1))
fmt.Printf("len(s2):%d cap(s2):%d\n", len(s2), cap(s2)
2
3
# 使用 make 创建切片
make()函数用于创建指定长度和容量的切片。
s1 := make([]int, 5, 10)
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))
s2 := make([]int, 0, 10)
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s2, len(s2), cap(s2))
2
3
4
5
# 切片的本质
切片就是一个框,框住了一块连续的内存。
切片属于引用类型,真正的数据都是保存在底层数组里的。
判断一个切片是否是空的,要是用
len(s) == 0
来判断
# append
// 调用append函数必须用原来的切片变量接收返回值
// append追加元素,原来的底层数组放不下的时候,Go底层就会把底层数组换一个
// 必须用变量接收append的返回值
s1 = append(s1, "广州")
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))
s1 = append(s1, "杭州", "成都")
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))
ss := []string{"武汉", "西安", "苏州"}
s1 = append(s1, ss...) // ...表示拆开
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))
2
3
4
5
6
7
8
9
10
# copy
a1 := []int{1, 3, 5}
a2 := a1 // 赋值
var a3 = make([]int, 3, 3)
copy(a3, a1) // copy
fmt.Println(a1, a2, a3)
a1[0] = 100
fmt.Println(a1, a2, a3)
2
3
4
5
6
7
# 切片的扩容
如果申请的容量大于原来的 2 倍,那就直接扩容至新申请的容量
如果小于 1024, 那么就直接两倍 3. 如果大于 1024,就按照 1.25 倍去扩容 4. 具体存储的值类型不同,扩容策略也有一定的不同。
# map
map 也是引用类型,必须初始化之后才能使用。
func main() {
var m1 map[string]int
fmt.Println(m1 == nil) // 还没有初始化(没有在内存中开辟空间)
m1 = make(map[string]int, 10) // 要估算好该map容量,避免在程序运行期间再动态扩容
m1["理想"] = 18
m1["jiwuming"] = 35
fmt.Println(m1)
fmt.Println(m1["理想"])
// 约定成俗用ok接收返回的布尔值
fmt.Println(m1["娜扎"]) // 如果不存在这个key拿到对应值类型的零值
value, ok := m1["娜扎"]
if !ok {
fmt.Println("查无此key")
} else {
fmt.Println(value)
}
// map的遍历
for k, v := range m1 {
fmt.Println(k, v)
}
// 只遍历key
for k := range m1 {
fmt.Println(k)
}
// 只遍历value
for _, v := range m1 {
fmt.Println(v)
}
// 删除
delete(m1, "jiwuming")
fmt.Println(m1)
delete(m1, "沙河") // 删除不存在的key
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 指针
Go 语言中不存在指针操作,只需要记住两个符号:
&
:取地址*
:根据地址取值
# 应用场景
需要修改参数值
函数传参时是值拷贝,也就是把当前的变量复制传递,这样函数内部修改参数值时不影响原始值,但如果想改原始值呢,就可以通过
&变量名
来获取参数对应的内存地址,这样就可以修改原值。当然值类型的需要传递内存地址,引用类型本身就是内存地址,所以即使是值拷贝也仅仅是把引用类型的指针拷贝,对引用类型引用的具体值修改还是会生效的。传递的变量比较大,避免内存拷贝。假设字符串有1m,这是如果传参会进行复制,避免不必要的内存占用可以传递内存地址。
声明引用类型时有必要声明指针类型吗?比如声明一个字符串指针类型的切片会比一个字符串类型的切片更节省内存吗?
答案是否定的。
函数传参是值拷贝的,无论是传递字符串类型的切片还是字符串指针类型的切片,都只是传递了指针的副本。因此,在函数内部操作字符串类型的切片和字符串指针类型的切片所引用的底层数据的内存使用量应该是相同的。
值得一提的是,尽管两种类型的切片在函数传参时所使用的内存相同,但是在分配内存时有所不同。在创建一个指针类型的切片时,除了切片结构体本身的内存外,还需要为切片指向的每个元素分配内存;而创建值类型的切片时,只需要为切片结构体和切片底层数组分配内存,因为字符串的底层数据是不可变的。
因此,在声明和初始化一个字符串类型的切片时,可以使用字符串字面值初始化,从而避免了新的内存分配。而对于字符串指针类型的切片,则需要在声明和初始化时显式地分配内存。如果您需要对字符串进行频繁的操作并且关心内存使用量,那么字符串类型的切片是更优的选择。
# make 和 new 的区别
- make 和 new 都是用来申请内存的
- new 很少用,一般用来给基本数据类型申请内存,
string
、int
,返回的是对应类型的指针(*string、*int)。 - make 是用来给
slice
、map
、chan
申请内存的,make 函数返回的的是对应的这三个类型本身
# 流程控制
# if 判断
// if条件判断
func main() {
// age := 19
// if age > 18 { // 如果 age > 18 就执行这个{}中的代码
// fmt.Println("啦啦啦啦啦啦啦啦!")
// } else { // 否则就执行这个{}中的代码
// fmt.Println("改写暑假作业啦!")
// }
// 多个判断条件
// if age > 35 {
// fmt.Println("人到中年")
// } else if age > 18 {
// fmt.Println("青年")
// } else {
// fmt.Println("好好学习!")
// }
// 作用域
// age变量此时只在if条件判断语句中生效
if age := 19; age > 18 { // 如果 age > 18 就执行这个{}中的代码
fmt.Println("啦啦啦啦啦啦啦啦!")
} else { // 否则就执行这个{}中的代码
fmt.Println("改写暑假作业啦!")
}
// fmt.Println(age) // 在这里是找不到age
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# for 循环
func main() {
// 基本格式
// for i := 0; i < 10; i++ {
// fmt.Println(i)
// }
// 变种1
// var i = 5
// for ; i < 10; i++ {
// fmt.Println(i)
// }
// 变种2
// var i = 5
// for i < 10 {
// fmt.Println(i)
// i++
// }
// 无限循环
// for {
// fmt.Println("123")
// }
// for range循环
s := "Hello沙河"
for i, v := range s {
fmt.Printf("%d %c\n", i, v)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# for range(键值循环)
Go 语言中可以使用for range
遍历数组、切片、字符串、map 及通道(channel)。 通过for range
遍历的返回值有以下规律:
- 数组、切片、字符串返回索引和值。
- map 返回键和值。
- 通道(channel)只返回通道内的值。
# switch case
func switchDemo1() {
finger := 3
switch finger {
case 1:
fmt.Println("大拇指")
case 2:
fmt.Println("食指")
case 3:
fmt.Println("中指")
case 4:
fmt.Println("无名指")
case 5:
fmt.Println("小拇指")
default:
fmt.Println("无效的输入!")
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func testSwitch3() {
switch n := 7; n {
case 1, 3, 5, 7, 9:
fmt.Println("奇数")
case 2, 4, 6, 8:
fmt.Println("偶数")
default:
fmt.Println(n)
}
}
2
3
4
5
6
7
8
9
10
func switchDemo4() {
age := 30
switch {
case age < 25:
fmt.Println("好好学习吧")
case age > 25 && age < 35:
fmt.Println("好好工作吧")
case age > 60:
fmt.Println("好好享受吧")
default:
fmt.Println("活着真好")
}
2
3
4
5
6
7
8
9
10
11
12
13
# goto
跳转到指定标签
func gotoDemo2() {
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if j == 2 {
// 设置退出标签
goto breakTag
}
fmt.Printf("%v-%v\n", i, j)
}
}
return
// 标签
breakTag:
fmt.Println("结束for循环")
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# break
和其他语言一样
# continue
和其他语言一样