[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`顯示版本信息表示成功。

2. 測試Go環境`win + r -> cmd -> go env`注意觀察`GOPATH`

3. GOPATH下最終會存在3個目錄

* 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`

> 一個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` 關鍵字來進行函數的聲明

## 包package
### 導入package

### package 導入合并

**注意**
* 導入包之后,就可以使用格式 `PackageName.FuncName`來對包中的函數進行調用
* 如果導入包之后未調用其中的函數或者類型將會報出編譯錯誤:

### package 別名

>當使用第三方包時,包名可能會非常接近或者相同,此時就可以使用
別名來進行區別和調用
### 省略調用


### 可見性規則
Go語言中,使用**大小寫**來決定該 常量、變量、類型、接口、結構或函數是否可以被外部包所調用:

**注意**
* 首字母大寫即為`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)
}
~~~

#### 多個變量的聲明與賦值
* 全局變量的聲明可使用 `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)
}
~~~

#### 變量的類型轉換
* 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)
}
~~~

#### 課堂作業
請嘗試運行以下代碼,看會發生什么,并思考為什么
~~~
package main
import "fmt"
func main() {
var a int = 65
b := string(a)
fmt.Println(b)
}
~~~

> 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)
}
~~~

#### 常量的初始化規則與枚舉
* 在定義常量組時,如果不提供初始值,則表示將使用上行的表達式
* 使用相同的表達式不代表具有相同的值
* 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)
}
~~~

~~~
import "fmt"
// 星期枚舉
const (
// 第一個不可省略表達式
Monday = iota
Tuesday
Wednerday
Thursday
Friday
Saturday
Sunday
)
func main() {
fmt.Println(Monday, Tuesday, Wednerday, Thursday, Friday, Saturday, Sunday)
}
~~~

### 運算符
**注意**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)
}
```

#### 遞增遞減語句
在Go當中,`++` 與 `-- `是作為語句而并不是作為表達式
```
package main
func main() {
var i = 0
var i1=i++
println(i1)
}
```

## 流程控制
### 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)
}
}
```

#### 課堂作業
選擇排序
```
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
}
```

* 作為變長數組的替代方案,可以關聯底層數組的局部或全部
* **引用類型**
* 可以直接創建或從底層數組獲取生成
* 如果多個`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))
}
```

* 一般使用`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]
}
```

**注意**
> 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`
}
```

#### 課堂作業
根據在 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 函數
* 可設置超時