基础
关键字
| 25 | 个 | 关 | 键 | 字 |
|---|---|---|---|---|
| package | import | const | var | type |
| 包 | 导入包 | 常量 | 定义变量 | 定义类型 |
| map | struct | interface | func | go |
| map类型 | 定义结构体 | 定义接口 | 函数定义 | 并发执行 |
| if | else | goto | select | chan |
| 选择结构 | 选择结构 | 跳转语句 | go特有的channel选择结构 | 定义channel |
| switch | case | break | default | fallthrough |
| 选择结构 | 选择结构标签 | 跳出循环 | 选择结构的默认选项 switch、select | 开启穿透能力 |
| for | range | continue | return | defer |
| 循环 | 从slice、map等结构中取元素 | 跳过本次循环 | 返回 | 延迟执行 |
数据类型
| 分类 | 数据类型 | 占用字节 | 比特位 | 最大值 | 用途 |
|---|---|---|---|---|---|
| 有符号整型 | int | C | D | E | F |
| 无符号整型 | uint | C | D | E | F |
| 单精度浮点型 | float32 | C | D | E | F |
| 单精度浮点型 | float64 | C | D | E | F |
| ACSII字符 | byte | 1 | 8 | 255 | 表示 ACSII 表中的一个字符,和 uint8 类型本质上没有区别。不支持中文 |
| Unicode字符 | rune | 3 | 32 | 表示一个 Unicode字符,和 int32 本质上也没有区别。类型本质上没有区别。支持中文 | |
| 字符串 | B | C | D | E | 本质是一个 byte 数组, uft-8 编码。英文字母占 1 个字节,中文字符占用 3 个字节 |
| 数组 | array | C | D | E | 数组的长度是固定的,所以在Go语言中很少直接使用数组 声明是需要指定长度 |
| 切片 | Slice | C | D | E | 切片是对数组的一个连续片段【左闭右开】的引用(可以容纳若干类型相同的元素的容器) 无法确定其值的长度,声明时可不指定长度 切片的第三个数,影响的只是切片的容量,而不会影响长度 |
| 字典 | map | C | D | E | 由若干个 key:value 这样的键值对映射组合在一起的数据结构,它是哈希表的一个实现,key唯一 key 不能是切片,不能是字典,不能是函数 在声明字典时,必须指定好你的key和value是什么类型 |
| 布尔类型 | boot | C | D | E | true 和 false |
| 指针 | pointer(ptr) | C | D | E | 内存地址 |
- 为uint8 和 uint32 ,直观上让人以为这是一个数值,但是实际上,它也可以表示一个字符,所以为了消除这种直观错觉,就诞生了 byte 和 rune 这两个别名类型。
- Unicode是一个可以表示世界范围内的绝大部分字符的编码规范
- 一个切片具备的三个要素:类型(Type),长度(size),容量(cap)
- 切片、指针 都是 引用类型
int 与 uint(无符号数)
- 当你在32位的系统下,int 和 uint 都占用 4个字节,也就是32位。
-
若你在64位的系统下,int 和 uint 都占用 8个字节,也就是64位。
- 2进制:以 0b 或 0B 为前缀
- 8进制:以 0o 或者 0O 为前缀
- 16进制:以 0x 为前缀
浮点数
科学计数法: aEb = a × 10b
| 浮点数 | 占用字节 | 位数 | 符号位 | 指数位 | 尾数位 | 最小值 | 精度 | 最大值 |
|---|---|---|---|---|---|---|---|---|
| float32 | 4 | 32 | 1 | 8 | 23 | 2-23 ≈ 1.19*10-7 | 6 | math.MaxFloat32 ≈ 3.4e38 |
| float64 | 8 | 64 | 1 | 11 | 52 | 2-52 ≈ 2.22*10-16 | 15 | math.MaxFloat64 ≈ 3.4e308 |
- 在Go语言里,浮点数的相关部分只能由10进制表示法表示
- 精度问题:
package main
import "fmt"
var myfloat01 float32 = 100000182
var myfloat02 float32 = 100000187
var myfloat03 float32 = 10000018
var myfloat04 float32 = 10000023
func main() {
fmt.Println("myfloat01: ", myfloat01)
fmt.Println("myfloat02: ", myfloat02)
// 因为 float32 只有6位精度,只保证小数点后第7位计算结果精确,第8位开始回不精确
fmt.Println(myfloat02 == myfloat01+5)
fmt.Println("myfloat03 = ", myfloat03)
fmt.Println("myfloat04 = ", myfloat04)
fmt.Println(myfloat04 == myfloat04+5)
}
// 输出结果
// myfloat01 = 1.00000184e+08
// myfloat02 = 1.00000184e+08
// false
// myfloat03 = 1.0000018e+07
// myfloat04 = 1.0000023e+07
// true
语法
Tip
强类型语言 true、false 和 0、1 不能直接比较
变量/常量都只能声明一次,声明多次,编译就会报错
单引号 双引号 不是等价的:单引号用来表示字符,双引号用来表示字符串 string
使用反引 `` 号包裹的字符串会忽略里面的转义,双引号不会
go支持定义一个类型字面量,也就是别名类型。
witch-case 是顺序执行的,select-case不是顺序执行的,都要写default
- 变量声明:var
- 变量赋值:name := "value" 等价于 var name string = "value" // 这里要一定要使用双引号,表示字符串,而在单引号表示 rune 类型的字符
- 声明和初始化多个变量:name, age := "wangbm", 28
- 交换变量:b, a = a, b 【相同类型的变量才能可以交换】
- new 函数声明一个指针变量(存放 数据的地址):var ptr = &name 等价于 ptr := new(name), 给指针赋值:*ptr = "字符串",未初始化的指针,其值是 nil
-
匿名变量,也称作占位符,或者空白标识符,用下划线表示。优点有三:
- 不分配内存,不占用内存空间
- 不需要你为命名无用的变量名而纠结
- 多次声明不会有任何问题
- 例:a, _ := GetData()
循环
// Go是 强类型,所以要求你条件表达式必须严格返回布尔型的数据【nil 和 0 和 1 都不行】
if (age > 18 && gender == "male") {
分支 1
// if 里可以允许先运行一个表达式,取得变量后
} else if age := 20;age > 18 {
fmt.Println("已经成年了")
// else if (或 else)和 两边的花括号,必须在同一行
} else {
分支 else
}
switch month {
// case 后可以接多个多个条件,多个条件之间是 或 的关系,用逗号相隔。
case 3, 4, 5:
fmt.Println("春天")
// case 条件常量不能重复,否则 在编译时会报错: duplicate case "value" in switch
case 6, 7, 8:
fmt.Println("夏天")
case 9, 10, 11:
fmt.Println("秋天")
case 12, 1, 2:
fmt.Println("冬天")
default:
fmt.Println("输入有误...")
}
// switch 可不接表达式, 就相当于 if - elseif - else
score := 30
switch {
case score >= 95 && score <= 100:
fmt.Println("优秀")
// case 使用关键字 fallthrough 开启穿透能力
fallthrough
case score >= 80:
fmt.Println("良好")
case score >= 60:
fmt.Println("合格")
case score >= 0:
fmt.Println("不合格")
default:
fmt.Println("输入有误...")
}
// 1 一个表达式:a := 1 for a <= 5 {do sth}
// 2 for i := 1; i <= 5; i++ {do sth}
// 3 for-range 遍历一个可迭代的对象 for key, value := range myarr {do sth}
// 4 无限循环
for [一个表达式 | ( init; condition; increment ) | Range表达式 | 不接表达式]
{
statement(s);
}
// goto 可以打破原有代码执行顺序,直接跳转到某一行执行代码
// 通常与条件语句配合使用。可用来实现条件转移, 构成循环,跳出循环体等功能
// goto语句与标签之间不能有变量声明,否则编译错误
if condition {
do sth A
goto flag1;
do sth B
}
flag1: 表达式;
do sth C
延迟调用 defer
// defer 能实现将这个 xxx 函数的调用延迟到当前函数执行完后再执行
func xxx() {
fmt.Println("B")
}
func main() {
defer xxx()
fmt.Println("A")
}
// 输出: A B
// 使用 defer 只是延时调用函数,此时传递给函数里的变量,不应该受到后续程序的影响
func main() {
name := "go"
defer fmt.Println(name) // 输出: go
name = "python"
fmt.Println(name) // 输出: python
}
// 输出: python go
// 如果 defer 后面跟的是匿名函数,情况会有所不同, defer 会取到最后的变量值
func main() {
name := "go"
fmt.Println(name) // 输出: go
defer func() {
fmt.Println(name) // 输出: python
}()
name = "python"
fmt.Println(name) // 输出: python
}
// 多个defer 反序调用,有点类似栈一样,后进先出。
func main() {
name := "go"
defer fmt.Println(name) // 输出: go
name = "python"
defer fmt.Println(name) // 输出: python
name = "java"
fmt.Println(name)
}
// 输出 java python go
// defer 是return 后才调用, 用途:当一个函数里有多个 return 时,你得多调用好多次这个函数,代码就臃肿起来了。
import "fmt"
var name string = "go"
func myfunc() string {
defer func() {
name = "python"
}()
fmt.Printf("myfunc 函数里的name:%s\n", name)
return name
}
func main() {
myname := myfunc()
fmt.Printf("main 函数里的name: %s\n", name)
fmt.Println("main 函数里的myname: ", myname)
}
// 输出如下
// myfunc 函数里的name:go
// main 函数里的name: python
// main 函数里的myname: go
信道/通道 select-case
跟 switch-case 相比,select-case 用法比较单一,它仅能用于 信道/通道 的相关操作。
package main
import (
"fmt"
)
func main() {
c1 := make(chan string, 1)
c2 := make(chan string, 1)
c2 <- "hello"
// 在运行 select 时,会遍历所有(如果有机会的话)的 case 表达式,只要有一个信道有接收到数据,那么 select 就结束,所以输出如下
select {
case msg1 := <-c1:
fmt.Println("c1 received: ", msg1)
case msg2 := <-c2:
fmt.Println("c2 received: ", msg2)
// 不写default 可能导致死锁
default:
fmt.Println("No data received.")
}
}
select VS switch
- select 只能用于 channel 的操作(写入/读出/关闭),而 switch 则更通用一些;
- select 的 case 是随机的,而 switch 里的 case 是顺序执行;
- select 要注意避免出现死锁,同时也可以自行实现超时机制;
- select 里没有类似 switch 里的 fallthrough 的用法;
- select 不能像 switch 一样接函数或其他表达式。
异常机制:panic 和 recover
panic:抛出异常,使程序崩溃
- 手动触发宕机,只需要调用 panic 这个内置函数即可 ```go package main
func main() { panic("crash") }
### recover:捕获异常,恢复程序或做收尾工作
recover 它可以让程序在发生宕机后起生回生。必须在 defer 函数中才能生效,其他作用域下,它是不工作的。
* 子协程里触发 panic,只能触发自己协程内的 defer,而不能调用 main 协程里的 defer 函数的
```go
import "fmt"
func set_data(x int) {
defer func() {
// recover() 可以将捕获到的panic信息打印
if err := recover(); err != nil {
fmt.Println(err)
}
}()
// 故意制造数组越界,触发 panic
var arr [10]int
arr[x] = 88
}
func main() {
set_data(20)
// 如果能执行到这句,说明panic被捕获了
// 后续的程序能继续运行
fmt.Println("everything is ok")
}
语句块
- Go 使用的是词法作用域,而词法作用域依赖于语句块
- 语句块内部声明的名字是无法被外部块访问的 —— 作用域
- 显式语句块是由花括弧 {} 所包含的一系列语句。
-
隐式语句块:
- 主语句块:包括所有源码,对应内置作用域
- 包语句块:包括该包中所有的源码(一个包可能会包括一个目录下的多个文件),对应包级作用域
- 文件语句块:包括该文件中的所有源码,对应文件级作用域
- for 、if、switch等语句本身也在它自身的隐式语句块中,对应局部作用域
