本章將簡(jiǎn)要介紹Go語(yǔ)言的發(fā)展歷史和關(guān)鍵的語(yǔ)言特性唧取,并引領(lǐng)讀者對(duì)Go語(yǔ)言的主要特性進(jìn)行一次快速全面的瀏覽枫弟,讓讀者對(duì)Go語(yǔ)言的總體情況有一個(gè)清晰的印象骇塘,并能夠快速上手款违,用 Go 語(yǔ)言編寫和運(yùn)行自己的第一個(gè)小程序奠货。
語(yǔ)言簡(jiǎn)史
提起 Go 語(yǔ)言的出身递惋,我們就必須將我們飽含敬意的眼光投向持續(xù)推出驚世駭俗成果的貝爾實(shí)驗(yàn)室睛廊。貝爾實(shí)驗(yàn)室已經(jīng)走出了多位諾貝爾獎(jiǎng)獲得者超全,一些對(duì)于現(xiàn)在科技至關(guān)重要的研究成果, 比如晶體管疏遏、通信技術(shù)财异、數(shù)碼相機(jī)的感光元件 CCD 和光電池等都源自貝爾實(shí)驗(yàn)室。該實(shí)驗(yàn)室在科技界的地位可想而之唱遭,是一個(gè)毫無(wú)爭(zhēng)議的科研圣地戳寸。
這里我們重點(diǎn)介紹一下貝爾實(shí)驗(yàn)室中一個(gè)叫計(jì)算科學(xué)研究中心的部門對(duì)于操作系統(tǒng)和編程語(yǔ)言的貢獻(xiàn)】皆螅回溯至1969年(估計(jì)大部分讀者那時(shí)候都還沒(méi)出世)疫鹊,肯·湯普遜(Ken Thompson) 和丹尼斯·里奇(Dennis Ritchie)在貝爾實(shí)驗(yàn)室的計(jì)算科學(xué)研究中心里開發(fā)出了Unix這個(gè)大名鼎鼎的操作系統(tǒng),還因?yàn)殚_發(fā)Unix而衍生出了一門同樣赫赫有名的編程語(yǔ)言——C語(yǔ)言司致。對(duì)于很大一部分人而言,Unix就是操作系統(tǒng)的鼻祖蚌吸,C語(yǔ)言也是計(jì)算機(jī)課程中廣泛使用的編程語(yǔ)言锈拨。Unix 和C語(yǔ)言在過(guò)去的幾十年以來(lái)已經(jīng)造就了無(wú)數(shù)的成功商業(yè)故事,比如曾在90年代如日中天的太陽(yáng)微系統(tǒng)(Sun MicroSystems)羹唠,現(xiàn)在正如日中天的蘋果的Mac OS X操作系統(tǒng)其實(shí)也可以認(rèn)為是Unix 的一個(gè)變種(FreeBSD)奕枢。
雖然已經(jīng)取得了如此巨大的成就娄昆,貝爾實(shí)驗(yàn)室的這幾個(gè)人并沒(méi)有因此而沉浸在光環(huán)中止步不前,他們從20世紀(jì)80年代又開始了一個(gè)名為Plan 9的操作系統(tǒng)研究項(xiàng)目缝彬,目的就是解決Unix中的一些問(wèn)題萌焰,發(fā)展出一個(gè)Unix的后續(xù)替代系統(tǒng)。在之后的幾十年中谷浅,該研究項(xiàng)目又演變出了另一個(gè) 叫Inferno的項(xiàng)目分支扒俯,以及一個(gè)名為L(zhǎng)imbo的編程語(yǔ)言。
Limbo是用于開發(fā)運(yùn)行在小型計(jì)算機(jī)上的分布式應(yīng)用的編程語(yǔ)言一疯,它支持模塊化編程撼玄,編譯期和運(yùn)行時(shí)的強(qiáng)類型檢查,進(jìn)程內(nèi)基于具有類型的通信通道墩邀,原子性垃圾收集和簡(jiǎn)單的抽象數(shù)據(jù)類型掌猛。它被設(shè)計(jì)為:即便是在沒(méi)有硬件內(nèi)存保護(hù)的小型設(shè)備上,也能安全運(yùn)行眉睹。
Limbo語(yǔ)言被認(rèn)為是Go語(yǔ)言的前身荔茬,不僅僅因?yàn)槭峭慌嗽O(shè)計(jì)的語(yǔ)言,而是Go語(yǔ)言確實(shí)從 Limbo語(yǔ)言中繼承了眾多優(yōu)秀的特性竹海。
貝爾實(shí)驗(yàn)室后來(lái)經(jīng)歷了多次的動(dòng)蕩慕蔚,包括肯·湯普遜在內(nèi)的Plan 9項(xiàng)目原班人馬加入了Google。在Google斋配,他們創(chuàng)造了Go語(yǔ)言孔飒。早在2007年9月,Go語(yǔ)言還是這幫大牛的20%自由時(shí)間的實(shí)驗(yàn)項(xiàng)目许起。幸運(yùn)的是,到了2008年5月菩鲜,Google發(fā)現(xiàn)了Go語(yǔ)言的巨大潛力园细,從而開始全力支持這個(gè)項(xiàng)目,讓這批人可以全身心投入Go語(yǔ)言的設(shè)計(jì)和開發(fā)工作中接校。Go語(yǔ)言的第一個(gè)版本在2009 年11月正式對(duì)外發(fā)布猛频,并在此后的兩年內(nèi)快速迭代,發(fā)展迅猛蛛勉。第一個(gè)正式版本的Go語(yǔ)言于2012 年3月28日正式發(fā)布鹿寻,讓Go語(yǔ)言迎來(lái)了第一個(gè)引人矚目的里程碑。
基于Google對(duì)開源的一貫擁抱態(tài)度诽凌, Go語(yǔ)言也自然而然地選擇了開源方式發(fā)布毡熏,并使用BSD 授權(quán)協(xié)議。任何人可以查看Go語(yǔ)言的所有源代碼侣诵,并可以為Go語(yǔ)言發(fā)展而奉獻(xiàn)自己的力量痢法。
Google作為Go語(yǔ)言的主推者狱窘,并沒(méi)有簡(jiǎn)簡(jiǎn)單單地把語(yǔ)言推給開源社區(qū)了事,它不僅組建了一 個(gè)獨(dú)立的小組全職開發(fā)Go語(yǔ)言财搁,還在自家的服務(wù)中逐步增加對(duì)Go語(yǔ)言的支持蘸炸,比如對(duì)于Google 有戰(zhàn)略意義的云計(jì)算平臺(tái)GAE(Google AppEngine)很早就開始支持Go語(yǔ)言了。按目前的發(fā)展態(tài)勢(shì)尖奔,在Google內(nèi)部搭儒,Go語(yǔ)言有逐漸取代Java和Python主流地位的趨勢(shì)。在Google的更多產(chǎn)品中提茁, 我們將看到Go語(yǔ)言的蹤影淹禾,比如Google核心的搜索和廣告業(yè)務(wù)。
在這里我們已經(jīng)清晰詮釋了為什么在語(yǔ)言泛濫的時(shí)代Google還要設(shè)計(jì)和推出一門新的編程語(yǔ)言甘凭。按照已經(jīng)發(fā)布的Go語(yǔ)言的特性稀拐,我們有足夠的理由相信Google推出此門新編程語(yǔ)言絕不僅僅是簡(jiǎn)單的跑馬圈地運(yùn)動(dòng),而是為了解決切實(shí)的問(wèn)題丹弱。
- 下面我們?cè)賮?lái)看看Go語(yǔ)言的主要作者德撬。
? 肯·湯普遜:設(shè)計(jì)了B語(yǔ)言 和C語(yǔ)言,創(chuàng)建了Unix和Plan 9操作系統(tǒng)躲胳,1983年圖靈獎(jiǎng)得主蜓洪,Go語(yǔ)言的共同作者。
? 羅布·派克:Unix小組的成員坯苹,參與Plan 9和Inferno操作系統(tǒng)隆檀,參與 Limbo和Go語(yǔ)言的研發(fā),《Unix編程環(huán)境》作者之一粹湃。
? 羅伯特·格里澤默:曾協(xié)助制作Java的HotSpot編譯器和Chrome瀏覽器的JavaScript引擎V8恐仑。
? 拉斯· 考克斯:參與Plan 9操作系統(tǒng)的開發(fā),Google Code Search項(xiàng)目負(fù)責(zé)人为鳄。
? 伊安·泰勒:GCC社區(qū)的活躍人物裳仆,gold連接器和GCC過(guò)程間優(yōu)化LTO 的主要設(shè)計(jì)者,Zembu公司的創(chuàng)始人孤钦。
? 布拉德·菲茨帕特里克: LiveJournal的創(chuàng)始人歧斟,著名開源項(xiàng)目memcached的作者。雖然我們這里只列出了一部分偏形,大家已經(jīng)可以看出這個(gè)語(yǔ)言開發(fā)團(tuán)隊(duì)空前強(qiáng)大静袖,這讓我們?cè)跒镚o語(yǔ)言的優(yōu)秀特性而興奮之外,還非晨∨ぃ看好這門語(yǔ)言的發(fā)展前景队橙。
語(yǔ)言特性
Go語(yǔ)言作為一門全新的靜態(tài)類型開發(fā)語(yǔ)言,與當(dāng)前的開發(fā)語(yǔ)言相比具備眾多令人興奮不已的新特性。本書從第2章開始喘帚,我們將對(duì)Go語(yǔ)言的各個(gè)方面進(jìn)行詳細(xì)解析畅姊,讓讀者能夠盡量輕松地掌握這門簡(jiǎn)潔、有趣卻又超級(jí)強(qiáng)大的新語(yǔ)言吹由。
- 這里先給讀者羅列一下Go語(yǔ)言主要的特性:
? 自動(dòng)垃圾回收
? 更豐富的內(nèi)置類型
? 函數(shù)多返回值
? 錯(cuò)誤處理
? 匿名函數(shù)和閉包
? 類型和接口
? 并發(fā)編程
? 反射
? 語(yǔ)言交互性
- 自動(dòng)垃圾回收
我們可以先看下不支持垃圾回收的語(yǔ)言的資源管理方式若未,以下為一小段C語(yǔ)言代碼:
void foo() { char* p = new char[128]; ... // 對(duì)p指向的內(nèi)存塊進(jìn)行賦值 func1(p); // 使用內(nèi)存指針 delete[] p; }
各種非預(yù)期的原因,比如由于開發(fā)者的疏忽導(dǎo)致后的delete語(yǔ)句沒(méi)有被調(diào)用倾鲫,都會(huì)引發(fā)經(jīng)典而惱人的內(nèi)存泄露問(wèn)題粗合。假如該函數(shù)被調(diào)用得非常頻繁,那么我們觀察該進(jìn)程執(zhí)行時(shí)乌昔,會(huì)發(fā)現(xiàn)該進(jìn)程所占用的內(nèi)存會(huì)一直瘋長(zhǎng)隙疚,直至占用所有系統(tǒng)內(nèi)存并導(dǎo)致程序崩潰,而如果泄露的是系統(tǒng)資源的話磕道,那么后果還會(huì)更加嚴(yán)重供屉,終很有可能導(dǎo)致系統(tǒng)崩潰。
手動(dòng)管理內(nèi)存的另外一個(gè)問(wèn)題就是由于指針的到處傳遞而無(wú)法確定何時(shí)可以釋放該指針?biāo)赶虻膬?nèi)存塊溺蕉。假如代碼中某個(gè)位置釋放了內(nèi)存伶丐,而另一些地方還在使用指向這塊內(nèi)存的指針, 那么這些指針就變成了所謂的“野指針”(wild pointer)或者“懸空指針”(dangling pointer)疯特,對(duì)這些指針進(jìn)行的任何讀寫操作都會(huì)導(dǎo)致不可預(yù)料的后果哗魂。
由于其杰出的效率,C和C++語(yǔ)言在非常長(zhǎng)的時(shí)間內(nèi)都作為服務(wù)端系統(tǒng)的主要開發(fā)語(yǔ)言漓雅,比如Apache录别、Nginx和MySQL等著名的服務(wù)器端軟件就是用C和C++開發(fā)的。然而邻吞,內(nèi)存和資源管理一直是一個(gè)讓人非常抓狂的難題组题。服務(wù)器的崩潰十有八九就是因?yàn)椴徽_的內(nèi)存和資源管理導(dǎo)致,更討厭的是這種內(nèi)存和資源管理問(wèn)題即使被發(fā)現(xiàn)了抱冷,也很難定位到具體的錯(cuò)誤地點(diǎn)崔列,導(dǎo)致無(wú)數(shù)程序員通宵達(dá)旦地調(diào)試程序。
這個(gè)問(wèn)題在多年里被不同人用不同的方式來(lái)試圖解決徘层,并誕生了一些非常著名的內(nèi)存檢查工具峻呕,比如Rational Purify利职、Compuware BoundsChecker和英特爾的Parallel Inspector等趣效。從設(shè)計(jì)方法的角度也衍生了類似于內(nèi)存引用計(jì)數(shù)之類的方法(通常被稱為“智能指針”),后續(xù)在Windows平臺(tái)上標(biāo)準(zhǔn)化的COM出現(xiàn)的一個(gè)重要原因就是為了解決內(nèi)存管理的難題猪贪。但是事實(shí)證明跷敬,這些工具和方法雖然能夠在一定程度上輔助開發(fā)者,但并沒(méi)法讓開發(fā)者避免通宵調(diào)試這樣又苦又累的工作热押。
到目前為止西傀,內(nèi)存泄露的最佳解決方案是在語(yǔ)言級(jí)別引入自動(dòng)垃圾回收算法(Garbage Collection斤寇,簡(jiǎn)稱GC)。所謂垃圾回收拥褂,即所有的內(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ì)象增加年齡屬性等缝驳,但基本原理都是如此。
自動(dòng)垃圾回收在C/C++社區(qū)一直作為一柄雙刃劍看待归苍,雖然到C++0x(后命名為C++11)正 式發(fā)布時(shí)用狱,這個(gè)呼聲頗高的特性總算是被加入了,但按C++之父的說(shuō)法霜医,由于C++本身過(guò)于強(qiáng)大齿拂, 導(dǎo)致在C++中支持垃圾收集變成了一個(gè)困難的工作。假如C++支持垃圾收集肴敛,以下的代碼片段在運(yùn)行時(shí)就會(huì)是一個(gè)嚴(yán)峻的考驗(yàn):int* p = new int; p += 10; // 對(duì)指針進(jìn)行了偏移署海,因此那塊內(nèi)存不再被引用 // …… 這里可能會(huì)發(fā)生針對(duì)這塊int內(nèi)存的垃圾收集 …… p -= 10; // 咦,居然又偏移到原來(lái)的位置 *p = 10; // 如果有垃圾收集医男,這里就無(wú)法保證可以正常運(yùn)行了
微軟的C++/CLI算是用一種偏門的方式讓C++程序員們有機(jī)會(huì)品嘗一下垃圾回收功能的鮮美味道砸狞。在C/C++之后出現(xiàn)的新語(yǔ)言,比如Java和C#等镀梭,基本上都已經(jīng)自帶自動(dòng)垃圾回收功能刀森。
Go語(yǔ)言作為一門新生的開發(fā)語(yǔ)言,當(dāng)然不能忽略內(nèi)存管理這個(gè)問(wèn)題报账。又因?yàn)镚o語(yǔ)言沒(méi)有C++ 這么“強(qiáng)大”的指針計(jì)算功能研底,因此可以很自然地包含垃圾回收功能。因?yàn)槔厥展δ艿闹С郑?開發(fā)者無(wú)需擔(dān)心所指向的對(duì)象失效的問(wèn)題透罢,因此Go語(yǔ)言中不需要delete關(guān)鍵字榜晦,也不需要free() 方法來(lái)明確釋放內(nèi)存。例如羽圃,對(duì)于以上的這個(gè)C語(yǔ)言例子乾胶,如果使用Go語(yǔ)言實(shí)現(xiàn),我們就完全不用考慮何時(shí)需要釋放之前分配的內(nèi)存的問(wèn)題,系統(tǒng)會(huì)自動(dòng)幫我們判斷识窿,并在合適的時(shí)候(比如CPU 相對(duì)空閑的時(shí)候)進(jìn)行自動(dòng)垃圾收集工作斩郎。
- 更豐富的內(nèi)置類型
除了幾乎所有語(yǔ)言都支持的簡(jiǎn)單內(nèi)置類型(比如整型和浮點(diǎn)型等)外,Go語(yǔ)言也內(nèi)置了一 些比較新的語(yǔ)言中內(nèi)置的高級(jí)類型喻频,比如C#和Java中的數(shù)組和字符串缩宜。除此之外,Go語(yǔ)言還內(nèi)置了一個(gè)對(duì)于其他靜態(tài)類型語(yǔ)言通常用庫(kù)方式支持的字典類型(map)甥温。Go語(yǔ)言設(shè)計(jì)者對(duì)為什么內(nèi)置map這個(gè)問(wèn)題的回答也頗為簡(jiǎn)單:既然絕大多數(shù)開發(fā)者都需要用到這個(gè)類型脓恕,為什么還非要每個(gè)人都寫一行import語(yǔ)句來(lái)包含一個(gè)庫(kù)?這也是一個(gè)典型的實(shí)戰(zhàn)派觀點(diǎn)窿侈,與很多其他語(yǔ)言的學(xué)院派氣息迥然不同炼幔。
另外有一個(gè)新增的數(shù)據(jù)類型:數(shù)組切片(Slice)。我們可以認(rèn)為數(shù)組切片是一種可動(dòng)態(tài)增長(zhǎng)的數(shù)組史简。這幾種數(shù)據(jù)結(jié)構(gòu)基本上覆蓋了絕大部分的應(yīng)用場(chǎng)景乃秀。數(shù)組切片的功能與C++標(biāo)準(zhǔn)庫(kù)中的vector非常類似。Go語(yǔ)言在語(yǔ)言層面對(duì)數(shù)組切片的支持圆兵,相比C++開發(fā)者有效地消除了反復(fù)寫以下幾行代碼的工作量:#include <vector> #include <map> #include <algorithm> using namespace std;
因?yàn)槭钦Z(yǔ)言內(nèi)置特性跺讯,開發(fā)者根本不用費(fèi)事去添加依賴的包,既可以少一些輸入工作量殉农,也可以讓代碼看起來(lái)盡量簡(jiǎn)潔刀脏。
- 函數(shù)多返回值
目前的主流語(yǔ)言中除Python外基本都不支持函數(shù)的多返回值功能,不是沒(méi)有這類需求超凳,可能是語(yǔ)言設(shè)計(jì)者沒(méi)有想好該如何提供這個(gè)功能愈污,或者認(rèn)為這個(gè)功能會(huì)影響語(yǔ)言的美感。
比如我們?nèi)绻x一個(gè)函數(shù)用于返回個(gè)人名字信息轮傍,而名字信息因?yàn)榘鄠€(gè)部分——姓氏暂雹、名字、中間名和別名创夜,在不支持多返回值的語(yǔ)言中我們有以下兩種做法:要么專門定義一個(gè)結(jié)構(gòu)體用于返回杭跪,比如:struct name { char first_name[20]; char middle_name[20]; char last_name[20]; char nick_name[48]; }; // 函數(shù)原型 extern name get_name(); // 函數(shù)調(diào)用 name n = get_name();
或者以傳出參數(shù)的方式返回多個(gè)結(jié)果:
// 函數(shù)原型 extern void get_name( /*out*/char* first_name, /*out*/char* middle_name, /*out*/char* last_name, /*out*/char* nick_name); // 先分配內(nèi)存 char first_name[20]; char middle_name[20]; char last_name[20]; char nick_name[48]; // 函數(shù)調(diào)用 get_name(first_name, middle_name, last_name, nick_name);
Go語(yǔ)言革命性地在靜態(tài)開發(fā)語(yǔ)言陣營(yíng)中率先提供了多返回值功能。這個(gè)特性讓開發(fā)者可以從原來(lái)用各種比較別扭的方式返回多個(gè)值的痛苦中解脫出來(lái)驰吓,既不用再區(qū)分參數(shù)列表中哪幾個(gè)用于輸入涧尿,哪幾個(gè)用于輸出,也不用再只為了返回多個(gè)值而專門定義一個(gè)數(shù)據(jù)結(jié)構(gòu)檬贰。
在Go語(yǔ)言中姑廉,上述的例子可以修改為以下的樣子: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)用相比C/C++語(yǔ)言要簡(jiǎn)化很多:
fn, mn, ln, nn := getName()
如果開發(fā)者只對(duì)該函數(shù)其中的某幾個(gè)返回值感興趣的話迷雪,也可以直接用下劃線作為占位符來(lái)忽略其他不關(guān)心的返回值限书。下面的調(diào)用表示調(diào)用者只希望接收l(shuí)astName的值,這樣可以避免聲明完全沒(méi)用的變量:
_, _, lastName, _ := getName()
我們會(huì)在下一章中詳細(xì)講解多重返回值的用法章咧。
- 錯(cuò)誤處理
Go語(yǔ)言引入了3個(gè)關(guān)鍵字用于標(biāo)準(zhǔn)的錯(cuò)誤處理流程倦西,這3個(gè)關(guān)鍵字分別為defer、panic和 recover赁严。在第2章中我們還會(huì)詳細(xì)描述Go語(yǔ)言錯(cuò)誤處理機(jī)制的獨(dú)特之處扰柠。整體上而言與C++和Java等語(yǔ)言中的異常捕獲機(jī)制相比, Go語(yǔ)言的錯(cuò)誤處理機(jī)制可以大量減少代碼量疼约,讓開發(fā)者也無(wú)需僅僅為了程序安全性而添加大量一層套一層的try-catch語(yǔ)句卤档。這對(duì)于代碼的閱讀者和維護(hù)者來(lái)說(shuō)也是一件很好的事情,因?yàn)榭梢员苊庠趯訉拥拇a嵌套中定位業(yè)務(wù)代碼程剥。下一章中將介紹Go語(yǔ)言中的錯(cuò)誤處理機(jī)制劝枣。
- 匿名函數(shù)和閉包
在Go語(yǔ)言中,所有的函數(shù)也是值類型织鲸,可以作為參數(shù)傳遞舔腾。Go語(yǔ)言支持常規(guī)的匿名函數(shù)和閉包,比如下列代碼就定義了一個(gè)名為f的匿名函數(shù)搂擦,開發(fā)者可以隨意對(duì)該匿名函數(shù)變量進(jìn)行傳遞和調(diào)用:
f := func(x, y int) int { return x + y }
- 類型和接口
Go語(yǔ)言的類型定義非常接近于C語(yǔ)言中的結(jié)構(gòu)(struct)稳诚,甚至直接沿用了struct關(guān)鍵字。相比而言瀑踢,Go語(yǔ)言并沒(méi)有直接沿襲C++和Java的傳統(tǒng)去設(shè)計(jì)一個(gè)超級(jí)復(fù)雜的類型系統(tǒng)扳还,不支持繼承和重載,而只是支持了基本的類型組合功能橱夭。
巧妙的是普办,雖然看起來(lái)支持的功能過(guò)于簡(jiǎn)潔,細(xì)用起來(lái)你卻會(huì)發(fā)現(xiàn)徘钥,C++和Java使用那些復(fù)雜的類型系統(tǒng)實(shí)現(xiàn)的功能在Go語(yǔ)言中并不會(huì)出現(xiàn)無(wú)法表現(xiàn)的情況衔蹲,這反而讓人反思其他語(yǔ)言中引入這些復(fù)雜概念的必要性。我們?cè)诘?章中將詳細(xì)描述Go語(yǔ)言的類型系統(tǒng)呈础。
Go語(yǔ)言也不是簡(jiǎn)單的對(duì)面向?qū)ο箝_發(fā)語(yǔ)言做減法舆驶,它還引入了一個(gè)無(wú)比強(qiáng)大的“非侵入式” 接口的概念,讓開發(fā)者從以往對(duì)C++和Java開發(fā)中的接口管理問(wèn)題中解脫出來(lái)而钞。在C++中沙廉,我們通常會(huì)這樣來(lái)確定接口和類型的關(guān)系:// 抽象接口 interface IFly { virtual void Fly()=0; }; // 實(shí)現(xiàn)類 class Bird : public IFly { public: Bird() {} virtual ~Bird() {} public: void Fly() { // 以鳥的方式飛行 } }; void main() { IFly* pFly = new Bird(); pFly->Fly(); delete pFly; }
顯然,在實(shí)現(xiàn)一個(gè)接口之前必須先定義該接口臼节,并且將類型和接口緊密綁定撬陵,即接口的修改會(huì)影響到所有實(shí)現(xiàn)了該接口的類型珊皿,而Go語(yǔ)言的接口體系則避免了這類問(wèn)題:
type Bird struct { ... } func (b *Bird) Fly() { // 以鳥的方式飛行 }
我們?cè)趯?shí)現(xiàn)Bird類型時(shí)完全沒(méi)有任何IFly的信息。我們可以在另外一個(gè)地方定義這個(gè)IFly 接口:
type IFly interface { Fly() }
這兩者目前看起來(lái)完全沒(méi)有關(guān)系巨税,現(xiàn)在看看我們?nèi)绾问褂盟鼈儯?/p>
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ī)制驰后,以及使用消息傳遞來(lái)共享內(nèi)存而不是使用共享內(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)度的開銷非常小晓避,一顆CPU調(diào)度的規(guī)模不下于每秒百萬(wàn)次簇捍,這使得我們能夠創(chuàng)建大量的goroutine,從而可以很輕松地編寫高并發(fā)程序俏拱,達(dá)到我們想要的目的暑塑。
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包提供了完備的讀寫鎖功能。
下面我們用一個(gè)簡(jiǎn)單的例子來(lái)演示goroutine和channel的使用方式志于。這是一個(gè)并行計(jì)算的例子涮因,由兩個(gè)goroutine進(jìn)行并行的累加計(jì)算,待這兩個(gè)計(jì)算過(guò)程都完成后打印計(jì)算結(jié)果恨憎,具體代碼如下所示。package main import "fmt" func sum(values [] int, resultChan chan int) { sum := 0 for _, value := range values { sum += value } // 將計(jì)算結(jié)果發(fā)送到channel中 resultChan <- sum } func main() { values := [] int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} resultChan := make(chan int, 2) go sum(values[:len(values)/2], resultChan) go sum(values[len(values)/2:], resultChan) // 接收結(jié)果 sum1, sum2 := <-resultChan, <-resultChan fmt.Println("Result:", sum1, sum2, sum1 + sum2) }
- 反射
反射(reflection)是在Java語(yǔ)言出現(xiàn)后迅速流行起來(lái)的一種概念郊楣。通過(guò)反射憔恳,你可以獲取對(duì)象類型的詳細(xì)信息,并可動(dòng)態(tài)操作對(duì)象净蚤。反射是把雙刃劍钥组,功能強(qiáng)大但代碼可讀性并不理想。若非必要今瀑,我們并不推薦使用反射程梦。
Go語(yǔ)言的反射實(shí)現(xiàn)了反射的大部分功能,但沒(méi)有像Java語(yǔ)言那樣內(nèi)置類型工廠橘荠,故而無(wú)法做 到像Java那樣通過(guò)類型字符串創(chuàng)建對(duì)象實(shí)例屿附。在Java中,你可以讀取配置并根據(jù)類型名稱創(chuàng)建對(duì)應(yīng)的類型哥童,這是一種常見(jiàn)的編程手法挺份,但在Go語(yǔ)言中這并不被推薦。
反射常見(jiàn)的使用場(chǎng)景是做對(duì)象的序列化(serialization贮懈,有時(shí)候也叫Marshal & Unmarshal)匀泊。 例如,Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)的encoding/json朵你、encoding/xml各聘、encoding/gob、encoding/binary等包就大量 依賴于反射功能來(lái)實(shí)現(xiàn)抡医。
這里先舉一個(gè)小例子躲因,可以利用反射功能列出某個(gè)類型中所有成員變量的值,如代碼所示忌傻。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é)果為:
我們會(huì)在后面章中簡(jiǎn)要介紹反射的基本使用方法和注意事項(xiàng)毛仪。
- 語(yǔ)言交互性
由于Go語(yǔ)言與C語(yǔ)言之間的天生聯(lián)系,Go語(yǔ)言的設(shè)計(jì)者們自然不會(huì)忽略如何重用現(xiàn)有C模塊的這個(gè)問(wèn)題芯勘,這個(gè)功能直接被命名為Cgo箱靴。Cgo既是語(yǔ)言特性,同時(shí)也是一個(gè)工具的名稱荷愕。
在Go代碼中衡怀,可以按Cgo的特定語(yǔ)法混合編寫C語(yǔ)言代碼棍矛,然后Cgo工具可以將這些混合的C 代碼提取并生成對(duì)于C功能的調(diào)用包裝代碼。開發(fā)者基本上可以完全忽略這個(gè)Go語(yǔ)言和C語(yǔ)言的邊界是如何跨越的抛杨。
與Java中的JNI不同够委,Cgo的用法非常簡(jiǎn)單,比如下面代碼就可以實(shí)現(xiàn)在Go中調(diào)用C語(yǔ)言標(biāo)準(zhǔn)庫(kù)的puts函數(shù)怖现。package main /* #include <stdio.h> */ import "C" import "unsafe" func main() { cstr := C.CString("Hello, world") C.puts(cstr) C.free(unsafe.Pointer(cstr)) }
我們將在后面章中詳細(xì)介紹Cgo的用法茁帽。
第一個(gè) Go程序
自 Kernighan 和 Ritchie 合著的《C程序設(shè)計(jì)語(yǔ)言》(The C Programming Language)出版以來(lái), 幾乎所有的編程書都以一個(gè)Hello world小例子作為開始屈嗤。我們也不免俗(或者說(shuō)尊重傳統(tǒng))潘拨,下 面我們從一個(gè)簡(jiǎn)單Go語(yǔ)言版本的Hello world來(lái)初窺Go這門新語(yǔ)言的模樣,如代碼所示饶号。
package main // 我們需要使用fmt包中的Println()函數(shù) import "fmt" func main() { fmt.Println("Hello, world. 你好铁追,世界!") }
- 代碼解讀
每個(gè)Go源代碼文件的開頭都是一個(gè)package聲明茫船,表示該Go代碼所屬的包琅束。包是Go語(yǔ)言里基本的分發(fā)單位,也是工程管理中依賴關(guān)系的體現(xiàn)算谈。要生成Go可執(zhí)行程序涩禀,必須建立一個(gè)名字為main的包,并且在該包中包含一個(gè)叫main()的函數(shù)(該函數(shù)是Go可執(zhí)行程序的執(zhí)行起點(diǎn))然眼。
Go語(yǔ)言的main()函數(shù)不能帶參數(shù)埋泵,也不能定義返回值。命令行傳入的參數(shù)在os.Args變量中保存罪治。如果需要支持命令行開關(guān)丽声,可使用flag包。在后面我們將解釋如何使用flag包來(lái)做命令行參數(shù)規(guī)范的定義觉义,以及獲取和解析命令行參數(shù)雁社。
在包聲明之后,是一系列的import語(yǔ)句晒骇,用于導(dǎo)入該程序所依賴的包霉撵。由于本示例程序用到了Println()函數(shù),所以需要導(dǎo)入該函數(shù)所屬的fmt包洪囤。
有一點(diǎn)需要注意徒坡,不得包含在源代碼文件中沒(méi)有用到的包,否則Go編譯器會(huì)報(bào)編譯錯(cuò)誤瘤缩。 這與下面提到的強(qiáng)制左花括號(hào){的放置位置以及之后會(huì)提到的函數(shù)名的大小寫規(guī)則喇完,均體現(xiàn)了Go 語(yǔ)言在語(yǔ)言層面解決軟件工程問(wèn)題的設(shè)計(jì)哲學(xué)。
所有Go函數(shù)(包括在對(duì)象編程中會(huì)提到的類型成員函數(shù))以關(guān)鍵字func開頭剥啤。一個(gè)常規(guī)的函數(shù)定義包含以下部分:
func 函數(shù)名(參數(shù)列表)(返回值列表) {
// 函數(shù)體
}
對(duì)應(yīng)的一個(gè)實(shí)例如下:func Compute(value1 int, value2 float64)(result float64, err error) { // 函數(shù)體 }
Go支持多個(gè)返回值锦溪。以上的示例函數(shù)Compute()返回了兩個(gè)值不脯,一個(gè)叫result,另一個(gè)是 err刻诊。并不是所有返回值都必須賦值防楷。在函數(shù)返回時(shí)沒(méi)有被明確賦值的返回值都會(huì)被設(shè)置為默認(rèn)值,比如result會(huì)被設(shè)為0.0则涯,err會(huì)被設(shè)為nil复局。
Go程序的代碼注釋與C++保持一致,即同時(shí)支持以下兩種用法:
/* 塊注釋 */
// 行注釋
相信熟悉C和C++的讀者也發(fā)現(xiàn)了另外一點(diǎn)粟判,即在這段Go示例代碼里沒(méi)有出現(xiàn)分號(hào)亿昏。Go 程序并不要求開發(fā)者在每個(gè)語(yǔ)句后面加上分號(hào)表示語(yǔ)句結(jié)束,這是與C和C++的一個(gè)明顯不同 之處浮入。
編譯環(huán)境準(zhǔn)備 &開發(fā)工具選擇
在這一部分中龙优,相信大家都已經(jīng)非常熟悉各種工具和環(huán)境的搭建了羊异,簡(jiǎn)單闡述一下:環(huán)境變量的配置基本和Java類似事秀,注意GOPATH的設(shè)置和使用,至于工具大家自己選擇合適自己的就可以野舶,我使用的是IDEA易迹,集成個(gè)插件就可以了,在這里就不多說(shuō)了平道。
小結(jié)
本章我們簡(jiǎn)要介紹了Go語(yǔ)言的起源和背景睹欲,并結(jié)合若干代碼示例簡(jiǎn)要介紹了我們認(rèn)為值得關(guān)注的關(guān)鍵特性,之后按老規(guī)矩以Hello, world這個(gè)例子作為起點(diǎn)幫助讀者快速熟悉這門新語(yǔ)言一屋,消除對(duì)Go語(yǔ)言的陌生感窘疮,大家可以自行搭建好自己的Go開發(fā)環(huán)境。
通過(guò)這一章的學(xué)習(xí)冀墨,我們相信讀者對(duì)于Go語(yǔ)言的簡(jiǎn)單易學(xué)特性已經(jīng)有了比較直接的了解闸衫。 在后續(xù)的章節(jié)中,各位讀者可以快速動(dòng)手嘗試各種Go語(yǔ)言令人興奮的語(yǔ)言功能诽嘉。