一、if
和 Go 函数一样,if 语句的分支代码块的左大括号与 if 关键字在同一行上,这也是 Go 代码风格的统一要求,gofmt 工具会帮助我们实现这一点;
if 语句的布尔表达式整体不需要用括号包裹,一定程度上减少了开发人员敲击键盘的次数。而且,if 关键字后面的条件判断表达式的求值结果必须是布尔类型,即要么是 true,要么是 false。
1.1 if 语句形式
1 | package main |
1.2 if 自用变量
在 if 语句中声明自用变量是 Go 语言的一个惯用法,这种使用方式直观上可以让开发者有一种代码行数减少的感觉,提高可读性。
同时,由于这些变量是 if 语句自用变量,它的作用域仅限于 if 语句的各层隐式代码块中,if 语句外部无法访问和更改这些变量,这就让这些变量具有一定隔离性,这样你在阅读和理解 if 语句的代码时也可以更聚焦。
1 | func main() { |
逻辑表达式中可以含有变量变量或常量;
if句子中允许包含1个(仅1个分号),在分号初始化一些局部变量(即只在if块内可见);
二、switch
Go语言的 switch 要比C语言的更加通用,表达式不需要为常量,甚至不需要为整数,case 按照从上到下的顺序进行求值,直到找到匹配的项,如果 switch 没有表达式,则对 true 进行匹配,因此,可以将 if else-if else 改写成一个 switch。
相对于C语言和 Java 等其它语言来说,Go语言中的 switch 结构使用上更加灵活,语法设计尽量以使用方便为主。
2.1 基本用法
1 | func main() { |
1 | func add(n int) int { |
2.2 空 switch
switch后带表达式时,switch-case只能模拟相等的情况;如果switch后不带表达式,case后就可以跟任意的条件表达式。
1 | func main() { |
2.3 fallthrough
switch中从上往下,只要找到成立的case,就不再执行后面的case了,所以为了提高性能,把大概率会匹配的case放在前面;
case里如果带了fallthrough,则执行完本case还会去判断下一个case是否满足;
在switch type语句的case子句中不能使用fallthrough。
1 | func main() { |
输出如下:
1 | hello |
2.4 switch type
switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型。
1 | func main() { |
三、for 循环
在go中,只有for循环,没有其他循环关键字,没有while循环,也没有do while,通过for循环是可以实现 类似于while的功能
3.1 for 循环基本使用
1 | func main() { |
3.2 for range
遍历数组或切片
for i, ele := range arr
遍历string
for i, ele := range “Hello World”
遍历map,go不保证遍历的顺序
for key, value := range m
遍历channel,遍历前一定要先close
for ele := range ch
for range拿到的是数据的拷贝
1 | func main() { |
冒泡排序
- 排序就是把一组数据按照特定的顺序重新排列.可以是升序,降序等
- 冒泡排序利用双重for循环把最大(小)的值移动到一侧,每次可以判断出一个数据,如果有n个数组,执行n-1次循环就可以完成排序
- 排序代码(升序还是降序主要是看if判断是大于还是小于
1 | // 冒泡排序 |
3.3 break 和 continue
break与continue用于控制for循环的代码流程,并且只针对最靠近自己的外层for循环;
break:跳出循环,break语句用于在结束其正常执行之前突然终止fro循环;
continue:跳出一次循环,continue语句用于跳过for循环的当前迭代,在continue语句后面的for循环中的所有代码将不会在当前迭代中执行,循环将继续到下一个迭代。
break
1 | func main() { |
continue
1 | func for_continue() { |
四、goto 与 label
Go 语言中有 goto
这个功能,这个功能会影响代码的可读性, 会让代码结构看起来比较乱。
for、switch 或 select 语句都可以配合标签(label)形式的标识符使用,即某一行第一个以冒号(:
)结尾的单词(gofmt 会将后续代码自动移至下一行)。
1 | func main() { |
Labal可以有多个,但是标签(Labal)定义了就必须使用
goto也可以用于跳出循环,执行指定标签位置代码
1 | func main() { |
break和continue 也可以使用label
1 | func main() { |
还可以使用 goto 语句和标签配合使用来模拟循环
1 | func main() { |
特别注意 使用标签和 goto 语句是不被鼓励的:它们会很快导致非常糟糕的程序设计,而且总有更加可读的替代方案来实现相同的需求。
五、struct
go语言可以通过自定义的方式形成新的类型,结构体
就是这些类型中的一种复合类型,结构体是由一个或多个任意类型的值聚合成的实体,每个值都可以称为结构体的成员。
结构体的成员也可以称为字段,每个字段有如下属性:
- 字段名必须唯一
- 字段拥有自己的类型和值
- 字段的类型也可以是结构体,甚至是字段所在结构体的类型
使用关键字 type 可以将各种基本类型定义为自定义类型,基本类型包括整型、字符串、布尔等。结构体是一种复合的基本类型,通过 type 定义为自定义类型后,使结构体更便于使用。
结构体的定义格式如下:
1 | type 类型名 struct { |
对各个部分的说明:
- 类型名:标识自定义结构体的名称,在同一个包内不能重复。
- struct{}:表示结构体类型,type 类型名 struct{}可以理解为将 struct{} 结构体定义为类型名的类型。
- 字段1、字段2……:表示结构体字段名,结构体中的字段名必须唯一。
- 字段1类型、字段2类型……:表示结构体各个字段的类型。
5.1 结构体创建、访问和修改
- 结构体可以定义在函数内部或函数外部(与普通变量一样),定义位置影响到结构体的访问范围;
- 如果结构体定义在函数外面,结构体名称首字母大写则结构体能跨包访问;
- 如果结构体能跨包访问,并且属性首字母大写则属性也能跨包访问。
结构体创建、访问和修改
1 | //定义结构体 |
5.2 成员函数(方法)
1 | //可以把user理解为hello函数的参数,即heool(u user, man string) |
调用函数方法
1 | type user struct { |
为任意类型添加方法
1 | //自定义类型 |
5.3 匿名结构体
匿名结构体通常用于只使用一次的情况
1 | //声明一个名为stu的匿名结构体 |
5.4 结构体中含有匿名成员
1 | type Stuednt struct { |
5.5 结构体指针
例如:在成员函数中修改一个的结构体的属性
1 | type Stuednt struct { |
我们创建的s想要调用say
,实参是按照值传递的,所以say
接收到的是实参的副本,也就是s的复制品。既然是副本,那么我们在say
修改形参接收者的属性并不会影响到实参本身的属性
通过指针修改
1 | type Stuednt struct { |
我们使用未修改前的语句时,实参接收者是Student类型的变量,形参接收者是*Student类型的变量,go的编译器会隐式的获取变量的地址, 因此我们可以修改成功
5.6 结构体嵌套
一个结构体中可以嵌套包含另一个结构体或结构体指针
1 | //Address 地址结构体 |
嵌套匿名结构体
当访问结构体成员时会先在结构体中查找该字段,找不到再去匿名结构体中查找。
1 | //Address 地址结构体 |
嵌套结构体的字段名冲突
嵌套结构体内部可能存在相同的字段名。这个时候为了避免歧义需要指定具体的内嵌结构体的字段。
1 | //Address 地址结构体 |
六、深拷贝与浅拷贝
深拷贝(Deep Copy)
拷贝的是数据本身,创造一个样的新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值。既然内存地址不同,释放内存地址时,可分别释放。
值类型的数据,默认全部都是深复制,Array、Int、String、Struct、Float,Bool。
浅拷贝(Shallow Copy)
拷贝的是数据地址,只复制指向的对象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化。释放内存地址时,同时释放内存地址。
引用类型的数据,默认全部都是浅复制,Slice,Map。