[TOC]
## golang 介绍
[官网](https://golang.org/)
[百度百科](http://baike.baidu.com/link?url=OIpBeQaglgo2VoUMWgk2JOTfK-NfnC-iHiZ0uxznAHlK1c8CZP4vq7V86s66vSdiy8wlr3ptrJOjCyL91gv6kWmlEygxH1tkXuZcBPvvt_x7X7c8-vKVpxh-Wn0br7O8dxSCvJJc5VyLSDbStB6WP_)
> Go语言于2009年11月正式宣布推出,成为开放源代码项目,并在Linux及Mac OS X平台上进行了实现,后追加Windows系统下的实现...
**注意** 在接下来的学习中请确保go环境已经安装好,以及GOPATH环境变量和IED也已经准备好。
1. 测试Go版本 `win + r -> cmd -> go version`显示版本信息表示成功。
![](https://box.kancloud.cn/9b05b7f9db7ba7355cff454176c75cd0_312x58.png)
2. 测试Go环境`win + r -> cmd -> go env`注意观察`GOPATH`
![](https://box.kancloud.cn/b6a864cd69ca382c9c4261a6726242ae_470x417.png)
3. GOPATH下最终会存在3个目录
![](https://box.kancloud.cn/35de774047ae406b8c8e98224b8c9d7e_409x131.png)
* bin(存放编译后生成的可执行文件)
* pkg(存放编译后生成的包文件)
* src(存放项目源码)
## 第一个Go程序
1. 在`GOPATH/src`目录下新建`hello.go`文件
2. 编写代码如下
~~~
package main
import (
"fmt"
)
func main() {
fmt.Println("hello , world")
}
~~~
3. 在此处打开命令窗口输入`go run hello.go`
![](https://box.kancloud.cn/f814781d18f3364947a725524188c3b7_175x21.png)
> 一个golang 版本的helloword就完成了。
## 关键字
> Go内置关键字(25个均为小写)
|1|2|3|4|5|
|--- | --- | --- | --- | --- |
|break|default|func|interface|select|
|case|defer|go|map|struct|
|chan|else|goto|package|switch|
|const|fallthrough|if|range|type|
|continue |for|import|return|var|
## Go注释方法
~~~
// :单行注释
/* */:多行注释
~~~
## 程序结构
1. Go程序是通过 `package` 来组织的(与`Java`类似)
2. 只有 `package` 名称为 `main` 的包可以包含 `main` 函数
3. 一个可执行程序**有且仅有**一个 `main` 包
4. 通过 `import` 关键字来导入其它非 `main` 包
5. 通过 `const` 关键字来进行常量的定义
6. 通过在函数体外部使用 `var` 关键字来进行全局变量的声明与赋值
7. 通过 `type` 关键字来进行定义类型的别名,结构(`struct`)或接口(`interface`)的声明
8. 通过 `func` 关键字来进行函数的声明
![](https://box.kancloud.cn/872389245bc078becb0d34302d14b7fa_354x486.png)
## 包package
### 导入package
![](https://box.kancloud.cn/e049a0573ec028bd91890a13354040cf_166x79.png)
### package 导入合并
![](https://box.kancloud.cn/0a7ca234f4401c682003ea972c589ace_159x98.png)
**注意**
* 导入包之后,就可以使用格式 `PackageName.FuncName`来对包中的函数进行调用
* 如果导入包之后未调用其中的函数或者类型将会报出编译错误:
![](https://box.kancloud.cn/2832b1621f2353b4f556ab5ca7166f47_424x60.png)
### package 别名
![](https://box.kancloud.cn/2fad40a8d000f532a3ec37fc5850261e_321x220.png)
>当使用第三方包时,包名可能会非常接近或者相同,此时就可以使用
别名来进行区别和调用
### 省略调用
![](https://box.kancloud.cn/1bf369737dce6f9a37b90cb07dd0897f_295x192.png)
![](https://box.kancloud.cn/f75106ee17520d02b94bf5daaf8201f0_300x72.png)
### 可见性规则
Go语言中,使用**大小写**来决定该 常量、变量、类型、接口、结构或函数是否可以被外部包所调用:
![](https://box.kancloud.cn/322fc0912f6895b4fadeba4dad0fd92e_250x113.png)
**注意**
* 首字母大写即为`public`
* 首字母小写即为`private`
#### 课堂作业
既然导入多个包时可以进行简写,那么声明多个 常量、全局变量
或一般类型(非接口、非结构)是否也可以用同样的方法呢?请自行尝试
~~~
//当前程序的包名称
package main
import "fmt"
// 常量
const (
ZName = "zxysilent"
ZEmail = "zxysilent@goxmail.com"
ZPhone = 18284151024
)
// 包级变量(全局变量)
var (
name = "zxysilent"
email = "zxysilent@foxmail.com"
phone = 18284151024
)
// 类型别名
type (
INT int
STR string
)
// 程序入口
func main() {
fmt.Println("hello , word")
}
~~~
## Go类型
### 基本类型
* 布尔型:bool
- 长度:1字节
- 取值范围:true, false
- 注意事项:不可以用数字代表true或false
* 整型:int/uint
- 根据运行平台可能为32或64位
* 8位整型:int8/uint8
- 长度:1字节
- 取值范围:-128~127/0~255
* 字节型:byte(uint8别名)
* 16位整型:int16/uint16
- 长度:2字节
- 取值范围:-32768~32767/0~65535
* 32位整型:int32(rune)/uint32
- 长度:4字节
- 取值范围:-2^32/2~2^32/2-1/0~2^32-1
* 64位整型:int64/uint64
- 长度:8字节
- 取值范围:-2^64/2~2^64/2-1/0~2^64-1
* 浮点型:float32/float64
- 长度:4/8字节
- 小数位:精确到7/15小数位
* 复数:complex64/complex128
- 长度:8/16字节
* 足够保存指针的 32 位或 64 位整数型:uintptr
* 其它值类型:
- array、struct、string
* 引用类型:
- slice、map、chan
* 接口类型:inteface
* 函数类型:func
### 类型零值
>零值并不等于空值,而是当变量被声明为某种类型后的默认值,
通常情况下值类型的默认值为`0`,`bool`为`false`,`string`为空字符串
## 变量&常量
### 变量
#### 单个变量的声明与赋值
* 变量的声明格式:`var <变量名称> <变量类型>`
* 变量的赋值格式:`<变量名称> = <表达式>`
* 声明的同时赋值:`var <变量名称> [变量类型] = <表达式>`
~~~
//当前程序的包名称
package main
import "fmt"
// 程序入口
func main() {
// 变量的申明
var a int
//变量的赋值
a = 123
// 变量声明的同时赋值
var b int = 234
// 类型可省略,编译器可以自行推断
var c = 345
//最简单的 申明和赋值
d := 456
fmt.Println(a, b, c, d)
}
~~~
![](https://box.kancloud.cn/22bac1898614fcbbbf79a3b2bfbcb31b_524x129.png)
#### 多个变量的声明与赋值
* 全局变量的声明可使用 `var()` 的方式进行简写
* 全局变量的声明不可以省略` var`,但可使用并行方式
* 所有变量都可以使用类型推断
* 局部变量**不**可以使用` var() `的方式简写,只能使用并行方式
> 全局
~~~
package main
var (
// 常规方式
a = "hello"
// 并行方式
c, d = 1, 2
//syntax error: unexpected :=
//e:=5
)
//syntax error: non-declaration statement outside function body
//e:=5
// 程序入口
func main() {
}
~~~
> 局部
~~~
package main
import "fmt"
func main() {
// 多个变量的声明
var a, b, c int
// 多个变量的赋值
a, b, c = 1, 2, 3
// 多个变量的声明同时赋值
var d, e, f int = 4, 5, 6
// 多个变量的省略类型的声明赋值(编译器推断类型
var g, h, i = 7, 8, 9
// 最简单的多个变量声明赋值
j, k, l := 10, 11, 12
fmt.Println(a, b, c, d, e, f, g, h, i, j, k, l)
}
~~~
![](https://box.kancloud.cn/2fefd9744a80249fb15f2f4200cd84c4_350x34.png)
#### 变量的类型转换
* Go中不存在隐式转换,所有类型转换必须显式声明
* 转换只能发生在两种相互兼容的类型之间
* 类型转换的格式: `<ValueA> [:]= <TypeOfValueA>(<ValueB>)`
~~~
package main
import "fmt"
func main() {
// 在相互兼容的两种类型之进行转换
var a float32 = 1.1
b := int(a)
// 不兼容的转换无法通过编译(compile)
var c bool = false
d := int(c)
fmt.Println(a, b, c, d)
}
~~~
![](https://box.kancloud.cn/06e0b0749602a43a3fd4456e7c3d2bc2_496x49.png)
#### 课堂作业
请尝试运行以下代码,看会发生什么,并思考为什么
~~~
package main
import "fmt"
func main() {
var a int = 65
b := string(a)
fmt.Println(b)
}
~~~
![](https://box.kancloud.cn/d6eec5539e913771e535ff93381d6720_391x30.png)
> string() 表示将数据转换成文本格式,因为计算机中存储的任何东西
本质上都是数字,因此此函数自然地认为我们需要的是用数字65表示
的文本 A。
### 常量
#### 常量的定义
* 常量的值在编译时就已经确定
* 常量的定义格式与变量基本相同
* 等号右侧必须是常量或者常量表达式
* 常量表达式中的函数必须是内置函数
~~~
package main
import "fmt"
// 定义单个常量
const a int = 1
const b = 'A'
const (
text = "12345"
lens = len(text)
num = b * 100
)
// 同时定义多个常量
const i, j, k = 1, false, "str"
const (
d, e, f = i, !j, len(k)
)
func main() {
fmt.Println(a, b, text, lens, num, i, j, k, d, e, f)
}
~~~
![](https://box.kancloud.cn/0176de65c3212a82e10933dc1aa269de_357x38.png)
#### 常量的初始化规则与枚举
* 在定义常量组时,如果不提供初始值,则表示将使用上行的表达式
* 使用相同的表达式不代表具有相同的值
* iota是常量的计数器,从0开始,组中每定义1个常量自动递增1
* 通过初始化规则与iota可以达到枚举的效果
* 每遇到一个const关键字,iota就会重置为0
~~~
package main
import "fmt"
const (
a = "A" //"A"
b //"A
c = iota //2
d //3
)
func main() {
fmt.Println(a, b, c, d)
}
~~~
![](https://box.kancloud.cn/3f55cc361585f8aa5d3c7a53587cdb31_332x37.png)
~~~
import "fmt"
// 星期枚举
const (
// 第一个不可省略表达式
Monday = iota
Tuesday
Wednerday
Thursday
Friday
Saturday
Sunday
)
func main() {
fmt.Println(Monday, Tuesday, Wednerday, Thursday, Friday, Saturday, Sunday)
}
~~~
![](https://box.kancloud.cn/712655b1563a5140afce272c063f45ea_341x38.png)
### 运算符
**注意**Go中的运算符均是从左至右结合
优先级(从高到低)
1. `^ ! ` (一元运算符)
2. `* / % << >> & &^`
3. `+ - | ^ ` (二元运算符)
4. `== != < <= >= >`
5. `<- ` (专门用于channel)
6. `&&`
7. `||`
### 其他
#### 指针
Go虽然保留了指针,但与其它编程语言不同的是,在Go当中不支持指针运算以及`->`运算符,而直接采用`.`选择符来操作指针目标对象的成员
操作符`&`取变量地址,使用`*`通过指针间接访问目标对象
默认值为 `nil `而非 `NULL`
```
package main
func main() {
var i = 0
var p = &i
println(*p)
var p1 *int
println(p1==nil)
}
```
![](https://box.kancloud.cn/d8ce3b829f1e3cb5efefe79de66d0bc9_323x46.png)
#### 递增递减语句
在Go当中,`++` 与 `-- `是作为语句而并不是作为表达式
```
package main
func main() {
var i = 0
var i1=i++
println(i1)
}
```
![](https://box.kancloud.cn/acbc0de159df10b650cda9b6b69c4ba0_488x35.png)
## 流程控制
### if
* 条件表达式没有括号
* 左大括号必须和条件语句或`else`在同一行
```
package main
func main() {
// 简短申明赋值
a,b:=1,2
// 没有小括号
if a>b{
println(b)
}else{
print(a)
}
}
```
* 支持一个初始化表达式(可以是并行方式)
* 支持单行模式
```
package main
func main() {
if a,b:=1,2;a>b{
println(b)
}else{
print(a)
}
}
```
* 初始化语句中的变量为`block`级别,同时隐藏外部同名变量
```
package main
func main() {
var a =true
if a,b:=1,2;a>b{
println(b)
}else{
print(a)
}
println(a)
}
```
### for
* Go只有`for`一个循环语句关键字,但支持3种形式
* 初始化和步进表达式可以是多个值
* 条件语句每次循环都会被重新检查,因此**不建议**在条件语句中使用函数,尽量提前计算好条件并以变量或常量代替
* 左大括号必须和条件语句在同一行
```
package main
func main() {
for{
//死循环
}
}
```
```
package main
func main() {
flag := 1
//while
for flag < 5 {
flag++
println(flag)
}
}
```
```
package main
func main() {
//index:=1
// for ;index < 5;index++ {
// println(index)
// }
for idx:=0;idx<5;idx++{
println(idx)
}
}
```
### switch
* 可以使用任何类型或表达式作为条件语句
* 不需要写`break`,一旦条件符合自动终止
* 如希望继续执行下一个`case`,需使用`fallthrough`语句
* 支持一个初始化表达式(可以是并行方式),右侧需跟分号
* 左大括号必须和条件语句在同一行
```
package main
func main() {
swh := 1
switch swh {
case 0:
println(0)
case 1:
{
println(1)
println("OK")
}
default:
println("default")
}
}
```
```
package main
func main() {
switch swh:=1;{
case swh > 0:
println(0)
fallthrough
case swh == 1:
{
println("OK")
}
default:
println("default")
}
}
```
### goto, break, continue
> 跳转语句
* 三个语法都可以配合标签使用
* 标签名区分大小写,若不使用会造成编译错误
* `break`与`continue`配合标签可用于多层循环的跳出**标签同级**
* `goto`是**调整执行位置**,与其它2个语句配合标签的结果并不相同
```
package main
func main() {
FLG:
for{
for i:=0;i<10;i++{
if i>2{
break FLG
}else{
println(i)
}
}
}
}
```
```
package main
func main() {
FLG:
for i := 0; i < 10; i++ {
for {
println(i)
continue FLG
}
}
}
```
#### 课堂作业
将上面中的continue替换成goto,程序运行的结果还一样吗?
## Array
* 定义数组的格式:`var <varName> [n]<type>` **n>=0**
* **数组长度也是类型的一部分**,因此具有不同长度的数组为不同类型
* 数组之间可以使用`==`或`!=`进行比较,但不可以使用`<`或`>`
* 可以使用`new`来创建数组,此方法返回一个指向数组的指针
* 数组在Go中为**值类型**
* 注意区分指向数组的指针和指针数组
* Go支持多维数组
```
package main
import "fmt"
func main() {
var arr1 [5]int = [5]int{}
arr1[1] = 99
var arr2 = [4]int{}
// paintln 只能输出简单类型
//println(arr1)
fmt.Println(arr1)
// 不同的类型不能比较
//invalid operation: arr1 == arr2 (mismatched types [5]int and [4]int)
// if arr1 == arr2 {
// fmt.Println("arr1==arr2")
// }
//指向数组的指针
var arr3 = new([3]int)
fmt.Println(&arr2, arr3)
// 由编译器推断数组大小
arr4 := [...]int{1, 2, 3, 4, 5, 6, 10: 9}
fmt.Println(arr4, len(arr4))
// 值类型 copy
arr5 := arr4
fmt.Printf("%p,%p\n", &arr4[1], &arr5[1])
//arr6:=[2][3]int{}
//多维数组
arr6 := [2][3]int{
{1, 2, 3},
{4, 5, 6},
}
fmt.Println(arr6)
}
```
### range
* 完整使用方式 `for k,v:=range arr{ /* do something*/}`
* 索引方式 `for item:=range { /* do something*/}`
* 值方式 `for _,v:=range arr{/* do something*/}`
```
package main
import "fmt"
func main() {
var arr = [10]int{2, 3, 4, 5, 6, 7, 8, 9}
for k,v:=range arr{
fmt.Println(k,v)
//i:=0
//fmt.Printf("i:%p\n",&i)
fmt.Printf("%p,%p\n",&k,&v)
}
println("oth")
for item:=range arr{
fmt.Println(item)
}
for _,v:=range arr{
fmt.Println(v)
}
}
```
![](https://box.kancloud.cn/3ce68d810665591af031a100e332e438_369x100.png)
#### 课堂作业
选择排序
```
package main
import "fmt"
func main() {
//选择排序
arr := [...]int{1, 3, 2, 8, 5, 7, 9}
fmt.Println(arr)
for i := 0; i < len(arr)-1; i++ {
for j := i + 1; j < len(arr); j++ {
if arr[i] > arr[j] {
// 交换两个变量的值,不用定义第三个变量
arr[i], arr[j] = arr[j], arr[i]
}
}
}
fmt.Println(arr)
}
```
## 切片Slice
* 其本身并不是数组,它指向底层的数组
```
type slice struct{
len int
cap int
data point
}
```
![](https://box.kancloud.cn/e9e02517d67ebb089065440ef219d7cf_391x224.png)
* 作为变长数组的替代方案,可以关联底层数组的局部或全部
* **引用类型**
* 可以直接创建或从底层数组获取生成
* 如果多个`slice`指向相同底层数组,其中一个的值改变会影响全部
* 使用`len()`获取元素个数,`cap()`获取容量
```
package main
import "fmt"
func main() {
var arr = [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
// 一般方式
// var s1 []int =[]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
var s1 = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println(s1)
// 通过数组
// var s2 = arr[a:b]// a:b a<=x<b 0<x<len()
// var s2 = arr[:]
// var s2 = arr[0:len(arr)]
var s2 = arr[0:3] //0,1,2
fmt.Println(s2)
var s3 = arr[2:4] //2,3
fmt.Println(s3)
// 引用
s2[2]=99
fmt.Println(s2)
fmt.Println(s3)
fmt.Println(len(s3),cap(s3))
}
```
![](https://box.kancloud.cn/3b17569f85f25d7b5c3c31069eb88473_291x120.png)
* 一般使用`make()`创建 ` make([]T, len, cap)`
> 其中`cap`可以省略,则和`len`的值相同
* `len`表示存数的元素个数,`cap`表示容量
* `append` `copy`
```
package main
import "fmt"
func main() {
// make创建 slice
var s1 = make([]int, 2, 4)
fmt.Println(s1, len(s1), cap(s1)) //[0 0] 2 4
// 省略cap则和len值相同
var s2 = make([]int, 2)
fmt.Println(s2, len(s2), cap(s2)) //[0 0] 2 2
// append 可能会返回新的slice
for i:=0;i<10;i++{
s1=append(s1,i,i+1)
fmt.Printf("%p\n",s1)//fmt.Printf("%p\n",&s1)
}
// append 一个slice
s1=append(s1,s2...)
s3:=[]int{1,2,3,4,5}
s4:=[]int{7,8,9}
//copy(s3,s4)
//fmt.Println(s3)//[7 8 9 4 5]
copy(s4,s3)
fmt.Println(s4)//[1 2 3]
}
```
![](https://box.kancloud.cn/7e9c8372ac234dee205e6caad7ed61c6_277x227.png)
**注意**
> reslice时索引以被slice的切片为准
> 索引不可以超过新slice的切片的`len()`值
> 索引越界不会导致底层数组的重新分配而是引发错误
>`append`只会在`slice`尾部追加元素
>可以将一个slice追加在另一个slice尾部
>如果最终长度未超过追加到slice的容量则返回原始slice
>如果超过追加到的slice的容量则将重新分配数组并拷贝原始数据
>%p: `&sliceNmae`,`sliceName`
## map
* 类似其它语言中的哈希表或者字典,以`key-value`形式存储数据
* `key`必须是支持`==`或`!=`比较运算的类型,**不**可以是函数、`map`或`slice`
* `map`使用`make()`创建,支持 `:=` 这种简写方式
* `make([keyType]valueType, cap)`,`cap`表示容量,可省略
* 超出容量时会自动扩容,但尽量提供一个合理的初始值
* 使用`len()`获取元素个数
* 键值对不存在时自动添加,使用`delete()`删除某键值对
* 使用`for range `对`map`和`slice`进行迭代操作
```
package main
import "fmt"
func main() {
// 创建 map
//var m map[int]string = make(map[int]string, 4)
//var m = make(map[int]string, 4)
m := make(map[int]string, 4)
fmt.Println(m,len(m))
m[2]="m2"
m[10]="m10"
fmt.Println(m)
fmt.Println(m[1]) //没有会返回零值
res,ok:=m[1]
fmt.Println(res,ok)
res,ok=m[2]
fmt.Println(res,ok)
// 删除元素
delete (m,2)
fmt.Println(m)
m[1]="m1"
m[2]="m2"
m[3]=`m3`
//遍历
for k:=range m{
fmt.Print(k)
fmt.Printf("\t%p\n",&k)
}
}
```
> rang 循环随机
**注意**
> 若对未初始化的map 不可进行赋值操作
```
package main
import "fmt"
func main() {
var m map[int]string
fmt.Println(m[1])
delete(m,2)
m[3]=`3`
}
```
![](https://box.kancloud.cn/60e3af3ffff46d7acc8b591bebd2461d_368x86.png)
#### 课堂作业
根据在 for range 部分讲解的知识,尝试将类型为`map[int]string`
的键和值进行交换,变成类型`map[string]int`
```
package main
import "fmt"
func main() {
//字面赋值
var m1 = map[int]string{
1: "item1",
2: "item2",
3: "item3",
4: "item4",
5: "item5",
6: "item6",
}
fmt.Println(m1)
var m2 = make(map[string]int)
for k, v := range m1 {
m2[v] = k
}
fmt.Println(m2)
}
```
## function
* 定义函数使用关键字 func,且左大括号不能另起一行
* 函数也可以作为一种类型使用
* Go 函数 **不**支持嵌套、重载和默认参数
* 支持以下特性:
> 无需声明原型
不定长度变参
多返回值
命名返回值参数
匿名函数、闭包
```
func funcName(参数列表 )(返回参数列){
//do something
}
```
```
// 多个同类型变量可简写,一个返回值可以省略括号
//func add(a int ,b int)(int){
//func add(a, b int) (int) {
func add(a, b int) int {
return a + b
}
```
```
package main
import "fmt"
func main() {
// 函数作为一种类型
add := func(a, b int) {
fmt.Println(a + b)
}
add(10, 20)
f := newFunc(100)
fmt.Println(f(10))
fmt.Println(f(100))
// 不定参数
params(1, 2, 3)
params(1)
params()
// 多返回值
//a10, a100 := mutRtn(10)
//fmt.Println(a10, a100)
// 只接收想要的值
a10, _ := mutRtn(10)
fmt.Println(a10)
_, n := named(100)
fmt.Println(n)
// 匿名函数
func(x int) {
fmt.Println("你不知道我的名字", x)
}(10)
}
// 函数作为类型
func newFunc(x int) func(int) int {
return func(t int) int {
return x + t
}
}
// 不定参数
func params(x ...int) {
fmt.Println(x)
}
// 多返回值
func mutRtn(a int) (int, int) {
return a * 10, a * 100
}
// 命名返回值
func named(a int) (x int, y int) {
x = 10 * a
y = 20 * a
return
}
```
## defer
* 函数体执行结束后按照调用顺序的**反顺序**逐个执行
* 常用于资源清理、文件关闭、解锁以及记录时间等操作
* 支持匿名函数的调用
* 如果函数体内某个变量作为defer时匿名函数的参数,则在定义defer时即已经获得了拷贝,否则则是引用某个变量的地址
```
package main
import "fmt"
func main() {
defer fmt.Println("hello defer")
for i := 0; i < 5; i++ {
func() {
fmt.Println(i)
}()
defer func() {
fmt.Println("defer:", i)
}()
//defer func(x int) {
// fmt.Println("defer-:", x)
//}(i)
}
}
```
* 通过与匿名函数配合可在return之后修改函数计算结果
```
package main
import "fmt"
func main() {
res := test(10)
fmt.Println(res)
res1 := test1(10)
fmt.Println(res1)
}
func test(i int) int {
defer func() {
i++
}()
return i * 10
}
func test1(i int) (r int) {
defer func() {
r++
}()
r = i * 10
return
}
```
* 即使函数发生严重错误也会执行
* go 没有异常机制,但有 panic/recover 模式来处理错误
* panic 可以在任何地方引发,但recover只有在defer调用的函数中有效
```
package main
import "fmt"
func main() {
// exit
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
panic("提前终止程序")
}
```
## struct
* 结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员
* 使用 `type <Name> struct{} `定义结构,名称遵循可见性规则(大/小写字母)
* 支持指向自身的指针类型成员
* 支持匿名结构,可用作成员或定义成员变量
* 可以使用字面值对结构进行初始化
* 允许直接通过指针来读写结构成员
* 相同类型的成员可进行直接拷贝赋值
* 支持 == 与 !=比较运算符,但不支持 > 或 <
* 支持匿名字段,本质上是定义了以某个类型名为名称的字段
* 嵌入结构作为匿名字段看起来像继承,但不是继承
* 可以使用匿名字段指针
## method
* Go 中虽没有class,但依旧有method
* 通过显示说明receiver来实现与某个类型的组合
* 只能为同一个包中的类型定义方法
* Receiver 可以是类型的值或者指针
* 不存在方法重载
* 可以使用值或指针来调用方法,编译器会自动完成转换
* 从某种意义上来说,方法是函数的语法糖,因为receiver其实就是
* 方法所接收的第1个参数(Method Value vs. Method Expression)
* 如果外部结构和嵌入结构存在同名方法,则优先调用外部结构的方法
* 类型别名不会拥有底层类型所附带的方法
* 方法可以调用结构中的非公开字段
## interface
* 接口是一个或多个方法签名的集合
* 只要某个类型拥有该接口的所有方法签名,即算实现该接口,无需显示声明实现了哪个接口
* 接口只有方法声明,没有实现,没有数据字段
* 接口可以匿名嵌入其它接口,或嵌入到结构中
* 将对象赋值给接口时,会发生拷贝,而接口内部存储的是指向这个复制品的指针,既无法修改复制品的状态,也无法获取指针
* 只有当接口存储的类型和对象都为nil时,接口才等于nil
* 接口调用不会做receiver的自动转换
* 接口同样支持匿名字段方法
* 接口也可实现类似OOP中的多态
* 空接口可以作为任何类型数据的容器
## 更多
### 类型断言
* 通过类型断言的`valur,ok`可以判断接口中的数据类型
* 使用`type switch`则可针对空接口进行比较全面的类型判断
### 反射reflection
* 反射使用 TypeOf 和 ValueOf 函数从接口中获取目标对象信息
* 反射会将匿名字段作为独立字段(匿名字段本质)
* interface.data 是 settable即 pointer-interface则可利用反射修改对象状态
* 通过反射可以“动态”调用方法
### 并发
* 并发不是并行,并发主要由切换时间片来实现“同时”运行,并行则是直接利用多核实现多线程的运行
## Channel
* Channel 是 goroutine 沟通的桥梁,大都是阻塞同步的
* 通过 make 创建,close 关闭
* Channel 是引用类型
* 可以使用 for range 来迭代不断操作 channel
* 可以设置单向或双向通道
* 可以设置缓存大小,在未被填满前不会发生阻塞
## Select
* 可处理一个或多个 channel 的发送与接收
* 同时有多个可用的 channel时按随机顺序处理
* 可用空的 select 来阻塞 main 函数
* 可设置超时