一、变量
-
go语言是一门静态类型的编程语言,在编译之前,所有的变量都要声明数据类型。
-
go语言中变量的声明方式主要有两种:var + 变量名 + 数据类型、短变量声明,其中短变量声明只能声明局部变量
-
go语言拥有类型推断
-
类型推断有什么好处:
- 可以明显提升代码的灵活性,使得代码的重构可以更加容易,同时又不会给代码的维护带来额外负担
- 不会损失程序的运行效率
- 对于变量的重声明需要注意的地方
- 由于变量在之前已经声明过了,所以再次声明的时候必须和之前的数据类型保持一致
- 变量重声明只能用短变量声明的方式进行
- 变量重声明的时候至少需要两个变量,一个需要重声明的变量,一个新变量
- 变量类型的断言主要有两种方式:
- 将变量转成空接口,然后再和具体的类型做比较
- 实现函数根据返回结果判断类型是否相等
- 常见类型转换注意的问题
- 整数类型和整数常量互转:
a 只要源值在目标类型的范围内,转换就是合法的
b go语言中整数类型是用补码表示的,高数域的常量转换为低数域的常量是,只要移除高数位的补码即可
- 浮点型转整型
a 会把浮点型小数点后的全部截取掉
- 整数型转string型
a 被转换的整数值应该可以代表一个有效的 Unicode 代码点,否则转换的结果会是一堆黑色问号
- string 类型与各种切片类型互转
a string 转换成[]byte切片,字符串呗转换成一个个的字节
b string 转换成 []rune,字符串转换成一个个 Unicode 字符
- 别名类型和潜在类型的区别
- 别名类型和源类型除了名字之外,没有其他任何不同
- 潜在类型相同的不同类型之间是可以相互转换的
二、数组和切片
- 数组
- 集合类的类型,可以用来存储某一类型的值,属于值类型
- 数组的长度可不变,声明时就要声明长度,声明之后长度不可变
- 数组的容量永远等于数组的长度
- 两个数组只有存储的值和长度完全相等,两个数组才相等
- 切片
- 集合类的类型,可以用来存储某一类型的值,属于引用类型
- 长度可变,是对于数组一段连续的引用
- 切片的长度会随着元素的增加而增加,不会随着元素的减少而减少
- 切片的容量可以看作是其底层数组的长度
3.切片的扩容机制
- 当切片的长度小于1024时,每次切片扩容都是原来的两倍,当切片长度大于1024时,切片容量每次为之前的1.25倍
- 扩容时会生成新的切片,同时也就生成了新的底层数组,所以每个底层对于它的切片来说都是不变的
- 如果一次性追加的元素较多,比2倍还大,那么就以最新长度为扩容之后的容量
- 新长度不会超过切片的容量,那么切片就不会扩容,那么邻近切片窗口右边的元素会被新元素替换
三、字典
- 键值对类型
- map 键类型限制
- 字典不会存储键值,而是存储键值的哈希值
- 字典的键值类型不能是包含函数类型、字典类型、切片类型的数据类型
- 字典的键值类型限制是因为要对键值进行 == 判定
- 应该优先考虑哪些类型作为字典的键值类型
- 求哈希和判等操作越快,越适合作为字典的键值类型
- 宽度越小的类型,哈希操作越快,宽度是指数据类型的占据字节大小
- 不建议使用高级数据类型作为键值类型,容易存在变数,比如数组,值改动了,那么就不再是同一个数组
- 对于值为nil 的字典类型,除了新增键值对,其余操作都不会报错
四、List 和 Ring
- List:
- 非基础数据类型,位于container包下
- 双链循环链表
- 其中元素为Element
- 不允许将外部的 Element 加入到List中
- MoveBefore()、MoveAfter()分别用于把给定的元素移动到另一个元素的前面和后面
- MoveToFront()、MoveToBack() 分别把一个元素移动到链表的最前端和最后端
- Front()、Back() 获取链表最前端和最后端的元素
- InsertBefore()、InsertAfter() 分别在指定元素前面和后面插入一个元素
- PushFront()、PushBack() 分别在链表最前端和最后端加入一个新元素
- List的延迟初始化
- 将初始化分散在实际使用时初始化,可以分散初始化需要的计算量和存储空间消耗
- Ring 和 List 的区别
- 自身的数据结构不同,Ring自己就可以表示,List需要借助 Element 表示
- 一个Ring类型的值只是Ring链表中的一个值,一个List类型的值是一个完整链表
- 初始化的不同,Ring初始化可以指定其中元素,List初始化不能这么做
- 零值不同,Ring的零值是一个长度为1的链表,List的零值是一个长度为0的链表
- Ring的Len() 方法算法复杂度为 O(n),List 的 Len() 方法的算法复杂度为 O(1)
五、通道
- channel:通道
- 通过多个通道可以在多个goroutine通信
- 并发安全
- 一个通道相当于一个先进先出的队列
- channel 通道发送和接收的特性
- 通道的发送操作是互斥的、接收操作也是互斥的
- 发送操作和接收操作对于元素值的操作是不可分割的
- 发送操作在完全完成之前通道会被阻塞,接收操作也是如此
- 通道中存储的都是放入元素的副本,所以在接收元素的时候有两步,第一步复制元素,第二步将副本放入通道
- 通道在移除元素的时候也有两步,第一步将元素的副本给接收方,第二步删除这个元素副本
- 发送操作和接收操作什么时候会被长时间阻塞
- 当channel中已经满了,发送操作就会长时间阻塞,直到有元素被移除(对于缓冲通道)
- 单channel 空了,接收操作会长时间阻塞,直到有新的元素进来(对于缓冲通道)
- 对于分缓冲通道,它的发送操作和接收操作是同步进行的,一旦操作开始就会阻塞,直到接收结束
- 对于值为 nil 的通道,发送操作盒接收操作会处于永久的阻塞状态
- 发送操作和接收操作什么时候会引发panic
- 一个通道关闭,再进行发送操作会引发panic
- 关闭一个已经关闭了的通道也会引发panic
- 获取通道中的值时会返回两个结果,一个是通道中的元素值,一个是通道是否关闭,但是对于一个通道中还有元素的已关闭通道来说,
还能获取到值,而且第二个bool值为true,这说明通过这种方式来判断通道是否关闭有延迟性 - 关闭通道的操作应该由发送方来进行,而不是接收方
- 通道分类
- 单向通道类型:仅支持接收、仅支持发送
- 双向通道:既支持接收也支持发送
- 单向通道的作用更多是用来约束其他代码的行为
- go 语言会把双向通道处理成单向通道,当参数是单向通道时
六、select
- select 语句
- 只能与 chan 通道进行联用,格式与 switch 类似
- select 语句执行规则
- 如果select语句中有默认分支,那么select语句不会阻塞,当其他分支语句阻塞时,会执行默认分支
- 如果没哟默认分支,那么当其他分支阻塞时,select 语句就会阻塞,只有满足某一个分支满足条件
- select 语句接收通道中的值时,最好用两个参数来接收,以此来及时知道通道是否关闭,然后做出应对
- select 语句中 case 分支中的表达式只会执行一次,如果需要重复执行,需要加入 for 循环
- select 分支选择规则
- 对于每一个 case 分支,至少都有一个接受操作或者发送操作,包含的表达式会从左到右被执行
- 所有的候选分支 case 表达值的求值顺讯是从上到下的顺序求值的
- 当一个 case 分支中的接收或者发送表达式处于阻塞状态时,那么可以认为这个表达式求值失败,这个 case 分支不满足条件
- select 语句选择候选分支是在所有候选分支表达式求值完毕之后进行的,当所有候选分支不满足条件时,如果有默认分支就执行默认分支,没有默认分支则select状态处于阻塞状态
- 如果select 语句中有多个候选分支满足条件,那么会通过一种伪随机算法从这种候选分支中选择一个进行执行
- 一个select 语句只能有一个默认分支,并且只在没有候选分支满足条件时执行
- select 语句的执行以及 候选分支的选择都是独立的,但是select 语句是否并发安全取决于 case 分支中的代码是否并发安全