第1章 初識(shí)Go語(yǔ)言

本章將簡(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ǔ)言功能诽嘉。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蔚出,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子虫腋,更是在濱河造成了極大的恐慌骄酗,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悦冀,死亡現(xiàn)場(chǎng)離奇詭異趋翻,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)盒蟆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門嘿歌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)掸掏,“玉大人,你說(shuō)我怎么就攤上這事宙帝∩シ铮” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵步脓,是天一觀的道長(zhǎng)愿待。 經(jīng)常有香客問(wèn)我,道長(zhǎng)靴患,這世上最難降的妖魔是什么仍侥? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮鸳君,結(jié)果婚禮上农渊,老公的妹妹穿的比我還像新娘。我一直安慰自己或颊,他們只是感情好砸紊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著囱挑,像睡著了一般醉顽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上平挑,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天游添,我揣著相機(jī)與錄音,去河邊找鬼通熄。 笑死唆涝,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的唇辨。 我是一名探鬼主播廊酣,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼助泽!你這毒婦竟也來(lái)了啰扛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤嗡贺,失蹤者是張志新(化名)和其女友劉穎隐解,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诫睬,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡煞茫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片续徽。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蚓曼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出钦扭,到底是詐尸還是另有隱情纫版,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布客情,位于F島的核電站其弊,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏膀斋。R本人自食惡果不足惜梭伐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望仰担。 院中可真熱鬧糊识,春花似錦、人聲如沸摔蓝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)项鬼。三九已至哑梳,卻和暖如春劲阎,著一層夾襖步出監(jiān)牢的瞬間绘盟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工悯仙, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留龄毡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓锡垄,卻偏偏與公主長(zhǎng)得像沦零,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子货岭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容