Go基本语法

Go基本语法

程序入口

  1. 必须是main包 package main
  2. 必须是main方法 func main()
  3. 文件名不一定是main.go
  4. 文件夹名不一定是main

获取返回值

Go语言的main函数不支持任何返回值 因此return是不可以使用的
通过使用os.Exit()来返回状态

1
os.Exit(0)

获取命令行参数

Go语言的main函数不支持传入参数 在程序中直接通过os.Args获取命令行参数

1
2
3
4
5
func main() {
if len(as.Args) > 1 {
fmt.Println("It's", os.Args[1])
}
}

声明变量

1
2
3
4
5
6
7
8
//直接声明
var name string = "Ruojhen"
var id int = 123
var name = "shaoyuanhang@outlook.com" //不指定类型声明 靠编译器自动识别
/*但编译器自动识别会产生我们不需要的效果 例如: */
var decimal = 0.06 //因为右值带有小数点 所以在不指定类型的情况下 编译器将这个变量声明为float64 但很多时候我们不需要这么高的精度 因为高精度意味着占用内存空间就更大
var decimal float32 = 0.03
/*以上声明方式可以不赋予变量值 这样变量就会被初始化为初值 string就是空字符串 int就是0 float32/float64就是0.0 bool就是false 指针就是nil*/
1
2
3
4
5
6
7
8
9
//多个变量一起声明 只能用于函数内部
var(
id int =132456
name = "syh"
gender = "男"
)
fmt.Print(id,name,gender)

//打印结果为 132456syh男
1
2
3
4
5
6
7
8
9
//快速初始化变量
name:="syh" /*效果等价于*/ var name = "syh" var name string = "syh"

id,name:= 123,"syh" //相当于将123赋值给id "syh"赋值给name
/*这个方法还能用于交换两个数*/
var a int =100
var b int =200
b,a=a,b
fmt.Print(a," ",b)//输出结果为 200 100
1
2
3
4
5
6
7
8
9
10
11
12
13
//声明指针变量
var age int =100
var address =&age
fmt.Print(age," ",address)//输出结果为 100 0xc00000a0c8

//使用new函数声明匿名变量
ptr:=new(int)
fmt.Println("ptr address",ptr) //ptr address 0xc0000a0090
fmt.Println("ptr value",*ptr) //ptr value 0

/*所谓匿名变量 又称作占位符 空白标识符 在代码中我们使用下划线代替他
表示一些我们在函数返回值中必须要接收 但以后却用不到的值 匿名变量优点
: Ⅰ不分配内存空间 Ⅱ不需要为命名无用的变量名纠结 Ⅲ多次声明不会有任何问题

环境变量

三个环境变量 GOROOT GOPATH GOBIN
GOROOT Go语言的安装路径
GOPATH 若干工作区目录的路径 是自己定义的工作空间
GOBIN Go程序生成的可执行文件的路径

其中 GOPATH的概念最多 GOPATH的意义也是被询问的最多的

回答是: 可以把GOPATH简单理解成Go语言的工作目录 GOPATH的值是一个目录的路径
每个目录都代表Go语言的一个工作区
利用工作区下的文件夹src去存放源码文件(.go)
pkg存放平台相关目录(linux_amd64)内存放归档文件(
.a)
bin存可执行文件(**.exe)

1
2
归档文件就是在linux下是.a的文件 是archive文件 是程序编译后生成的静态库文件
与Java的jar包不同 jar属于动态链接库

Go命令

1
2
3
go build   #编译程序并生成二进制可执行文件 但不会运行程序
go run #编译并运行程序 但不会生成二进制可执行文件
go install #编译应用 生成二进制文件 并将其移动到$GOBIN目录中 也不会运行程序

数据类型

fmt输出格式

1
2
3
4
5
6
7
8
9
10
%b    表示为二进制
%c 该值对应的unicode码值
%d 表示为十进制
%o 表示为八进制
%q 该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示
%x 表示为十六进制,使用a-f
%X 表示为十六进制,使用A-F
%U 表示为Unicode格式:U+1234,等价于"U+%04X"
%E 用科学计数法表示
%f 用浮点数表示

整型

int int8 int16 int32 int64 有符号整型
uint uint8 uint16 uint32 uint64 无符号整型

不同进制整型表示

十进制

1
var num int = 10

二进制 0b前缀

1
var num01 int = 0b1100

八进制 0o前缀

1
var num02 int = 0o14

十六进制

1
var num03 int = 0xC

输出格式对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
)

func main(){
var num01 int = 0b1100
var num02 int = 0o14
var num03 int = 0xC

fmt.Printf("2进制数 %b 表示的是: %d \n", num01, num01)
fmt.Printf("8进制数 %o 表示的是: %d \n", num02, num02)
fmt.Printf("16进制数 %X 表示的是: %d \n", num03, num03)
}

浮点型

浮点型表示有两种表示方法 以0.037为例 可以直接用0.037 亦或使用3.7E-2表示0.037 同理3.7E+1 = 37
有时候浮点型类型值可以被简化 37.0可以简化为37 0.037可以简化为.037
浮点数的整数部分和小数部分只能由十进制表示

Go提供了两种精度的浮点数float32 float64

float32 单精度 存储占用4个字节 32位
float64 双精度 存储占用8个字节 64位

byte/rune

byte

byte 占用一个字节 8个比特位 2^8=256 所以byte类型的范围是 0-255 和uint8本质上没有区别 byte表示的是ACSII表中的一个字符

1
2
3
4
5
6
7
8
9
10
11
12
13
import "fmt"

func main() {
var a byte = 65
// 8进制写法: var a byte = '\101' 其中 \ 是固定前缀
// 16进制写法: var a byte = '\x41' 其中 \x 是固定前缀

var b uint8 = 66
fmt.Printf("a 的值: %c \nb 的值: %c", a, b)

// 或者使用 string 函数
// fmt.Println("a 的值: ", string(a)," \nb 的值: ", string(b))
}

输出结果为

1
2
a 的值: A
b 的值: B

在 ASCII 表中,由于字母 A 的ASCII 的编号为 65 字母 B 的ASCII 编号为 66 所以上面的代码也可以写成这样

1
2
3
4
5
6
7
import "fmt"

func main() {
var a byte = 'A'
var b uint8 = 'B'
fmt.Printf("a 的值: %c \nb 的值: %c", a, b)
}

rune

rune 占用四个字节 32个比特位 所以和uint32本质上没有区别 rune表示的是一个Unicode字符

1
2
3
4
5
6
7
8
9
10
import (
"fmt"
"unsafe"
)

func main() {
var a byte = 'A'
var b rune = 'B'
fmt.Printf("a 占用 %d 个字节数\nb 占用 %d 个字节数", unsafe.Sizeof(a), unsafe.Sizeof(b))
}

输出结果为

1
2
a 占用 1 个字节数
b 占用 4 个字节数

表示中文字符要用rune类型

1
var name rune = '简'

既然 byteuint8 没有区别 runeuint32 没有区别 那为何还要多设置这两个类型呢

因为uint8 uint32 直观上让人理解为这是一个整型变量 是一个数值 但实际上它也可以表示一个字符 为了消除这个直观错觉
应运而生了byte rune

字符串

1
var mystr string = "hello"

byterune都是字符类型 多个字符放在一起就组成了字符串 即string

hello在ASCII表中对应的数字分别是 104 101 108 108 111

1
2
3
4
5
6
7
8
9
10
import (
"fmt"
)

func main() {
var mystr01 string = "hello"
var mystr02 [5]byte = [5]byte{104, 101, 108, 108, 111}
fmt.Printf("mystr01: %s\n", mystr01)
fmt.Printf("mystr02: %s", mystr02)
}

输出结果为

1
2
mystr01: hello
mystr02: hello

mystr01 mystr02输出内容一样 说明string的本质是byte数组

Go语言的string使用utf-8进行编码 英文字母占1个字节 中文汉字占3个字节

字符串不仅仅可以使用双引号来表示 还可以使用反引号 反引号通常应用于存在转移字符\的情况下

被反引号包裹的字符串会被忽略掉其中的转义字符

1
2
var mystr01 string = "\\r\\n"
var mystr02 string = `\r\n`

这两个字符串的打印结果都是\r\n

1
2
3
4
5
6
7
8
9
import (
"fmt"
)

func main() {
var mystr01 string = `\r\n`
fmt.Printf(`\r\n`)
fmt.Printf("的解释型字符串是: %q", mystr01)
}

输出结果是

1
\r\n的解释型字符串是: "\\r\\n"

同时反引号可以不写换行符\n来表示一个多行的字符串 因为换行符会被忽略掉

1
2
3
4
5
6
7
8
9
10
import (
"fmt"
)

func main() {
var mystr01 string = `Hello
Shaoyuanhangyes Ruojhen`

fmt.Println(mystr01)
}

输出结果

1
2
Hello
Shaoyuanhangyes Ruojhen

数组

数组声明

1
2
3
4
var arr [3]int
arr[0] = 1
arr[1] = 2
arr[2] = 3
1
2
3
4
5
//第一种方式
var arr [3] = [3]int{1,2,3}

//第二种方式
arr := [3]int{1,2,3},

3表示数组元素个数 可以使用...来让系统自己根据实际情况来分配空间

1
arr := [...]int{1,2,3}

值得注意的是 [3]int [4]int虽然都是数组 但类型却不同 使用fmt的%T来查得

1
2
3
4
5
6
7
8
9
10
import (
"fmt"
)

func main() {
arr01 := [...]int{1, 2, 3}
arr02 := [...]int{1, 2, 3, 4}
fmt.Printf("%d 的类型是: %T\n", arr01, arr01)
fmt.Printf("%d 的类型是: %T", arr02, arr02)
}

输出结果为

1
2
[1 2 3] 的类型是: [3]int
[1 2 3 4] 的类型是: [4]int

type

使用关键字type来起别名

1
2
3
4
5
6
7
8
9
10
import (
"fmt"
)

func main() {
type arr3 [3]int

myarr := arr3{1,2,3}
fmt.Printf("%d 的类型是: %T", myarr, myarr)
}

输出结果为

1
[1 2 3] 的类型是: main.arr3

只初始化部分位置

1
arr := [4]int{2:3} //表示的数组内有4个元素 索引为2的位置的值为3 其余未指定初值的为0

打印arr的结果为 [0 0 3 0]

切片

切片是对数组的一个连续片段的引用 所以切片是一个引用类型 这个片段可以是整个数组 也可以是数组的子集 但这个片段是一个左闭右开的区间

即终止索引对应的项不在这个片段中

1
2
3
4
5
6
7
8
import (
"fmt"
)

func main() {
myarr := [...]int{1, 2, 3}
fmt.Printf("%d 的类型是: %T", myarr[0:2], myarr[0:2])
}

输出结果为

1
[1 2] 的类型是: []int

切片是引用类型 所以未给予初值的切片的默认值是nil

切片构造方式

对数组进行片段截取
1
2
3
4
5
myarr := [5]int{1,2,3,4,5}

mysli1 := myarr[1:3] //表示从索引1-索引2的元素

mysli2 := myarr[1:3:4]//表示从索引1-索引2的元素

mysli1 mysli2打印结果一模一样 其中mysli2中的4是做什么的呢

在切片时 若不指定第三个数 那么切片的终止索引会一直到原数组的最后一个数 若指定了第三个数 那么切片的终止索引就到这第三个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

func main(){
myarr := [5]int{1,2,3,4,5}
fmt.Printf("myarr 的长度为:%d,容量为:%d\n", len(myarr), cap(myarr))

mysli1 := myarr[1:3]
fmt.Printf("mysli1 的长度为:%d,容量为:%d\n", len(mysli1), cap(mysli1))
fmt.Println(mysli1)

mysli2 := myarr[1:3:4]
fmt.Printf("mysli2 的长度为:%d,容量为:%d\n", len(mysli2), cap(mysli2))
fmt.Println(mysli2)
}

输出结果为

1
2
3
4
5
myarr 的长度为:5,容量为:5
mysli1 的长度为:2,容量为:4
[2 3]
mysli2 的长度为:2,容量为:3
[2 3]

所以切片的第三个数只影响切片的容量 不影响切片的长度

从头声明赋值
1
2
3
4
5
var strList[]string // 声明字符串切片

var numList[]int // 声明整型切片

var numListEmpty = []int{} // 声明一个空切片

可以看到 声明切片类型和声明数组类型基本上是一模一样 除了不需要说明长度

make函数构造切片

make([]Type,size,cap) Type类型 size长度 cap容量

1
2
3
4
5
6
7
8
9
10
11
import (
"fmt"
)

func main() {
a := make([]int, 2)
b := make([]int, 2, 10)
fmt.Println(a, b)
fmt.Println(len(a), len(b))
fmt.Println(cap(a), cap(b))
}

输出结果为

1
2
3
[0 0] [0 0]
2 2
2 10

因此使用make函数构造切片的时候不指定容量的时候 容量等于长度值

只初始化部分位置

和数组一样

1
2
3
4
5
6
7
8
9
import (
"fmt"
)

func main() {
a := []int{4:2}
fmt.Println(a)
fmt.Println(len(a), cap(a))
}

输出结果为

1
2
[0 0 0 0 2]
5 5

数组与切片

数组与切片都是可容纳若干类型相同元素的容器
不同点在于 数组容器大小固定 而切片本身是引用类型 可以append进行元素添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import (
"fmt"
)

func main() {
myarr := []int{1}
// 追加一个元素
myarr = append(myarr, 2)
// 追加多个元素
myarr = append(myarr, 3, 4)
// 追加一个切片, ... 表示解包,不能省略
myarr = append(myarr, []int{7, 8}...)
// 在第一个位置插入元素
myarr = append([]int{0}, myarr...)
// 在中间插入一个切片(两个元素)
myarr = append(myarr[:5], append([]int{5,6}, myarr[5:]...)...)
//先计算内层append 拼接完成后再计算外层append

fmt.Println(myarr)
}

输出结果为

1
[0 1 2 3 4 5 6 7 8]

可以如此理解数组与切片的区别
切片是引用类型 数组是值类型
数组长度固定 切片是动态的数组
切片比数组多一个属性 容量cap
切片的底层实现是数组
切片不能进行等值判断 只能和nil判断

字典

字典 Map 若干个key:value组建在一起的无序键值对
要求每一个key都是唯一的 可以使用== !=来进行判断 key不能是切片 不能是字典 不能是函数
map是一个集合 可以迭代它 不过map是无序的 无法决定他的返回顺序 因为map是使用hash表实现的
声明map时必须指定好key和value的类型

声明初始化字典

1
2
3
4
5
6
7
8
//第一种方式
var scores map[string]int = map[string]int{"English":80,"Chinese":90}
//第二种方式
scores := map[string]int{"English":80,"Chinese":90}
//第三种方式 使用make函数
scores := make(map[string]int)
scores["English"] = 80
scores["Chinese"] = 90

需要注意的是 只声明不初始化的map类型的初值为nil 无法直接进行赋值 所以需要使用make函数先对其初始化 然后才能直接赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import "fmt"

func main(){
// 声明一个名为 score 的字典
var scores map[string]int

// 未初始化的 score 的零值为nil,无法直接进行赋值
if scores == nil {
// 需要使用 make 函数先对其初始化
scores = make(map[string]int)
}

// 经过初始化后,就可以直接赋值
scores["chinese"] = 90
fmt.Println(scores)
}

打印结果为 map[chinese:90]

对字典的操作

1
2
3
4
5
6
7
scores := make(map[string]int)
//更新元素
scores["math"] = 150
//读取元素 若key不存在 也不会报错 而是返回value的零值
fmt.Print(scores["math"])
//删除元素 若key不存在 也不会报错
delete(scores,"math")

判断key是否存在

因为有可能key对应的value恰好为零值 所以想要判断key是否存在不能够使用判断value零值的办法

字典的下标读取会返回两个值 可以使用第二个返回值来表示对应的key是否存在 若存在 ok则为true 若不存在 ok为false

1
2
3
4
5
6
7
8
9
10
import "fmt"

func main() {
scores := map[string]int{"english": 80, "chinese": 85}
if math, ok := scores["math"]; ok {
fmt.Printf("math 的值是: %d", math)
} else {
fmt.Println("math 不存在")
}
}

对字典进行循环

1
2
3
4
5
6
7
8
9
import "fmt"
//获取key和value
func main() {
scores := map[string]int{"english": 80, "chinese": 85}

for subject, score := range scores {
fmt.Printf("key: %s, value: %d\n", subject, score)
}
}
1
2
3
4
5
6
7
8
9
import "fmt"
//只获取key 不需要占位符
func main() {
scores := map[string]int{"english": 80, "chinese": 85}

for subject := range scores {
fmt.Printf("key: %s\n", subject)
}
}
1
2
3
4
5
6
7
8
9
import "fmt"
//只获取value 需要使用占位符替代key
func main() {
scores := map[string]int{"english": 80, "chinese": 85}

for _, score := range scores {
fmt.Printf("value: %d\n", score)
}
}

布尔类型

与其他语言不同的一点 go的true不等于1 false不等于0 即 boolint不能直接转换 需要自己实现转换函数

1
2
3
4
5
6
7
//bool转int
func bool2int(b bool) int {
if b {
return 1
}
return 0
}
1
2
3
4
//in转bool
func int2bool(i int) bool {
return i != 0
}

指针

创建指针

1
2
3
4
5
6
7
8
9
10
11
12
//第一种
aint :=1 //定义普通变量
ptr1 := &aint //定义指针变量

//第二种
astr := new(string) //创建指针
*astr ="Ruojhen" //给指针赋值

//第三种
aint := 1
var bint *int
bint = &aint

以防混淆

1
2
3
4
5
6
7
8
9
10
import "fmt"

func main() {
aint := 1 // 定义普通变量
ptr := &aint // 定义指针变量
fmt.Println("普通变量存储的是:", aint)
fmt.Println("普通变量存储的是:", *ptr)
fmt.Println("指针变量存储的是:", &aint)
fmt.Println("指针变量存储的是:", ptr)
}

打印结果为

1
2
3
4
普通变量存储的是: 1
普通变量存储的是: 1
指针变量存储的是: 0xc0000100a0
指针变量存储的是: 0xc0000100a0

打印指针指向的内存地址

1
2
3
4
5
// 第一种
fmt.Printf("%p", ptr)

// 第二种
fmt.Println(ptr)

指针的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import "fmt"

func main() {
astr := "hello"
aint := 1
abool := false
arune := 'a'
afloat := 1.2

fmt.Printf("astr 指针类型是:%T\n", &astr)
fmt.Printf("aint 指针类型是:%T\n", &aint)
fmt.Printf("abool 指针类型是:%T\n", &abool)
fmt.Printf("arune 指针类型是:%T\n", &arune)
fmt.Printf("afloat 指针类型是:%T\n", &afloat)
}

打印结果为

1
2
3
4
5
astr 指针类型是:*string
aint 指针类型是:*int
abool 指针类型是:*bool
arune 指针类型是:*int32
afloat 指针类型是:*float64

可以发现用 *+所指向变量值的数据类型就是对应的指针类型

指针的初始值

指针声明后不初始化赋值的话 指针的值就会是nil

切片与指针

指针和切片一样都是引用类型

若想通过一个函数改变一个数组内的值 有两种方式

1.将数组的切片作为参数传给函数
2.将数组的指针作为参数传给函数

按照go的语言习惯 使用切片将会更加简洁

1
2
3
4
5
6
7
8
9
10
//使用切片
func modify(sls []int) {
sls[0] = 90
}

func main() {
a := [3]int{89, 90, 91}
modify(a[:])
fmt.Println(a)
}
1
2
3
4
5
6
7
8
9
10
//使用指针
func modify(arr *[3]int) {
(*arr)[0] = 90
}

func main() {
a := [3]int{89, 90, 91}
modify(&a)
fmt.Println(a)
}

go指针限制

相较于cpp的指针 go指针有着许多限制 目的是避免指针操作的危险

限制一 指针不能参与数学运算

限制二 不同类型的指针不允许相互转换

限制三 不同类型的指针不能比较和相互赋值

流程控制

For循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//接一个表达式
a := 1
for a<=5 {
fmt.Println(a)
a++
}
//接三个表达式
for i := 1; i <= 5;i++ {
fmt.Println(i)
}
//不接任何表达式 为无限循环

for {

}

for ;; {

}

for range

遍历一个可迭代的数据结构用for range语句实现 range后可接数组 切片 字符串等

按照以往的编程习惯 随便写一个循环

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
)

func main(){
vector := [...]string{"C++","Go","Git","Redis"}
for i := range vector {
fmt.Println("I will learn ",i)
}
}

输出结果为

1
2
3
4
I will learn  0
I will learn 1
I will learn 2
I will learn 3

发现与我们期望的不符 做一些小更改

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
)

func main(){
vector := [...]string{"C++","Go","Git","Redis"}
for _, i := range vector {
fmt.Println("I will learn ",i)
}
}

输出结果为

1
2
3
4
I will learn  C++
I will learn Go
I will learn Git
I will learn Redis

这是因为range返回两个值 第一个是索引 第二个是数据

defer 延迟语句

defer后接函数名 就能实现将这个函数的调用延迟到当前函数执行完再调用

理解defer的使用效果

1
2
3
4
5
6
7
func syhprint(){
fmt.Println("B")
}
func main(){
defer syhprint()
fmt.Println("A")
}

输出结果为

1
2
A
B

代码也可以简写为

1
2
3
4
func main(){
defer fmt.Println("B")
fmt.Println("A")
}

defer的快照作用

使用defer只是延时调用了函数 此时已经传递给函数的变量 就像一个快照一样被保存了下来 不会受到后续程序执行的影响

1
2
3
4
5
6
func main(){
language := "C++"
defer fmt.Println(language)
language = "Go"
fmt.Println(language)
}

输出结果为

1
2
Go
C++

由此可见 在变量被修改前延时调用打印函数 即使变量值被修改了 最后延迟调用的函数的变量值依旧是修改前的值 就像是defer给这个函数的变量值做了一个快照

多个defer调用的顺序

1
2
3
4
5
6
7
8
func main(){
language := "C++"
defer fmt.Println(language)
language = "Go"
defer fmt.Println(language)
language = "Rust"
fmt.Println(language)
}

输出结果为

1
2
3
Rust
Go
C++

由此可见 多个defer一起调用的时候 输出的结果是反序的 类似栈的后进先出

defer/return

defer和return同时存在的话 先调用谁呢 看下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import (
"fmt"
)

var name string = "C++"

func rename() string {
defer func() {
name = "Go"
}()
fmt.Println("rename函数内的name为",name)
return name
}

func main(){
myname := rename()
fmt.Println("main函数里的name为",name)
fmt.Println("main函数里的myname为",myname)
}

输出结果为

1
2
3
rename函数内的name为 C++
main函数里的name为 Go
main函数里的myname为 C++

对代码进行剖析 理解为何是这个输出结果

1
2
3
4
5
6
7
8
9
10
11
在main函数里初始化myname的时候 调用了rename函数 
rename函数内输出了name值为全局变量的"C++"
同时rename函数内延迟修改了全局变量name的值为"Go"
main函数输出name值 为修改后的"Go"
main函数输出myname值 为rename函数的返回值"C++"

因此我们发现在rename函数调用完毕后 才会延迟修改全局变量name的值

所以return回的值为修改前的"C++"
因此return的调用在defer之前
毕竟要完成函数后才会继续延迟调用

为什么要有defer

能够缩减代码量 使代码不那么臃肿 下面是一段经典的代码

1
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
36
37
38
39
40
41
42
43
44
45
46
//不用defer
func f() {
r := getResource() //0,获取资源
......
if ... {
r.release() //1,释放资源
return
}
......
if ... {
r.release() //2,释放资源
return
}
......
if ... {
r.release() //3,释放资源
return
}
......
r.release() //4,释放资源
return
}

//使用defer
func f() {
r := getResource() //0,获取资源

defer r.release() //1,释放资源
......
if ... {
...
return
}
......
if ... {
...
return
}
......
if ... {
...
return
}
......
return
}

异常

Go语言中 异常的抛出和捕获依赖两个内置函数

panic 抛出异常 使程序崩溃

recover 捕获异常 恢复程序
recover调用后 抛出的panic将会在此处终结 不会再向外抛出异常
并且recover必须在defer修饰下才能发挥作用

panic

手动抛出异常

1
2
3
func main(){
panic("程序出错了")
}

控制台输出效果为

1
2
3
4
5
6
panic: 程序出错了

goroutine 1 [running]:
main.main()
d:/Code/My Code/Golang/Hexomd/src/github.com/Ruojhen/hexomd01/test.go:8 +0x40
exit status 2

recover

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func data(x int) {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
//制造数组越界 出发panic
var arr [10]int
arr[x] = 25
}

func main() {
data(20) //数组越界 抛出异常 程序应该终止
//若程序执行到这里i说明panic被捕获
fmt.Println("It is fine")
}
```
控制台输出结果为

```bash
runtime error: index out of range [20] with length 10
It is fine
End of reading! -- Thanks for your supporting