《Go語言編程》 許式偉 呂桂華 著
一腮敌、語言特性
垃圾回收阱当、更豐富內(nèi)置類型、函數(shù)多返回值糜工、錯誤處理弊添、匿名函數(shù)和閉包、類型和接口捌木、并發(fā)編程油坝、反射、語言交互性
二、順序編程
2.2 常量
- 如果定義常量時沒有指定類型澈圈,那么它與字面量一樣彬檀,是無類型常量。
const(
size int 64 = 1024
eof = -1
)
- iota 在每個const關(guān)鍵詞出現(xiàn)時被重置為0瞬女,然后沒出現(xiàn)一次iota窍帝,其所代表數(shù)字會增加1
const(
c0 = iota //c0 == 0
c1 //c1==1
c2 //c2==2
)
- 枚舉:
Go不支持明確的enum關(guān)鍵字。
下面是一個常規(guī)的枚舉表示法:
const(
Sunday = iota
Monday
Tuesday
Wednesday
...
)
以大寫字母開頭的常量在包外可見
2.3類型
對于常規(guī)開發(fā)來說诽偷,用int和uint就可以了坤学,以免導(dǎo)致移植困難
字符串在Go中也是基本類型,字符串的內(nèi)容不能在初始化后被修改
- 字符串遍歷:
str := "hello world"
n := len(str)
for i:=0; i<n; i++{
ch := str[i]
fmt.Println(ch)
}
for _,v := range myArray{
fmt.Print(v)
}
字符類型
Go中支持兩個字符類型报慕,一個是Byte深浮,一個是rune數(shù)組
在Go中,數(shù)組是值類型眠冈,所有的值類型變量在賦值和作為參數(shù)傳遞時都將產(chǎn)生一次復(fù)制動作飞苇。若數(shù)組作為函數(shù)的參數(shù)類型,則在調(diào)用時函數(shù)體中無法修改傳入數(shù)組的內(nèi)容洋闽。若想修改玄柠,需使用切片來實現(xiàn)。切片
類似于C++中數(shù)組和vector的關(guān)系诫舅。
基于數(shù)組創(chuàng)建切片:
var myArray [10]int = [10]int{1,2,3,4,5,6,7,8,9,10}
var mySlice []int = myArray[:5]
#遍歷切片
for _, v := range mySlice{
fmt.Print(v)
}
直接創(chuàng)建:
mySlice := make([]int, 5) //初始元素個數(shù)為5的切片羽利,初始值為0
mySlice := make([]int, 5, 10) //初始元素個數(shù)為5的切片,初始值為0刊懈,并預(yù)留10個元素的存儲空間
mySlice := []int{1,2,3,4,5}
在切片后附加元素:
mySlice = append(mySlice, 1, 2, 3) //附加1这弧,2,3
mySlice = append(mySlice, mySlice...) //...相當于把mySlice打散了傳進去
內(nèi)容復(fù)制:
copy() :若兩個切片不一樣虚汛,就會按較小的那個數(shù)組切片的元素個數(shù)進行復(fù)制
- map
GO中map也是基本數(shù)據(jù)類型
#聲明
var myMap map[string] PersonInfo //PersonInfo是一個結(jié)構(gòu)體匾浪,也是value的類型
#創(chuàng)建
myMap = make(map[string] PersonInfo)
# 賦值
myMap["1234"] = PersonInfo{初始化}
# 刪除
delete(myMap,"1234")
#查找
value, ok := myMap["1234"]
if ok{
處理value
}
2.4流程控制語句
- 跳轉(zhuǎn)語句
goto 標簽
2.5函數(shù)
小寫字母開頭的函數(shù)只在包內(nèi)可見,大寫字母開頭的函數(shù)才能被其他包使用(前提是在導(dǎo)入了包的情況下)
func Add(a int, b int) (ret int, err error){
if a<0 || b<0 {
err = errors.New("error")
return //在函數(shù)中執(zhí)行不帶任何參數(shù)的return語句時卷哩,會返回對應(yīng)的返回值變量的值
}
return a+b, nil
}
- 不定參數(shù)
func myFun(args ...int){
for _, v := range args{
fmt.Print(v)
}
}
不定參數(shù)的傳遞:
myfun(args...)
myfun(args[1:]...) //任意的int slice都可以傳進去
任意類型不定參數(shù)的傳遞:
func Printf(format string, args ...interface{}){
//...
}
用interface{}傳遞任意類型數(shù)據(jù)是GO語言的慣例用法
- 匿名函數(shù)和閉包
在Go中蛋辈,函數(shù)可以像普通變量一樣被傳遞或使用
匿名函數(shù)可以直接賦值給一個變量或者直接執(zhí)行
閉包可以非常靈活地操作外部變量,因此在Go語言中将谊,閉包常用于創(chuàng)建函數(shù)變量冷溶、實現(xiàn)函數(shù)式編程、并發(fā)編程等場景尊浓。
2.6 錯誤處理
- error接口
type error interface{
Error() string
}
對于大多數(shù)函數(shù)逞频,若要返回錯誤,可定義為如下模式:
將error作為返回值的最后一個
func Foo(param int) (n int, err error){}
#調(diào)用代碼時:
n, err := Foo(0)
if err != nil{
// 錯誤處理
} else {
// 使用返回值n
}
- 定義自己的error類型
首先栋齿,定義一個用于承載錯誤信息的類型
type PathError struct {
Op string
Path string
Err error
}
然后苗胀,實現(xiàn)Error()方法
func (e *PathError) Error() string{
return e.Op + "" + e.Path + "" + e.Err.Error()
}
返回err時襟诸,只需要將其他函數(shù)返回的普通的err當作參數(shù)用于初始化PathError,然后就可以直接返回PathError變量了基协。
- defer
在當前函數(shù)退出前執(zhí)行defer的操作歌亲,defer遵循先進后出的原則
defer srcFile.Close()
# 可以加一個匿名函數(shù)處理
defer func(){
}()
- panic() 和 recover()
報告和處理運行時錯誤和程序中的錯誤場景
panic 用于引發(fā)運行時錯誤”ぬ停可以調(diào)用 panic 函數(shù)來中止程序的正常執(zhí)行应结。panic 會導(dǎo)致程序立即停止,并觸發(fā)執(zhí)行函數(shù)調(diào)用棧的逆序執(zhí)行泉唁,直到所有被推遲(defer)的函數(shù)調(diào)用執(zhí)行完畢鹅龄,然后程序退出。
recover 用于捕獲 panic 引發(fā)的異常亭畜,并在程序中恢復(fù)扮休。它只能在延遲函數(shù)中調(diào)用,用于恢復(fù) panic 引發(fā)的錯誤拴鸵。
三玷坠、面向?qū)ο缶幊?/h2>
3.1類型系統(tǒng)
- 為類型添加方法
Go語言中的大多數(shù)類型都是值語義【⒚辏可以給任何類型八堡,包括內(nèi)置類型增加新方法。任何類型都可以被Any類型引用聘芜,Any類型就是空接口兄渺,即interface{}
Go語言中的大多數(shù)類型都是值語義【⒚辏可以給任何類型八堡,包括內(nèi)置類型增加新方法。任何類型都可以被Any類型引用聘芜,Any類型就是空接口兄渺,即interface{}
通過為int取別名并增加了方法,他就變成了一個全新的類型汰现,但這個新類型又完全擁有int的功能:
type Integer int
func (a Integer) Less(b Integer) bool {
return a<b
}
只有在需要修改對象的時候挂谍,才必須使用指針
func (a *Integer) Add (b Integer){
*a += b
}
值語義和引用語義
GO中數(shù)組切片,map瞎饲,channel口叙,接口是引用語義結(jié)構(gòu)體
Go的結(jié)構(gòu)體和其他語言的類具有同等地位。因為所有的Go語言類型(指針除外)都可以有自己的方法嗅战,所以妄田,結(jié)構(gòu)體只是很普通的符合類型。
3.2 初始化
rectPtr := new(Rect) //使用new關(guān)鍵字創(chuàng)建結(jié)構(gòu)體指針
rect2 := &Rect{} //也是指針驮捍,加了&的都是指針疟呐,不加&就直接返回對象
rect3 := &Rect{0,0,100,200}
rect4 := &Rect{width:100, height:200}
在Go中無構(gòu)造函數(shù)的概念,對象的創(chuàng)建通常交由一個全局的創(chuàng)建函數(shù)來完成厌漂,以NewXXX來命名
func NewRect(x,y,width,height float64) *Rect{
return &Rect{x,y,width,height}
}
3.3匿名組合
Go也提供了繼承,但采用了組合的文法斟珊,所以將其稱為匿名組合苇倡。
// 定義一個Person結(jié)構(gòu)體
type Person struct {
Name string
Age int
}
// 定義一個Student結(jié)構(gòu)體富纸,匿名組合了Person結(jié)構(gòu)體
type Student struct {
Person
Grade int
}
func main() {
// 創(chuàng)建一個Student對象
student := Student{
Person: Person{
Name: "Alice",
Age: 20,
},
Grade: 10,
}
// 訪問Student對象的字段
fmt.Println("Name:", student.Name) // 輸出: Name: Alice
fmt.Println("Age:", student.Age) // 輸出: Age: 20
fmt.Println("Grade:", student.Grade) // 輸出: Grade: 10
}
//Student 結(jié)構(gòu)體匿名組合了 Person 結(jié)構(gòu)體。這意味著 Student 類型繼承了 Person 類型的所有字段和方法旨椒。
- 方法重寫
當結(jié)構(gòu)體通過匿名組合嵌套了多個結(jié)構(gòu)體晓褪,且這些結(jié)構(gòu)體有相同方法名時,可進實現(xiàn)重寫效果
3.5 接口
在Go語言中综慎,一個類只需要實現(xiàn)了接口要求的所有函數(shù)涣仿,我們就說這個類實現(xiàn)了該接口
3.5.3 接口賦值
有兩種情況:將對象實例賦值給接口(賦值時對象前加&)、將一個接口賦值給另外一個接口(方法列表為其子集)
3.5.4接口查詢
接口查詢是指通過類型斷言或類型判斷來判斷一個值是否實現(xiàn)了特定的接口示惊,并獲取其實現(xiàn)的接口類型或調(diào)用接口方法好港。
- 類型斷言
類型斷言用于將一個接口類型的值轉(zhuǎn)換為其他具體類型。如果轉(zhuǎn)換成功米罚,可以訪問其特定類型的方法和屬性钧汹。
type Shape interface {
Area() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}
func main() {
var shape Shape
circle := Circle{Radius: 5.0}
shape = circle
// 類型斷言
if c, ok := shape.(Circle); ok {
fmt.Printf("Type assertion successful. Radius: %.2f\n", c.Radius)
} else {
fmt.Println("Type assertion failed")
}
}
- 類型判斷
類型判斷用于根據(jù)不同的類型執(zhí)行相應(yīng)的代碼塊。
func calculateArea(shape Shape) {
switch v := shape.(type) {
case Circle:
fmt.Printf("Area of circle: %.2f\n", v.Area())
default:
fmt.Println("Unknown shape")
}
}
func main() {
circle := Circle{Radius: 5.0}
calculateArea(circle)
}
3.5.6 接口組合
3.5.7 Any類型
由于Go中任何對象實例都滿足空接口interface{} 所以录择,interface{}看起來像是可以指向任何對象的Any類型拔莱。
當函數(shù)可以接受任意的對象實例時,我們會將其聲明為interface{}
func Printf(fmt string, args ...interface{})
四隘竭、并發(fā)編程
4.1 并發(fā)基礎(chǔ)
并發(fā)的幾種主流的實現(xiàn)模型:
- 多進程
開銷最大塘秦,好處在于簡單、進程間互不影響 - 多線程
比進程開銷小动看,但其開銷依舊較大 - 基于回調(diào)的非阻塞/異步IO
使用多線程模式會很快耗盡服務(wù)器的內(nèi)存和CPU資源尊剔。而這種模式通過事件驅(qū)動的方式使用異步IO,使服務(wù)器持續(xù)運轉(zhuǎn)弧圆,且盡可能地少用線程赋兵,降低開銷。但是編程復(fù)雜搔预。 - 協(xié)程
本質(zhì)上是一種用戶態(tài)線程霹期,不需要操作系統(tǒng)來進行搶占式調(diào)度,系統(tǒng)開銷極小拯田,可以有效提高線程的任務(wù)并發(fā)性历造。使用協(xié)程的優(yōu)點是編程簡單,結(jié)構(gòu)清晰船庇;缺點是需要語言支持吭产。
4.2 協(xié)程
Go語言在語言級別支持輕量級線程,叫g(shù)oroutine鸭轮。其讓輕量級線程的切換管理不依賴于系統(tǒng)的線程和進程臣淤,也不依賴于CPU的核心數(shù)量。
4.3 goroutine
goroutine是Go語言中輕量級線程實現(xiàn)窃爷,由Go運行時管理邑蒋。
加上go
關(guān)鍵字姓蜂,這次調(diào)用就會在一個新的goroutine中并發(fā)執(zhí)行,當被調(diào)用的函數(shù)返回時医吊,這個gorontine也就結(jié)束了钱慢。如果這個函數(shù)由返回值,其返回值會被拋棄卿堂。
4.4 并發(fā)通信
Go語言以消息機制而非共享內(nèi)存作為通信方式
消息機制認為每個并發(fā)單元是自包含的束莫,獨立的個體,并且都有自己的變量草描,但在不同的并發(fā)單元間這些變量不共享览绿。每個并發(fā)單元的輸入和輸出只有一種,那就是消息陶珠。類似于進程的概念挟裂,每個進程不會被其他進程打擾。
這種消息機制被稱為Channel揍诽。不要通過共享內(nèi)存來通信诀蓉,而應(yīng)該通過通信來共享內(nèi)存。
4.5 Channel
channel 是Go語言在語言級別提供的goroutine間的通信方式暑脆。channel是進程內(nèi)的通信方式
渠啤。如果需要跨進程通信,我們建議用分布式系統(tǒng)的方法來解決添吗,比如使用Socket或者HTTP等通信協(xié)議沥曹。
channel是類型相關(guān)的,一個channel只能傳遞一種類型的值碟联。
- 基本語法
var chanName chan ElementType
var m map[string] chan bool //聲明一個map妓美,其元素類型是bool的channel
# 聲明并初始化
ch := make(chan int)
ch<-value
value := <-ch
向channel中寫入數(shù)據(jù)會導(dǎo)致程序阻塞,直到有其他goroutine從這個channel中讀取數(shù)據(jù)鲤孵。如果channel之前沒有寫入數(shù)據(jù)壶栋,那么從channel中讀數(shù)據(jù)也會導(dǎo)致程序阻塞。
- select
每個case語句里必須是一個channel操作
select {
case <-chan1:
... //如chan1成功讀到數(shù)據(jù)普监,則進行該case處理語句贵试,并直接忽略掉讀到的數(shù)據(jù)
case chan2<- 1:
... //如果成功向chan2寫入數(shù)據(jù),則進行該case語句
default:
... //上面都沒有成功凯正,進入default
}
在 Go 語言的 select 語句中毙玻,如果多個 case 同時滿足條件,Go 會隨機選擇一個 case 執(zhí)行廊散,這也稱為偽隨機選擇桑滩。這意味著無法確定哪個 case 會被優(yōu)先選擇,因此我們不能依賴于特定 case 的執(zhí)行順序允睹。
緩沖機制
即使沒有讀取方运准,寫入方也可以一直往channel里寫入往声,在緩沖區(qū)填完之前都不會阻塞。
c := make (chan int, 1024)超時機制
channel實現(xiàn)超時機制
time out := make(chan bool, 1)
go func(){
time.sleep(1e9) //1s
timeout <- true
}()
select{
case <-ch:
...
case <-timeout:
...
}
channel的傳遞
channel 本身在定義后也可以通過channel來傳遞
可以利用這個特性來實現(xiàn)管道特性單向channel
單向channel只能用于發(fā)送或者接受數(shù)據(jù)戳吝。只是對channel的一種使用限制。
我們在將一個channel變量傳遞到一個函數(shù)時贯涎,可以通過將其指定為單向channel變量听哭,從而限制該函數(shù)中可以對channel的操作,比如只能往這個channle寫或讀塘雳。
# 聲明
var ch1 chan int
var ch2 chan<-float64 //單向的陆盘,只能寫float64數(shù)據(jù)
var ch3 <-chan int //單向的,只能讀取int數(shù)據(jù)
#初始化
ch4 := make(chan int)
ch5 := <-chan int(ch4)
ch6 := chan<- int(ch4)
#用法
func Parse(ch <-chan int){
for value := range ch{
...
}
}
- 關(guān)閉channel
close(ch)
判斷是否已經(jīng)被關(guān)閉:
x,ok := <-ch
只需要看第二個bool返回值即可败明,false表示已經(jīng)被關(guān)閉
4.6 多核并行化
可以通過設(shè)置環(huán)境變量GOAMAXPROCS的值來控制使用多少給CPU核心
或者runtime.GOMAXPROCS()
runtime.NumCOU()獲取核心數(shù)
4.7 讓出時間片
runtime.Gosched()主動讓出時間片給其他goroutine
實際上隘马,要想精細控制goroutine行為,須要深入了解runtime包提供的具體功能妻顶。
4.8 同步
- 同步鎖
sync包提供了兩種鎖類型:sync.Mutex 和sync.RWMutex酸员。
當一個goroutine獲取Mutex后,其他goroutine只能等到該goroutine釋放Mutex讳嘱。
RWMutex是經(jīng)典的單寫多讀模型幔嗦,在讀鎖占用的情況下會阻止寫,但不阻止讀沥潭。從RWMutex的實現(xiàn)看邀泉,其組合了Mutex。讀鎖調(diào)用RLock()钝鸽,寫鎖Lock()
任何一個Lock() 或RLock均需要保證對應(yīng)有Unlock或RUnlock()汇恤。
var l sync.Mutex
func foo() {
l.Lock()
defer l.Unlock()
}
- 全局唯一操作
對于從全局角度只需要運行一次的代碼,比如全局初始化操作拔恰,Go提供了一個Once類型來保證全局的唯一性操作因谎。
var once sync.Once
once.Do(func)
五、網(wǎng)絡(luò)編程
5.1 Socket編程
Go語言使用協(xié)議建立連接仁连,只需要調(diào)用net.Dial()即可蓝角。
func Dial(net, addr string) (Conn, error)
conn, err := net.Dial("tcp","192.168.0.10:2100")
conn,err := net.Dial("udp", "192.168.0.12:975")
conn,err := net.Dial("ip4:icmp","www.baidu.com") //ICMP連接
在建立成功后就可以用conn的Write()寫數(shù)據(jù),Read()讀數(shù)據(jù)
5.2HTTP編程
5.3RPC編程
RPC 是一種通過網(wǎng)絡(luò)從遠程計算機上請求服務(wù)饭冬,而不需要了解底層網(wǎng)絡(luò)細節(jié)的應(yīng)用程序通信協(xié)議使鹅。采用CS模式。
- Go中的RPC支持與處理
在net/rpc中實現(xiàn)了RPC昌抠,其允許客戶端程序通過網(wǎng)絡(luò)或者其他IO連接調(diào)用一個遠端對象的公開方法患朱。
服務(wù)端:將對象注冊為可訪問的服務(wù),該對象的方法需滿足:
必須是公開的炊苫、必須兩個參數(shù)裁厅,且參數(shù)類型都必須是包外部可訪問的或者是內(nèi)置類型冰沙、第二個參數(shù)必須是一個指針、必須返回一個error類型的值执虹。
func (t *T) MethodName(argType T1, relpayType *T2) error
第一個參數(shù)表示客戶端傳入的參數(shù)拓挥,第二個參數(shù)表示要返回給客戶端的結(jié)果。
RPC服務(wù)端通過調(diào)用rpc.ServerConn處理單個連接請求袋励。
RPC客戶端侥啤,可用rpc.Dial() 或rpc.DialHTTP()方法來與指定的RPC服務(wù)端建立連接。建立連接后茬故,可以同步或異步的方式接受RPC服務(wù)端的處理結(jié)果盖灸。調(diào)用RPC客戶端的Call()方法則進行同步處理。調(diào)用客戶端的Go()方法時磺芭,可以進行異步處理赁炎。
//服務(wù)定義
package math
type MathService struct{}
func (m *MathService) Add(args *Args, reply *int) error {
*reply = args.A + args.B
return nil
}
type Args struct {
A, B int
}
// 服務(wù)端
func main() {
mathService := new(math.MathService)
rpc.Register(mathService)
listener, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal("Error starting listener:", err)
}
defer listener.Close()
fmt.Println("Server is listening on port 1234")
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal("Error accepting connection:", err)
}
go rpc.ServeConn(conn)
}
}
//客戶端
func main() {
client, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
log.Fatal("Error connecting to server:", err)
}
args := &math.Args{A: 10, B: 5}
var reply int
err = client.Call("MathService.Add", args, &reply)
if err != nil {
log.Fatal("Error calling MathService.Add:", err)
}
fmt.Printf("Add result: %d + %d = %d\n", args.A, args.B, reply)
}
5.4 JSON處理
- 編碼為JSON格式
json.Marshal() 可對一組數(shù)據(jù)進行JSON編碼,將結(jié)構(gòu)體轉(zhuǎn)為json
json.Unmarshal(jsonData, &結(jié)構(gòu)體對象) 將json轉(zhuǎn)為結(jié)構(gòu)體