不是為了與眾不同。而是為了更加清晰易懂。
創(chuàng)新互聯(lián)堅持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:網(wǎng)站設(shè)計制作、網(wǎng)站建設(shè)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時代的蒙陰網(wǎng)站設(shè)計、移動媒體設(shè)計的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
Rob Pike 曾經(jīng)在 Go 官方博客解釋過這個問題(原文地址:),簡略翻譯如下(水平有限翻譯的不對的地方見諒):
引言
Go語言新人常常會很疑惑為什么這門語言的聲明語法(declaration syntax)會和傳統(tǒng)的C家族語言不同。在這篇博文里,我們會進(jìn)行一個比較,并做出解答。
C 的語法
首先,先看看 C 的語法。C 采用了一種聰明而不同尋常的聲明語法。聲明變量時,只需寫出一個帶有目標(biāo)變量名的表達(dá)式,然后在表達(dá)式里指明該表達(dá)式本身的類型即可。比如:
int x;
上面的代碼聲明了 x 變量,并且其類型為 int——即,表達(dá)式 x 為 int 類型。一般而言,為了指明新變量的類型,我們得寫出一個表達(dá)式,其中含有我們要聲明的變量,這個表達(dá)式運算的結(jié)果值屬于某種基本類型,我們把這種基本類型寫到表達(dá)式的左邊。所以,下述聲明:
int *p;
int a[3];
指明了 p 是一個int類型的指針,因為 *p 的類型為 int。而 a 是一個 int 數(shù)組,因為 a[3] 的類型為 int(別管這里出現(xiàn)的索引值,它只是用于指明數(shù)組的長度)。
我們接下來看看函數(shù)聲明的情況。C 的函數(shù)聲明中關(guān)于參數(shù)的類型是寫在括號外的,像下面這樣:
int main(argc, argv)
int argc;
char *argv[];
{ /* ... */ }
如前所述,我們可以看到 main 之所以是函數(shù),是因為表達(dá)式 main(argc, argv) 返回 int。在現(xiàn)代記法中我們是這么寫的:
int main(int argc, char *argv[]) { /* ... */ }
盡管看起來有些不同,但是基本的結(jié)構(gòu)是一樣的。
總的來看,當(dāng)類型比較簡單時,C的語法顯得很聰明。但是遺憾的是一旦類型開始復(fù)雜,C的這套語法很快就能讓人迷糊了。著名的例子如函數(shù)指針,我們得按下面這樣來寫:
int (*fp)(int a, int b);
在這兒,fp 之所以是一個指針是因為如果你寫出 (*fp)(a, b) 這樣的表達(dá)式將會調(diào)用一個函數(shù),其返回 int 類型的值。如果當(dāng) fp 的某個參數(shù)本身又是一個函數(shù),情況會怎樣呢?
int (*fp)(int (*ff)(int x, int y), int b)
這讀起來可就點難了。
當(dāng)然了,我們聲明函數(shù)時是可以不寫明參數(shù)的名稱的,因此 main 函數(shù)可以聲明為:
int main(int, char *[])
回想一下,之前 argv 是下面這樣的
char *argv[]
你有沒有發(fā)現(xiàn)你是從聲明的「中間」去掉變量名而后構(gòu)造出其變量類型的?盡管這不是很明顯,但你聲明某個 char *[] 類型的變量的時候,竟然需要把名字插入到變量類型的中間。
我們再來看看,如果我們不命名 fp 的參數(shù)會怎樣:
int (*fp)(int (*)(int, int), int)
這東西難懂的地方可不僅僅是要記得參數(shù)名原本是放這中間的
int (*)(int, int)
它更讓人混淆的地方還在于甚至可能都搞不清這竟然是個函數(shù)指針聲明。我們接著看看,如果返回值也是個函數(shù)指針類型又會怎么樣
int (*(*fp)(int (*)(int, int), int))(int, int)
這已經(jīng)很難看出是關(guān)于 fp 的聲明了。
你自己還可以構(gòu)建出比這更復(fù)雜的例子,但這已經(jīng)足以解釋 C 的聲明語法引入的某些復(fù)雜性了。
還有一點需要指出,由于類型語法和聲明語法是一樣的,要解析中間帶有類型的表達(dá)式可能會有些難度。這也就是為什么,C 在做類型轉(zhuǎn)換的時候總是要把類型用括號括起來的原因,像這樣
(int)M_PI
Go 的語法
非C家族的語言通常在聲明時使用一種不同的類型語法。一般是名字先出現(xiàn),然后常常跟著一個冒號。按照這樣來寫,我們上面所舉的例子就會變成下面這樣:
x: int
p: pointer to int
a: array[3] of int
這樣的聲明即便有些冗長,當(dāng)至少是清晰的——你只需從左向右讀就行。Go 語言所采用的方案就是以此為基礎(chǔ)的,但為了追求簡潔性,Go 語言丟掉了冒號并去掉了部分關(guān)鍵詞,成了下面這樣:
x int
p *int
a [3]int
在 [3]int 和表達(dá)式中 a 的用法沒有直接的對應(yīng)關(guān)系(我們在下一節(jié)會回過頭來探討指針的問題)。至此,你獲得了代碼清晰性方面的提升,但付出的代價是語法上需要區(qū)別對待。
下面我們來考慮函數(shù)的問題。雖然在 Go 語言里,main 函數(shù)實際上沒有參數(shù),但是我們先謄抄一下之前的 main 函數(shù)的聲明:
func main(argc int, argv *[]byte) int
粗略一看和 C 沒什么不同,不過自左向右讀的話還不錯。
main 函數(shù)接受一個 int 和一個指針并返回一個 int。
如果此時把參數(shù)名去掉,它還是很清楚——因為參數(shù)名總在類型的前面,所以不會引起混淆。
func main(int, *[]byte) int
這種自左向右風(fēng)格的聲明的一個價值在于,當(dāng)類型變得更復(fù)雜時,它依然相對簡單。下面是一個函數(shù)變量的聲明(相當(dāng)于 C 語言里的函數(shù)指針)
f func(func(int,int) int, int) int
或者當(dāng)它返回一個函數(shù)時:
f func(func(int,int) int, int) func(int, int) int
上面的聲明讀起來還是很清晰,自左向右,而且究竟哪一個變量名是當(dāng)前被聲明的也容易看懂——因為變量名永遠(yuǎn)在首位。
類型語法和表達(dá)式語法帶來的差別使得在 Go 語言里調(diào)用閉包也變得更簡單:
sum := func(a, b int) int { return a+b } (3, 4)
指針
指針有些例外。注意在數(shù)組 (array )和切片 (slice) 中,Go 的類型語法把方括號放在了類型的左邊,但是在表達(dá)式語法中卻又把方括號放到了右邊:
var a []int
x = a[1]
類似的,Go 的指針沿用了 C 的 * 記法,但是我們寫的時候也是聲明時 * 在變量名右邊,但在表達(dá)式中卻又得把 * 放到左左邊:
var p *int
x = *p
不能寫成下面這樣
var p *int
x = p*
因為后綴的 * 可能會和乘法運算混淆,也許我們可以改用 Pascal 的 ^ 標(biāo)記,像這樣
var p ^int
x = p^
我們也許還真的應(yīng)該把 * 像上面這樣改成 ^ (當(dāng)然這么一改 xor 運算的符號也得改),因為在類型和表達(dá)式中的 * 前綴確實把好些事兒都搞得有點復(fù)雜,舉個例子來說,雖然我們可以像下面這樣寫
[]int("hi")
但在轉(zhuǎn)換時,如果類型是以 * 開頭的,就得加上括號:
(*int)(nil)
如果有一天我們愿意放棄用 * 作為指針語法的話,那么上面的括號就可以省略了。
可見,Go 的指針語法是和 C 相似的。但這種相似也意味著我們無法徹底避免在文法中有時為了避免類型和表達(dá)式的歧義需要補(bǔ)充括號的情況。
總而言之,盡管存在不足,但我們相信 Go 的類型語法要比 C 的容易懂。特別是當(dāng)類型比較復(fù)雜時。
? 何為框架:
框架一直是敏捷開發(fā)中的利器,能讓開發(fā)者很快的上手并做出應(yīng)用,甚至有的時候,脫離了框架,一些開發(fā)者都不會寫程序了。成長總不會一蹴而就,從寫出程序獲取成就感,再到精通框架,快速構(gòu)造應(yīng)用,當(dāng)這些方面都得心應(yīng)手的時候,可以嘗試改造一些框架,或是自己創(chuàng)造一個。
Gin是一個golang的微框架,封裝比較優(yōu)雅,API友好,源碼注釋比較明確,已經(jīng)發(fā)布了1.0版本。具有快速靈活,容錯方便等特點。其實對于golang而言,web框架的依賴要遠(yuǎn)比Python,Java之類的要小。自身的net/http足夠簡單,性能也非常不錯??蚣芨袷且恍┏S煤瘮?shù)或者工具的集合。借助框架開發(fā),不僅可以省去很多常用的封裝帶來的時間,也有助于團(tuán)隊的編碼風(fēng)格和形成規(guī)范。
(1)首先需要安裝,安裝比較簡單,使用go get即可
go get github.com/gin-gonic/gin
如果安裝失敗,直接去Github clone下來,放置到對應(yīng)的目錄即可。
(2)代碼中使用:
下面是一個使用Gin的簡單例子:
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
router.Run(":8080") // listen and serve on 0.0.0.0:8080
}
簡單幾行代碼,就能實現(xiàn)一個web服務(wù)。使用gin的Default方法創(chuàng)建一個路由handler。然后通過HTTP方法綁定路由規(guī)則和路由函數(shù)。不同于net/http庫的路由函數(shù),gin進(jìn)行了封裝,把request和response都封裝到gin.Context的上下文環(huán)境。最后是啟動路由的Run方法監(jiān)聽端口。麻雀雖小,五臟俱全。當(dāng)然,除了GET方法,gin也支持POST,PUT,DELETE,OPTION等常用的restful方法。
Gin可以很方便的支持各種HTTP請求方法以及返回各種類型的數(shù)據(jù),詳情可以前往查看。
2.1 匹配參數(shù)
我們可以使用Gin框架快速的匹配參數(shù),如下代碼所示:
冒號:加上一個參數(shù)名組成路由參數(shù)??梢允褂胏.Param的方法讀取其值。當(dāng)然這個值是字串string。諸如/user/rsj217,和/user/hello都可以匹配,而/user/和/user/rsj217/不會被匹配。
瀏覽器輸入以下測試:
返回結(jié)果為:
其中c.String是gin.Context下提供的方法,用來返回字符串。
其中c.Json是gin.Context下提供的方法,用來返回Json。
下面我們使用以下gin提供的Group函數(shù),方便的為不同的API進(jìn)行分類。
我們創(chuàng)建了一個gin的默認(rèn)路由,并為其分配了一個組 v1,監(jiān)聽hello請求并將其路由到視圖函數(shù)HelloPage,最后綁定到 0.0.0.0:8000
C.JSON是Gin實現(xiàn)的返回json數(shù)據(jù)的內(nèi)置方法,包含了2個參數(shù),狀態(tài)碼和返回的內(nèi)容。http.StatusOK代表返回狀態(tài)碼為200,正文為{"message": “welcome"}。
注:Gin還包含更多的返回方法如c.String, c.HTML, c.XML等,請自行了解??梢苑奖愕姆祷豀TML數(shù)據(jù)
我們在之前的組v1路由下新定義一個路由:
下面我們訪問
可以看到,通過c.Param(“key”)方法,Gin成功捕獲了url請求路徑中的參數(shù)。同理,gin也可以捕獲常規(guī)參數(shù),如下代碼所示:
在瀏覽器輸入以下代碼:
通過c.Query(“key”)可以成功接收到url參數(shù),c.DefaultQuery在參數(shù)不存在的情況下,會由其默認(rèn)值代替。
我們還可以為Gin定義一些默認(rèn)路由:
這時候,我們訪問一個不存在的頁面:
返回如下所示:
下面我們測試在Gin里面使用Post
在測試端輸入:
附帶發(fā)送的數(shù)據(jù),測試即可。記住需要使用POST方法.
繼續(xù)修改,將PostHandler的函數(shù)修改如下
測試工具輸入:
發(fā)送的內(nèi)容輸入:
返回結(jié)果如下:
備注:此處需要指定Content-Type為application/x-www-form-urlencoded,否則識別不出來。
一定要選擇對應(yīng)的PUT或者DELETE方法。
Gin框架快速的創(chuàng)建路由
能夠方便的創(chuàng)建分組
支持url正則表達(dá)式
支持參數(shù)查找(c.Param c.Query c.PostForm)
請求方法精準(zhǔn)匹配
支持404處理
快速的返回給客戶端數(shù)據(jù),常用的c.String c.JSON c.Data
當(dāng)客戶端在 發(fā)出POST請求時/albums,您希望將請求正文中描述的專輯添加到現(xiàn)有專輯數(shù)據(jù)中。
為此,您將編寫以下內(nèi)容:
1、編寫代碼
a.添加代碼以將專輯數(shù)據(jù)添加到專輯列表。
在此代碼中:
1)用于Context.BindJSON 將請求正文綁定到newAlbum。
2) album將從 JSON 初始化的結(jié)構(gòu)附加到albums 切片。
3)向響應(yīng)添加201狀態(tài)代碼,以及表示您添加的專輯的 JSON。
b.更改您的main函數(shù),使其包含該router.POST函數(shù),如下所示。
在此代碼中:
1)將路徑中的POST方法與 /albumspostAlbums函數(shù)相關(guān)聯(lián)。
使用 Gin,您可以將處理程序與 HTTP 方法和路徑組合相關(guān)聯(lián)。這樣,您可以根據(jù)客戶端使用的方法將發(fā)送到單個路徑的請求單獨路由。
a.如果服務(wù)器從上一節(jié)開始仍在運行,請停止它。
b.從包含 main.go 的目錄中的命令行,運行代碼。
c.從不同的命令行窗口,用于curl向正在運行的 Web 服務(wù)發(fā)出請求。
該命令應(yīng)顯示添加專輯的標(biāo)題和 JSON。
d.與上一節(jié)一樣,使用curl檢索完整的專輯列表,您可以使用它來確認(rèn)添加了新專輯。
該命令應(yīng)顯示專輯列表。
當(dāng)客戶端向 發(fā)出請求時GET /albums/[id],您希望返回 ID 與id路徑參數(shù)匹配的專輯。
為此,您將:
a.在您在上一節(jié)中添加的函數(shù)下方postAlbums,粘貼以下代碼以檢索特定專輯。
此getAlbumByID函數(shù)將提取請求路徑中的 ID,然后找到匹配的專輯。
在此代碼中:
(1)Context.Param用于從 URL 中檢索id路徑參數(shù)。當(dāng)您將此處理程序映射到路徑時,您將在路徑中包含參數(shù)的占位符。
(2)循環(huán)album切片中的結(jié)構(gòu),尋找其ID 字段值與id參數(shù)值匹配的結(jié)構(gòu)。如果找到,則將該album結(jié)構(gòu)序列化為 JSON,并將其作為帶有200 OK HTTP 代碼的響應(yīng)返回。
如上所述,實際使用中的服務(wù)可能會使用數(shù)據(jù)庫查詢來執(zhí)行此查找。
(3)如果找不到專輯,則返回 HTTP 404錯誤。
b.最后,更改您的main,使其包含對router.GET的新調(diào)用,路徑現(xiàn)在為/albums/:id ,如以下示例所示。
在此代碼中:
(1)將/albums/:id路徑與getAlbumByID功能相關(guān)聯(lián)。在 Gin 中,路徑中項目前面的冒號表示該項目是路徑參數(shù)。
a.如果服務(wù)器從上一節(jié)開始仍在運行,請停止它。
b.在包含 main.go 的目錄中的命令行中,運行代碼以啟動服務(wù)器。
c.從不同的命令行窗口,用于curl向正在運行的 Web 服務(wù)發(fā)出請求。
該命令應(yīng)顯示您使用其 ID 的專輯的 JSON。如果找不到專輯,您將收到帶有錯誤消息的 JSON。
恭喜!您剛剛使用 Go 和 Gin 編寫了一個簡單的 RESTful Web 服務(wù)。
本節(jié)包含您使用本教程構(gòu)建的應(yīng)用程序的代碼。