GO 語言總結

(這篇文章是我在看李文塔的Go語言核心編程這本書記錄下來的,算是個總結吧,方便以後複習用)
一、數據類型
1.1 布爾型、字符串
1.2 整型

unit8、unit16、unit32、unit64、int8、int16、int32、int64、byte(類似uint8)、rune(類似int32)、int、uintptr(無符號整型,存放一個指針)

1.3 浮點型

float32、float64、complex64、complex128

1.4 派生類型
指針類型(Pointer)、數組類型、結構化類型(struct)、Channel類型、函數類型、切片類型、接口類型(interface)、Map類型
1.5 查看一個變量的數據類型

fmt.println(reflect.TypeOf(x))

1.6 當使用等號 = 將一個變量的值負值給另一個變量時,如j=i,實際上是在內存中將i的值進行了拷貝,可以通過&i來獲取i的內存地址
1.7 同一個引用類型的指針指向的多個字可以是在連續的內存地址中(內存佈局是連續
的),這也是計算效率最高的一種存儲形式;也可以將這些字分散存放在內存中,
每個字都指示了下一個字所在的內存地址。
當使用賦值語句 r2 = r1 時,只有引用(地址)被複制。
如果 r1 的值被改變了,那麼這個值的所有引用都會指向被修改後的內容,在這個
例子中,r2 也會受到影響。
1.8 常量
常量是一個簡單值的標識符,在程序運行時,不會被修改的量。
常量中的數據類型只可以是布爾型、數字型(整數型、浮點型和複數)和字符串型。
顯式類型定義:

const b string = "abc"

隱式類型定義:

const b = "abc"

常量還可以用作枚舉:

const (
	Unknown = 0
	Female = 1
	Male = 2
)

1.9 自增長
在 golang 中,一個方便的習慣就是使用 iota 標示符,它簡化了常量用於增長數
字的定義,給以上相同的值以準確的分類。

const (
			CategoryBooks = iota // 0
			CategoryHealth // 1
			CategoryClothing // 2
		)

我們可以使用下劃線跳過不想要的值

type AudioOutput int
const (
	OutMute AudioOutput = iota // 0
	OutMono // 1
	OutStereo // 2
	_
	_
	OutSurround // 5
)

1.10 for循環

for a := 0; a < 10; a++ {
	fmt.Printf("a 的值爲: %d\n", a)
}

for 循環的 range 格式可以對 slice、map、數組、字符串等進行迭代循環。格式如下:

for key, value := range oldMap {
	newMap[key] = value
}

1.11 goto語句

LOOP: for a < 20 {
if a == 15 {
		/* 跳過迭代 */
		a = a + 1
		goto LOOP
	}
	fmt.Printf("a的值爲 : %d\n", a)
	a++
}
這段代碼跳過了a=15時的循環

1.12 函數
值傳遞
值傳遞是指在調用函數時將實際參數複製一份傳遞到函數中,這樣在函數中如果對
參數進行修改,將不會影響到實際參數。
默認情況下,Go 語言使用的是值傳遞,即在調用過程中不會影響到實際參數。

/* 定義相互交換值的函數 */
func swap(x, y int) int {
	var temp int
	temp = x /* 保存 x 的值 */
	x = y /* 將 y 值賦給 x */
	y = temp /* 將 temp 值賦給 y*/
	return temp;
}

引用傳遞
引用傳遞是指在調用函數時將實際參數的地址傳遞到函數中,那麼在函數中對參數
所進行的修改,將影響到實際參數。
/* 定義交換值函數*/

func swap(x *int, y *int) {
	var temp int
	temp = *x /* 保持 x 地址上的值 */
	*x = *y /* 將 y 值賦給 x */
	*y = temp /* 將 temp 值賦給 y */
}

1.13 main函數和init函數
Go裏面有兩個保留的函數:init函數(能夠應用於所有的package)和main函數
(只能應用於package main)。這兩個函數在定義時不能有任何的參數和返回值。
雖然一個package裏面可以寫任意多個init函數,但這無論是對於可讀性還是以後的
可維護性來說,我們都強烈建議用戶在一個package中每個文件只寫一個init函數。
Go程序會自動調用init()和main(),所以你不需要在任何地方調用這兩個函數。每個
package中的init函數都是可選的,但package main就必須包含一個main函數。
程序的初始化和執行都起始於main包。如果main包還導入了其它的包,那麼就會在
編譯時將它們依次導入。有時一個包會被多個包同時導入,那麼它只會被導入一次
(例如很多包可能都會用到fmt包,但它只會被導入一次,因爲沒有必要導入多
次)。當一個包被導入時,如果該包還導入了其它的包,那麼會先將其它包導入進
來,然後再對這些包中的包級常量和變量進行初始化,接着執行init函數(如果有的
話),依次類推。等所有被導入的包都加載完畢了,就會開始對main包中的包級常
量和變量進行初始化,然後執行main包中的init函數(如果存在的話),最後執行
main函數。

2.1 指針
一個指針變量可以指向任何一個值的內存地址它指向那個值的內存地址。
類似於變量和常量,在使用指針前你需要聲明指針。指針聲明格式如下:
var ip int / 指向整型*/
var fp float32 / 指向浮點型 */
2.2 指針使用流程:
定義指針變量。
爲指針變量賦值。
訪問指針變量中指向地址的值。
在指針類型前面加上 * 號(前綴)來獲取指針所指向的內容。

package main
import "fmt"
func main() {
	var a int= 20 /* 聲明實際變量 */
	var ip *int /* 聲明指針變量 */
	ip = &a /* 指針變量的存儲地址 */
	fmt.Printf("a 變量的地址是: %x\n", &a )
	/* 指針變量的存儲地址 */
	fmt.Printf("ip 變量儲存的指針地址: %x\n", ip )
	/* 使用指針訪問值 */
	fmt.Printf("*ip 變量的值: %d\n", *ip )
}

2.3 聲明數組
Go 語言數組聲明需要指定元素類型及元素個數,語法格式如下:
var variable_name [SIZE] variable_type
以上爲一維數組的定義方式。數組長度必須是整數且大於 0。例如以下定義了數組
balance 長度爲 10 類型爲 float32:
var balance [10] float32
2.4 初始化數組
以下演示了數組初始化

var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

初始化數組中 {} 中的元素個數不能大於 [] 中的數字。
如果忽略 [] 中的數字不設置數組大小,Go 語言會根據元素的個數來設置數組的大小:

var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

2.5 結構體
go 語言中數組可以存儲同一類型的數據,但在結構體中我們可以爲不同項定義不同
的數據類型。
結構體是由一系列具有相同類型或不同類型的數據構成的數據集合。

//定義結構體類型
type Books struct {
	title string
	author string
	subject string
	book_id int
}

2.6 切片(Slice)
Go 語言切片是對數組的抽象。
Go 數組的長度不可改變,在特定場景中這樣的集合就不太適用,Go中提供了一種
靈活,功能強悍的內置類型切片 (“動態數組”) ,與數組相比切片的長度是不固定
的,可以追加元素,在追加時可能使切片的容量增大。
2.7 切片定義
你可以聲明一個未指定大小的數組來定義切片:
var identifier []type
切片不需要說明長度。
或使用make()函數來創建切片:

var slice1 []type = make([]type, len)
也可以簡寫爲
slice1 := make([]type, len)

也可以指定容量,其中capacity爲可選參數。

make([]T, length, capacity)

這裏 len 是數組的長度並且也是切片的初始長度。
2.8 切片初始化

s :=[] int {1,2,3 }

直接初始化切片,[]表示是切片類型,{1,2,3}初始化值依次是1,2,3.其cap=len=3

s := arr[:]

初始化切片s,是數組arr的引用

s := arr[startIndex:endIndex]

將arr中從下標startIndex到endIndex-1 下的元素創建爲一個新的切片

s := arr[startIndex:]

缺省endIndex時將表示一直到arr的最後一個元素

s := arr[:endIndex]

缺省startIndex時將表示從arr的第一個元素開始

s1 := s[startIndex:endIndex]

通過切片s初始化切片s1

s :=make([]int,len,cap)

通過內置函數make()初始化切片s,[]int 標識爲其元素類型爲int的切片
2.9 append()

func main() {
	var numbers []int
	printSlice(numbers)
	/* 允許追加空切片 */
	numbers = append(numbers, 0)
	printSlice(numbers)
	/* 向切片添加一個元素 */
	numbers = append(numbers, 1)
	printSlice(numbers)
	/* 同時添加多個元素 */
	numbers = append(numbers, 2,3,4)
	printSlice(numbers)
	/* 創建切片 numbers1 是之前切片的兩倍容量*/
	numbers1 := make([]int, len(numbers), (cap(numbers))*2)
	/* 拷貝 numbers 的內容到 numbers1 */
	copy(numbers1,numbers)
	printSlice(numbers1)
}

2.10 封裝

type data struct {
	val int
}
//封裝的時候一定要是 基於指針類型的方法,否則不是同一個對象。
func (p_data* data)set(num int) {
	p_data.val = num
}
func (p_data* data)show() {
	fmt.Println(p_data.val)
}
func main() {
	p_data := &data{4}
	p_data.set(5)
	p_data.show()
}

2.11 繼承

type parent struct {
	val int
}
type child struct {
	parent
	num int
}
func main() {
	var c child
	c = child{parent{1}, 2}
	fmt.Println(c.num)
	fmt.Println(c.val)
}

2.12 多態

type act interface {
	write()
}
type xiaoming struct {
}
type xiaofang struct {
}
func (xm *xiaoming) write() {
	fmt.Println("xiaoming write")
}
func (xf *xiaofang) write() {
	fmt.Println("xiaofang write")
}
func main() {
	var w act;
	xm := xiaoming{}
	xf := xiaofang{}
	w = &xm
	w.write()
	w = &xf
	w.write()
}

2.13 接口
接口類型是對其它類型行爲的抽象和概括;因爲接口類型不會和特定的實現細節綁
定在一起,通過這種抽象的方式我們可以讓我們的函數更加靈活和更具有適應能力。
很多面向對象的語言都有相似的接口概念,但Go語言中接口類型的獨特之處在於它
是滿足隱式實現的。也就是說,我們沒有必要對於給定的具體類型定義所有滿足的
接口類型;簡單地擁有一些必需的方法就足夠了。這種設計可以讓你創建一個新的
接口類型滿足已經存在的具體類型卻不會去改變這些類型的定義;當我們使用的類
型來自於不受我們控制的包時這種設計尤其有用。
Go 語言提供了另外一種數據類型即接口,它把所有的具有共性的方法定義在一
起,任何其他類型只要實現了這些方法就是實現了這個接口。
2.14 接口實例

type Phone interface {
	call()
}
type NokiaPhone struct {
}
func (nokiaPhone NokiaPhone) call() {
	fmt.Println("I am Nokia, I can call you!")
}
type IPhone struct {
}
func (iPhone IPhone) call() {
	fmt.Println("I am iPhone, I can call you!")
}
func main() {
	var phone Phone
	phone = new(NokiaPhone)
	phone.call()
	phone = new(IPhone)
	phone.call()
}

2.15 類型斷言
golang的語言中提供了斷言的功能。golang中的所有程序都實現了interface{}的接
口,這意味着,所有的類型如string,int,int64甚至是自定義的struct類型都就此擁有
了interface{}的接口,這種做法和java中的Object類型比較類似。那麼在一個數據通
過func funcName(interface{})的方式傳進來的時候,也就意味着這個參數被自動的
轉爲interface{}的類型。
2.16 斷言的使用

func funcName(a interface{}) string {
	value, ok := a.(string)
	if !ok {
		fmt.Println("It is not ok for type string")
		return ""
	}
	fmt.Println("The value is ", value)
	return value
}
func main() {
	var a int = 10
	funcName(a)
}

2.17 斷言的使用

func sqlQuote(x interface{}) string {
	if x == nil {
		return "NULL"
	} else if _, ok := x.(int); ok {
		return fmt.Sprintf("%d", x)
	} else if _, ok := x.(uint); ok {
		return fmt.Sprintf("%d", x)
	} else if b, ok := x.(bool); ok {
		if b {
			return "TRUE"
		}
		return "FALSE"
	} else if s, ok := x.(string); ok {
		return sqlQuoteString(s) // (not shown)
	} else {
		panic(fmt.Sprintf("unexpected type %T: %v", x, x))
	}
}

2.18 寫文件操作

import (
	"fmt"
	"os"
)
func main() {
	myFile := "./abc.txt"
	fout, err := os.Create(myFile)
	//fout, err := os.OpenFile(myFile, os.O_CREATE, 0644)
	if err != nil {
		fmt.Println(err)
		return
	}
	for i := 0; i < 10; i++ {
		outstr := fmt.Sprintf("%s:%d\n", "Hello world",i)
		fout.WriteString(outstr)
		fout.Write([]byte("abcd\n"))
	}
	fout.Close()
}

2.19 讀文件操作

fin, err := os.Open(filename)
	if err != nil {
		fmt.Println(err)
	}
	defer fin.Close()
	buf := make([]byte, 1024) //開闢1024個字節的slice 作爲緩衝
	for {
		n, _ := fin.Read(buf)
		if n == 0 {
		//0 表示到達EOF
		break
	}
	os.Stdout.Write(buf)
}

2.20 文件複製

func main() {
	fi, err := os.Open("/home/itcast/abc.txt")//打開輸入*File
	if err != nil { panic(err) }
	defer fi.Close()
	fo, err := os.Create("/home/itcast/abc_new.txt")//創建輸出*Fi
	le
	if err != nil { panic(err) }
	defer fo.Close()
	buf := make([]byte, 1024)
	for {
		n, err := fi.Read(buf)//從input.txt讀取
		if err != nil && err != io.EOF { panic(err) }
		if n == 0 { break }
		if n2, err := fo.Write(buf[:n]); err != nil {//寫入output.txt,直到錯誤
			panic(err)
		} else if n2 != n {
			panic("error in writing")
		}
	}
}

2.21 文件複製2

func main() {
	b, err := ioutil.ReadFile("input.txt")//讀文件
	if err != nil { panic(err) }
	err = ioutil.WriteFile("output.txt", b, 0644)//寫文件
	if err != nil { panic(err) }
}

3.1 函數
函數是 Go 程序源代碼的基本構造單位,一個函數的定義包括如下幾個部分 函數聲明關
鍵字 也町、 函數名、參數列表、返回列表和函數體。函數名遵循標識符的命名規則, 首字母的
大小寫決定該函數在其他包的可見性:大寫時其他包可見,小寫時只有相同的包可以訪問;函
數的參數和返回值需要使用“()”包裹,如果只有一個返回值,而使用的是非命名的參數,則返回參數()
可以省略。
Go 函數實參到形參的傳遞永遠是值拷貝 有時函數調用後實參指向的值發生了變化,那是
因爲參數傳遞的是指針值的拷貝,實參是一個指針變量,傳遞給形參的是這個指針變量的副本,
二者指向同 地址 本質上參數傳遞仍然是值拷貝。
3.2 不定參數
所有的不定參數類型必須是相同的
不定參數必須是函數的最後 參數。
不定參數名在函數體 內相當於切片,對切片的操作同樣適合對不定參數的操作 例如

func sum(arr ...int) (sum int) {
	for , v : = range arr { // 此時 arr 就相當於切片,可以使用 range
		sum += v
	}
	return 
}

切片可以作爲參數傳遞給不定參數,切片名後要加上"…"

array := [...]int{1,2,3,4}
sum(array...)

數紐不可以作爲實參傳遞給不定參數的函數
3.3 函數簽名
函數類型有叫函數簽名,可以用fmt.Printf("%T\n",sum)來打印函數簽名
兩個函數類型相同的條件是:擁有相同的形參列表和返回值列表(列表元素的次序、個數
和類型都相同),形參名可以不同
3.4 匿名函數
匿名函數可以看作函數字面量 所有直接使用函
數類型變量的地方都可以由匿名函數代替。醫名函數可以直接賦值給函數變量,可以當作實參,
可以作爲返回值,還可以直接被調用

//匿名 函數被直接賦值函數變量
var sum= func(a , b int) int {
	return a + b
} 
//匿名函數作爲實參
doinput(func(x , y int) int {
	return x + y
} , 1 , 2) 

3.5 閉包
閉包是由函數及其相關引用環境組合而成的實體,一般通過在匿名函數中引用外部函數的
局部變量或包全局變量構成
多次調用該函數,返回的多個閉包所引用的外部變量是多個副本,原因是每次調用函
數都會爲局部變量分配內存
用一個閉包函數多次,如果該閉包修改了其引用的外部變量,則每一次調用該閉包對
該外部變量都有影響,因爲閉包函數共享外部引用。

4.1 命名類型
類型可以通過標識符來表示,這種類型稱爲命名類型。
使用 type 聲明的是命名類型
4.2 未命名類型
一個類型由預聲明類型、關鍵字和操作符組合而成,這個類型稱爲未命名類型。未命名類
型又稱爲類型字面量(Type Literal),本書中的未命名類型和類型字面量二者等價。
Go 語言的基本類型中的複合類型:數組( array )、 切片( li ce )、 字典( map )、通道( channel )、
指針(pointer 函數字面量( function )、結構( struct )和接口( interface 〕都屬於類型字面量,
也都是未命名類型
所以*int、[]int、[2]int、map[k]v都是未命名類型
使用 struct 字面量聲明的是未命名類型
4.3 類型可賦值
var b T2 = a
a可以複製給變量b必須滿足如下條件之一
*(1)T1和T2的類型相同
*(2)T1和T2具有相同的底層類型,並且T1和T2裏面至少有一個未命名類型
*(3)T2是接口類型,T1是具體類型,並且T1和T2裏面至少有一個未命名類型
*(4)T1和T2都是通道類型,他們擁有相同的元素類型,並且T1和T2裏面至少有一個未命名類型
*(5)a是一個字面常量值,可以用來表示類型T的值
4.4 類型強制轉換
由於 Go 是強類型的語言,如果不滿足自動轉換的條件,則必須進行強制類型轉換
非常量類型的變量 x 可以強制轉化並傳遞給類型 T 需要滿足如下任一條件
*(1)x可以直接複製給T類型變量
*(2)x的類型和T具有相同的底層類型
*(3)x 的類型和T 都是未命名的指針類型,並且指針指向的類型具有相同的底層類型
*(4)x 的類型和T 都是整型,或者都是浮點型
*(5)x 的類型和T 都是複數類型
*(6)x 是整數值或口byte 類型的值,T是 string 類型。
*(7)x 是一個字符串,T[] byte [] rune
4.5 自定義類型
前面介紹命名類型時提到了自定義類型。用戶自定義類型使用關鍵字type,,其語法格式是
type newtype oldtype oldtype 可以是自定義類型、預聲明類型、未命名類型中的任意一種。
4.6 類型方法

type T struct {
	a int
}
func (t T) Get () int { 
	return t.a
}
func (t *T)Set(int i){
	t.a = i
}
t.Get()

4.7 方法值
方法值(method value)其實是一個帶有閉包的函數變量,其底層實現原理和帶有閉包的匿名函數類似,
接收值被隱私地綁定到方法值得閉包環境中。後續調用不需要在顯示地傳遞接受者。

f := t.Set()
f(2)

4.8 方法表達式
t := T{a=1}
(T).Get(t)與 f := T.Get; f(t)等價
(*T).Set(&t,10)與f2 := T.Set;f3(&t,10)等價
優先使用方法值
4.9 組合和方法集
結構類型( struct )爲 Go 提供了強大的類型擴展,主要體現在兩個方面
struct 可以嵌入任意其它類型的字段
struct 可以嵌套自身的指針類型的字段 這
4.10 組合
從前面討論的命名類型的方法可知,使用 type 定義的新類型不會繼承原有類型的方法,有
個特例就是命名結構類型,命名結構類型可以嵌套其他的命名類型的宇段,外層的結構類型是
可以調用嵌入字段類型的方法,這種調用既可以是顯式的調用,也可以是隱式的調用。這就是
Go 的“繼承”,準確地說這就是 Go 的“組合”。
不推薦在多層的 struct 類型中內嵌多個同名的宇段;但是井不反對 tru ct 定義和內嵌字段同
名方法的用法,因爲這提供了一種編程技術 使得 struc 能夠重寫 內嵌字段的方法,提供面向
對象編程中子類覆蓋父類的同名方法的功能
4.11 函數類型
使用 func Functio ~ame ()語法格式定義的函數我們
稱爲“有名函數”,這裏所謂的有名是指函數在定義時指定了“函數名”;與之對應的是“匿名
函數”,所謂的醫名函數就是在定義時使用 func ()語法格式, 沒有指定函數名。通常所說的函
數就是指“有名函數”。

5.1 接口聲明
Go 的接口分爲接口字面量類型和接口命名類型 接口的聲明使用 interface 關鍵字
接口字面量類型的聲明語法如下:

interface{
	MethodSignature1
}

接口命名類型使用type關鍵字聲明,

type interfaceName interface{
	MethodSignature1
}
接口定義大括號內可以是方法聲明的集合,也可以嵌入另一個接口類型匿名字段,還可以
是二者的混合。接口支持嵌入匿名接口宇段,就是一個接口定義裏面可以包括其他接口,
聲明新接口類型的特點
(1)接口的命名一般以“er ”結尾
(2)接口定義的內部方法聲明不需要 func 引導。
(3)在接口定義中,只有方法聲明沒有方法實現。

5.2 接口初始化
單純地聲明一個接口變量沒有任何意義,接口只有被初始化爲具體的類型時纔有意義
。沒有初始化的接口變量,其默認值是 nil
5.3 實例賦值接口
如果具體類型實例的方法集是某個接口的方法集的超集,則稱該具體類型實現了接口,可
以將該具體類型的實例直接賦值給接口類型的變量 ,此時編譯器會進行靜態的類型檢查。接口
被初始化後,調用接口的方法就相當於調用接口綁定的具體類型的方法,這就是接口調用的語義
5.4 接口變量賦值接口變量
已經初始化的接口類型變量a直接賦值給另一個接口變量b,要求b的方法集是a的方法集的子集。

6.1 Goroutine
goroutine不同於thread,threads是操作系統中的對於一個獨立運行實例的描述,不
同操作系統,對於thread的實現也不盡相同;但是,操作系統並不知道goroutine的
存在,goroutine的調度是有Golang運行時進行管理的。啓動thread雖然比process
所需的資源要少,但是多個thread之間的上下文切換仍然是需要大量的工作的(寄
存器/Program Count/Stack Pointer/…),Golang有自己的調度器,許多goroutine
的數據都是共享的,因此goroutine之間的切換會快很多,啓動goroutine所耗費的資
源也很少,一個Golang程序同時存在幾百個goroutine是很正常的。
channel,即“管道”,是用來傳遞數據(叫消息更爲合適)的一個數據結構,即可以
從channel裏面塞數據,也可以從中獲取數據。channel本身並沒有什麼神奇的地
方,但是channel加上了goroutine,就形成了一種既簡單又強大的請求處理模型,
即N個工作goroutine將處理的中間結果或者最終結果放入一個channel,另外有M個
工作goroutine從這個channel拿數據,再進行進一步加工,通過組合這種過程,從
而勝任各種複雜的業務模型。
當一個程序啓動時,其主函數即在一個單獨的goroutine中運行,我們叫它main
goroutine。新的goroutine會用go語句來創建。在語法上,go語句是一個普通的函
數或方法調用前加上關鍵字go。go語句會使其語句中的函數在一個新創建的
goroutine中運行。而go語句本身會迅速地完成。
*go 的執行是非阻塞的,不會等待
*go 後面的函數的返回值會被忽略
*調度器不能保證多個goroutine的執行次序
*沒有父子goroutine的概念,所有的goroutine是平等地被調度和執行的
*Go 程序在執行時會單獨爲 main 函數 創建一個goroutine ,遇到其他 go 關鍵字時再去創建其他的 goroutine
runtime.GOMAXPROCS(0)表示查詢當前GOMAXPROCS(默認爲cup的個數)
runtime.NumGoroutine()表示查詢當前程序使用的goroutine數量
6.2 chan通道
通道是有類型的 ,可以簡單地把它理解爲有類型的管道。聲明 個簡單的通道語句是 chan
data Type ,但是簡單聲明 個通道變量沒有任何意義, a並沒有初始化,其值是 nil Go 語言
提供一個內置函數 make 來創建通道。例如:
創建 個元緩衝的通道,通道存放元素的類型爲 data type
make(chan datatype )
創建一個有 個緩衝的通道,通道存放元素的類型爲 data type
make(chan datatype, 10)
操作不同的chan會引發三種行爲
panic
*向已經關閉的通道寫入數據會導致panic
*重複關閉通道會導致panic
阻塞
*向未初始化的通道寫入數據或讀取數據會導致當前goroutine的永久堵塞
*向緩衝區已滿的通道寫入數據會導致阻塞
*通道中沒有數據,讀取該通道會導致 goroutine 阻塞。
非阻塞
*讀取己經關閉的通道不會引發阻塞,而是立即返回通道元素類型的零值,可以使用comrna , ok 語法判斷通道是否己經關閉。
*向有緩衝且沒有滿的通道讀/寫不會引發阻塞
6.3 WaitGroup
WaitGroup 用來等待多個 goroutine 完成, main goroutine 調用 Add 設置需要等待 goroutine
的數目,每一個 goroutine 結束時調用 Done(), Wait()被 main 用來等待所有的 goroutine 完成

var wg sync . WaitGroup 
func main() {
	wg.Add(1)
	go worker()
	wg.Wait()
	println("結束了")
}
func worker(){
	fmt.Println("Working......")
	for i:=0;i<100;i++{
		print(".")
	}
	defer wg.Done()
}

6.4 select
Go 語言借用多路複用的概念,提供了select關鍵字,用於多路監昕多個通道。當監聽的通道沒有狀態是可讀或可寫的,select是阻塞的;
只要監聽的通道中有 一個狀態是可讀或可寫的,則 select 就不會阻塞,而是進入處理就
緒通道的分支流程。如果監聽的通道有多個可讀或可寫的狀態, select隨機選取一個處理。

a := make(chan int,1)
for i:=0;i<10;i++{
	select {
	case a <-i:
	case x := <-a:
		println(x)
	}
}

6.5 扇入(Fan in)和扇出(Fan out)
扇入是指將多路通道聚合到一條通道中
處理 Go 語言最簡單的扇入就是使用 select 聚合多條通道服務;所謂的扇出是指將一條通道發
散到多條通道中處理,在 Go 語言裏面具體實現就是使用 關鍵字啓動多個 goroutine 併發處理。
6.6 通知退出機制
讀取己經關閉的通道不會引起阻塞,也不會導致panic,而是立即返回該通道存儲類型的零值
關閉select監聽的某個通道能使select立即感知這種通知,然後進行相應的處理。

func main() {
	done := make(chan  struct{})
	ch := GenerateIntA(done)
	fmt.Println(<-ch)
	fmt.Println(<-ch)
	close(done)
	fmt.Println(<-ch)
	fmt.Println(<-ch)
}
func GenerateIntA(done chan struct{}) chan int{
	ch := make(chan int)

	go func(){
		Lable:
			for   {
				select {
				case ch <- rand.Int():
				case <-done:
					break Lable
				}
			}
			close(ch)
	}()
	return ch
}

6.7 管道
通道可以分爲兩個方向,一個是讀,另一個是寫,假如一個函數的輸入參數和輸出參數都
是相 同的 chan 類型, 該函數可以調用自己,最終形成一個調用鏈。當然多個具有相同參數類
型的函數也能組成一個調用鏈,這很像 UNIX 系統的管道,是一個有類型的管道。
6.8 future模式
編程中經常遇到在一個流程中需要調用多個子調用的情況,這些子調用相互之間沒有依賴,
如果串行地調用,則耗時會很長,此時可以使用go併發編程中的future模式
future模式的基本原理是
*使用chan作爲函數參數
*啓動goroutine調用函數
*通過chan傳入參數
*通過chan異步獲取結果
6.9 context標準庫
Go 中的 goroutine 之間沒有父與子的關係,也就沒有所謂子進程退出後的通知機制,多個
goroutine 都是平行地被調度,多個 goroutine 如何協作工作涉及通信、同步、通知和退出四個方面。
通信: chan 通道當然是 goroutine 之間通信的基礎, 注意這裏的通信主要是指程序的數據通信
同步:不帶緩衝的 chan 提供了一個天然的同步等待機制:當然 sync WaitGroup 也爲
go routine 協同工作提供一 種同步等待機制
通知:這個通知和上面通信的數據不一樣,通知通常不是業務數據,而是管理、控制流數
據。要處理這個也好辦,在輸入端綁定兩個 chan 個用於業務流數據,另一個用於異常通知
數據,然後通過 select 收斂進行處理。這個方案可以解決簡單的問題,但不是一個通用的解決方案。
退出 goroutine 之間沒有父子關係,如何通知 goroutine 退出?可以通過 個單獨的通
道,藉助通道和 select 的廣播機制( close channel to broadcast )實現退出
6.10 context設計的目的
context 庫的設計目的就是跟蹤 goroutin 調用樹,並在這些 gouroutine 調用樹中傳遞通知和元數據
*退出通知機制–通知可以傳遞給整個 goroutine 調用樹上的每一個 goroutine
*傳遞數據–數據可以傳遞給整個goroutine調用數上的每一個goroutine
6.11 context的工作機制
第一個創建 Context goroutine
被稱爲 root 節點。 root 節點負責創建一個實現context接口的具體對象 並將該對象作爲參數
遞到其新拉起 goroutin ,下游的 goroutine 繼續封裝該對象,再傳遞 到更下游的goroutine
Context 對象在傳遞的過程中最終形成一個樹狀的數據結構,這樣通過位於 root 節點(樹的根節
點〉 Context 對象就能遍歷整個 Context 對象樹 ,通知和消息就可以通過 root 節點傳遞出去實
現了上游 goroutine 對下游 goroutin 的消息傳遞。

7.1 反射的基本概念
Go的反射基礎是接口和類型系統。Go的反射巧妙地藉助了實例到接口的轉換所使用的數據結構,首先將實例傳遞給內部的空接口,實際上是將一個實例類型
轉換爲接口可以表述的數據結構eface,反射基於這個轉換後的數據結構來訪問和操作實例的值和類型。在接口章節我們知道實例傳遞給interface{}類型。
編譯器會進行一個內部轉換,自動創建相關類型數據結構。
7.2 TypeOf

Go的refelct包通過refelct.TypeOf()返回一個Type類型的接口,使用者通過接口來獲取對象的類型信息。
	TypeOf的通用方法
	Name()								返回包含包名的類型名字,未命名返回空
	Kind()								返回底層的基礎類型
	Implements(u Type)bool				確定當前類型是否實現了u接口類型
	AssignableTo(u Type)bool			判斷當前類型的實例能否賦值給type爲u的類型變量
	ConvertibleTo(u Type)bool			判斷當前類型的實例是否能強制轉換爲u類型變量
	Comparable()bool					判斷當前類型是否支持比較(等於或者不等於)
	NumMethod()int						返回一個類型方法的個數
	MethodByName(String)(Method,bool)	通過名稱獲取方法method
	PkgPath()string						返回類型的包路徑,如果類型是預聲明類型或未命名類型,則返回空字符串
	Size()uintptr						返回存放該實例需要多大的字節空間
	
	不同類型專有的方法
	Elem()Type									返回類型元素的類型,只適用於Array、Chan、Map、Ptr、Slice類型
	
	Bits()int									返回數值類型內存佔用的位數
	
	struct類型專用方法
	NumField()int								返回字段數目
	Field(i int) StructField					通過索引獲取Struct字段
	FieldByIndex(index []int) StructField		
	FieldByName(name string) (StructField,bool)	通過名字獲取sturct字段
	
	func類型專用方法
	IsVariadic()bool							函數是否是不定參數函數
	NumIn() int									輸入參數個數
	NumOut() int								返回值個數
	In(i int)Type								返回第i個輸入參數類型
	Out(i int) Type 							返回第i個返回值類型
	
	Key() Type									返回map Key的類型
t := reflect.TypeOf(i)
v := reflect.ValueOf(&i)

fmt.Println("Type",t)
fmt.Println("Value",v)

for i:=0;i<t.NumField();i++{
	field := t.Field(i).Name
	value := v.Field(i).Interface()
	fmt.Println(field,value)
}

for i:=0;i<t.NumMethod();i++{
	m := t.Method(i)
	fmt.Println(m.Name,m.Type)
}

7.3 基礎類型
Type接口有一個Kind()方法,返回一個整數枚舉值,不同的值代表不同的類型。這裏的類型是一個抽象的概念,我們暫且稱之爲“基礎類型”,比如
所有的幾個都歸爲一種基礎類型struct,所有的函數都歸爲一種基礎類型func

type Kind uint

	const (
		Invalid Kind = iota
		Bool
		Int
		Int8
		Int16
		Int32
		Int64
		Uint
		Uint8
		Uint16
		Uint32
		Uint64
		Uintptr
		Float32
		Float64
		Complex64
		Complex128
		Array
		Chan
		Func
		Interface
		Map
		Ptr
		Slice
		String
		Struct
		UnsafePointer
	)

底層類型和基礎類型的區別在於,基礎類型是抽象的類型劃分,底層類型是針對每一個具
體的類型來定義的,比如不同的 struct 類型在基礎類型上都劃歸爲 sturct 類型,但不同的 struct
底層類型是不一樣的。
7.4 從實例到value 直接使用ValueOf()函數
從實例到Type 直接使用TypeOf()函數
從Type到Value Type裏面只有類型信息,所以直接從一個Type接口變量裏面是無法獲取實例的Value的,但可以通過該Type構建一個新實例的Value
*New返回的是一個Value,該Value的type爲PrtTo(typ),即Value的Type是指定typ的指針類型
func New(typ Type) Value
*Zero 返回的是一個 typ 類型的零值,注意返回的 Value 不能尋址 值不可改變
func Zero(typ Type) Value
從Value到Type 從反射對象 Value到 Type 可以直接調用 Value 的方法,因爲 Value 內部存放着到 Type 類型的指針。
*function (v Value) Type() Type
從Valeu 到實例 Value 本身就包含類型和值信息, reflect 提供了豐富的方法來實現從飛Value 到實例的轉換。
*該方法最通用,用來將 Value 轉換爲空接口,該空接口內部存放具體類型實例,可以使用接口類型查詢去還原爲具體的類型
func (v Value )Interface () (Interface {})
*Value 自身也提供豐富的方法,直接將 Value 轉換爲簡單類型實例,如果類型不匹配,則直接引起 panic
func (v Value) Bool () bool
func (v Value) Float() float64
func (v Value) Int() int64
func (v Value) Uint() uint64
7.5 Type指針和值得相互轉換
指針類型Type到值類型Type
//t 必須是 Array Chan Map Ptr, Slice ,否則會引起 panic
//Elem 返回的是其內部元素的 Type
t .Elem() Type
值類型Type到指針類型Type
//PtrTo 返回的是指向 t的指針型 Type
func PtrTo(t Type) Type
7.6 Value 的可修改行
通過 Can Set 判斷是否能修改

func (v Value ) CanSet( ) bool

通過 Set 進行修改

func (v Value ) Set(x Value ) 
//下面是代碼示例
type Student struct {
	Name string "學生姓名"
	Age int `a:"1111"b:"3333"`
}
func main() {
	s:= Student{
		Name: "張三",
		Age:  20,
	}
	typeToValue(&s)
}
func typeToValue(i interface{}){
	v := reflect.ValueOf(i)
	fmt.Println(v.CanSet(),v.Elem().FieldByName("Name").CanSet())
	name := "nihao"
	vc := reflect.ValueOf(name)
	v.Elem().FieldByName("Name").Set(vc)
	fmt.Println(v.Elem().FieldByName("Name"))
}
	
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章