語(yǔ)言特性
- 自動(dòng)垃圾回收
- 更豐富的內(nèi)置類型
- 函數(shù)多返回值
- 錯(cuò)誤處理
- 匿名函數(shù)和閉包
- 類型和接口
- 并發(fā)編程
- 反射
- 語(yǔ)言交互性
自動(dòng)垃圾回收
編程中,一個(gè)難題就是內(nèi)存管理,如果管理不好對(duì)內(nèi)存的使用,發(fā)生內(nèi)存泄露可能會(huì)引發(fā)嚴(yán)重的問(wèn)題,比如使程序崩潰。C和C++這樣的編程語(yǔ)言,提供了指針這樣靈活的類型,但卻必須手動(dòng)管理內(nèi)存,增加了編程的復(fù)雜性。Java等編程語(yǔ)言本身提供了自動(dòng)的垃圾回收機(jī)制,減少了編程的不少負(fù)擔(dān)。go語(yǔ)言融合了眾多編程語(yǔ)言的優(yōu)秀特點(diǎn),自然也引入了自動(dòng)的垃圾回收機(jī)制。
所謂垃圾回收,即所有的內(nèi)存分配動(dòng)作都會(huì)被在運(yùn)行時(shí)記錄,同時(shí)任何對(duì)該內(nèi)存的使用也都會(huì)被記錄,然后垃圾回收器會(huì)對(duì)所有已經(jīng)分配的內(nèi)存進(jìn)行跟蹤監(jiān)測(cè),一旦發(fā)現(xiàn)有些內(nèi)存已經(jīng)不再被任何人使用,就階段性地回收這些沒(méi)人用的內(nèi)存。當(dāng)然,因?yàn)樾枰M量最小化垃圾回收的性能損耗,以及降低對(duì)正常程序執(zhí)行過(guò)程的影響,現(xiàn)實(shí)中的垃圾回收算法要比這個(gè)復(fù)雜得多,比如為對(duì)象增加年齡屬性等,但基本原理都是如此。
更豐富的內(nèi)置類型
go語(yǔ)言不僅提供了幾乎所有語(yǔ)言都提供的整形,浮點(diǎn)型等簡(jiǎn)單類型,數(shù)組和字符串等高級(jí)類型,還提供了字典類型(map),數(shù)組切片(Slice)。
函數(shù)多返回值
Go語(yǔ)言革命性地在靜態(tài)開(kāi)發(fā)語(yǔ)言陣營(yíng)中率先提供了多返回值功能。這個(gè)特性讓開(kāi)發(fā)者可以從原來(lái)用各種比較別扭的方式返回多個(gè)值的痛苦中解脫出來(lái),既不用再區(qū)分參數(shù)列表中哪幾個(gè)用于輸入,哪幾個(gè)用于輸出,也不用再只為了返回多個(gè)值而專門定義一個(gè)數(shù)據(jù)結(jié)構(gòu)。
go語(yǔ)言函數(shù)返回多個(gè)值的簡(jiǎn)單示例:
func getName() (firstName, middleName, lastName, nickName string) {
return "May", "M", "Chen", "Babe"
}
因?yàn)榉祷刂刀家呀?jīng)有名字,因此各個(gè)返回值也可以用如下方式來(lái)在不同的位置進(jìn)行賦值,從而提供了極大的靈活性:
func getName() (firstName, middleName, lastName, nickName string) {
firstName = "May"
middleName = "M"
lastName = "Chen"
nickName = "Babe"
return
}
并不是每一個(gè)返回值都必須賦值,沒(méi)有被明確賦值的返回值將保持默認(rèn)的空值。
多返回值函數(shù)調(diào)用:
fn, mn, ln, nn := getName()
如果只對(duì)該函數(shù)其中的某幾個(gè)返回值感興趣的話,也可以直接用下劃線作為占位符來(lái)忽略其他不關(guān)心的返回值:
_, _, lastName, _ := getName()
錯(cuò)誤處理
Go語(yǔ)言引入了3個(gè)關(guān)鍵字用于標(biāo)準(zhǔn)的錯(cuò)誤處理流程。
- defer
- panic
- recover
與C++和Java等語(yǔ)言中的異常捕獲機(jī)制相比, Go語(yǔ)言的錯(cuò)誤處理機(jī)制可以大量減少代碼量,讓開(kāi)發(fā)者也無(wú)需僅僅為了程序安全性而添加大量一層套一層的try-catch語(yǔ)句。
代碼中過(guò)多的嵌套,真是讓人抓狂。
匿名函數(shù)和閉包
在Go語(yǔ)言中,所有的函數(shù)也是值類型,可以作為參數(shù)傳遞。
類型和接口
Go語(yǔ)言不支持繼承和重載,而只是支持了最基本的類型組合功能。
Go語(yǔ)言引入了一個(gè)無(wú)比強(qiáng)大的“非侵入式” 接口的概念,讓開(kāi)發(fā)者從以往對(duì)C++和Java開(kāi)發(fā)中的接口管理問(wèn)題中解脫出來(lái)。
go語(yǔ)言簡(jiǎn)單的接口使用示例:
type Bird struct {
...
}
func (b *Bird) Fly() { //以鳥的方式飛行
}
在實(shí)現(xiàn)Bird類型時(shí)完全沒(méi)有任何IFly的信息。我們可以在另外一個(gè)地方定義這個(gè)IFly。
type IFly interface {
Fly()
}
上面兩個(gè)定義看起來(lái)絲毫沒(méi)有關(guān)系,現(xiàn)在看看如何使用它們:
func main() {
var fly IFly = new(Bird)
fly.Fly()
}
可以看出,雖然Bird類型實(shí)現(xiàn)的時(shí)候,沒(méi)有聲明與接口IFly的關(guān)系,但接口和類型可以直接轉(zhuǎn)換,甚至接口的定義都不用在類型定義之前,這種比較松散的對(duì)應(yīng)關(guān)系可以大幅降低因?yàn)榻涌谡{(diào)整而導(dǎo)致的大量代碼調(diào)整工作。
并發(fā)編程
Go語(yǔ)言引入了goroutine概念,它使得并發(fā)編程變得非常簡(jiǎn)單。通過(guò)使用goroutine而不是裸用操作系統(tǒng)的并發(fā)機(jī)制,以及使用消息傳遞而不是共享內(nèi)存來(lái)通信,Go語(yǔ)言讓并發(fā)編程變得更加輕盈和安全。
通過(guò)在函數(shù)調(diào)用前使用關(guān)鍵字go,我們即可讓該函數(shù)以goroutine方式執(zhí)行。goroutine是一種比線程更加輕盈、更省資源的協(xié)程。Go語(yǔ)言通過(guò)系統(tǒng)的線程來(lái)多路派遣這些函數(shù)的執(zhí)行,使得每個(gè)用go關(guān)鍵字執(zhí)行的函數(shù)可以運(yùn)行成為一個(gè)單位協(xié)程。當(dāng)一個(gè)協(xié)程阻塞的時(shí)候,調(diào)度器就會(huì)自動(dòng)把其他協(xié)程安排到另外的線程中去執(zhí)行,從而實(shí)現(xiàn)了程序無(wú)等待并行化運(yùn)行。而且調(diào)度的開(kāi)銷非常小,這使得我們能夠創(chuàng)建大量的goroutine,從而可以很輕松地編寫高并發(fā)程序。
Go語(yǔ)言實(shí)現(xiàn)了CSP(通信順序進(jìn)程,Communicating Sequential Process)模型來(lái)作為goroutine間的推薦通信方式。在CSP模型中,一個(gè)并發(fā)系統(tǒng)由若干并行運(yùn)行的順序進(jìn)程組成,每個(gè)進(jìn)程不能對(duì)其他進(jìn)程的變量賦值。進(jìn)程之間只能通過(guò)一對(duì)通信原語(yǔ)實(shí)現(xiàn)協(xié)作。Go語(yǔ)言用channel(通道) 這個(gè)概念來(lái)輕巧地實(shí)現(xiàn)了CSP模型。channel的使用方式比較接近Unix系統(tǒng)中的管道(pipe)概念,可以方便地進(jìn)行跨goroutine的通信。
另外,由于一個(gè)進(jìn)程內(nèi)創(chuàng)建的所有g(shù)oroutine運(yùn)行在同一個(gè)內(nèi)存地址空間中,因此如果不同的goroutine不得不去訪問(wèn)共享的內(nèi)存變量,訪問(wèn)前應(yīng)該先獲取相應(yīng)的讀寫鎖。Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)中的sync包提供了完備的讀寫鎖功能。
反射
Go語(yǔ)言的反射實(shí)現(xiàn)了反射的大部分功能,但沒(méi)有像Java語(yǔ)言那樣內(nèi)置類型工廠,所以無(wú)法做到像Java那樣通過(guò)類型字符串創(chuàng)建對(duì)象實(shí)例。
反射最常見(jiàn)的使用場(chǎng)景是做對(duì)象的序列化(serialization)。 例如,Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)的encoding/json、encoding/xml、encoding/gob、encoding/binary等包就大量依賴于反射功能來(lái)實(shí)現(xiàn)。
go語(yǔ)言發(fā)射的簡(jiǎn)單示例:
package main
import (
"fmt"
"reflect"
)
type Bird struct {
Name string
LifeExpectance int
}
func (b *Bird) Fly() {
fmt.Println("I am flying...")
}
func main() {
sparrow := &Bird{"Sparrow", 3}
s := reflect.ValueOf(sparrow).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
fmt.Printf("%d: %s %s = %v\n", i, typeOfT.Field(i).Name, f.Type(),
f.Interface())
}
}
輸出結(jié)果:
0: Name string = Sparrow
1: LifeExpectance int = 3
語(yǔ)言交互性
Go語(yǔ)言為了方便的重用現(xiàn)有的C模塊,提供了Cgo。
在Go代碼中,可以按Cgo的特定語(yǔ)法混合編寫C語(yǔ)言代碼,然后Cgo工具可以將這些混合的C代碼提取并生成對(duì)于C功能的調(diào)用包裝代碼。
Go語(yǔ)言使用C模塊的簡(jiǎn)單示例:
package main
/*
#include <stdio.h>
*/
import "C"
import "unsafe"
func main() {
cstr := C.CString("Hello, world")
C.puts(cstr)
C.free(unsafe.Pointer(cstr))
}
語(yǔ)言結(jié)構(gòu)
基礎(chǔ)組成
- 包聲明
- 引入包
- 函數(shù)
- 變量
- 語(yǔ)句 & 表達(dá)式
- 注釋
簡(jiǎn)單示例
學(xué)習(xí)一門語(yǔ)言,從輸出“Hello,world!”開(kāi)始。
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")/* 輸出Hello, World! */
}
-
package main定義了包名。必須在源文件中非注釋的第一行指明這個(gè)文件屬于哪個(gè)包。package main表示一個(gè)可獨(dú)立執(zhí)行的程序,每個(gè) Go 應(yīng)用程序都包含一個(gè)名為main的包。 -
import "fmt"告訴 Go 編譯器這個(gè)程序需要使用fmt包中的內(nèi)容。 -
func main()是程序開(kāi)始執(zhí)行的函數(shù)。main函數(shù)是每一個(gè)可執(zhí)行程序所必須包含的。 -
/*...*/是多行注釋。你也可以使用單行注釋// -
fmt.Println("Hello, World!"),使用fmt包中的輸出函數(shù)Println將“Hello, World!”字符串輸出到控制臺(tái)。
執(zhí)行g(shù)o程序
- 在你的機(jī)器上,安裝go語(yǔ)言開(kāi)發(fā)環(huán)境。
- 在你設(shè)置的工作目錄下,新建hello.go文件。
- 將上面示例代碼復(fù)制到hello.go文件中。
- 進(jìn)入hello.go所在目錄,執(zhí)行g(shù)o run hello.go命令行
$ go run hello.go
Hello, World!