這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
當我還是個學生的時候,總感覺伺服器端編程是非常困難的一件事。老師們只是簡單的那麼一講:建立一個socket偵聽用戶端串連,把串連上來的用戶端弄個map儲存起來,然後不斷輪詢。。。這種當然是無法在實際工作中使用的,關鍵的一些東西,比如並發如何處理啊,資料庫操作怎樣最佳化啊,安全性如何保證啊,這些課堂上都學不到。我也試圖自學一些這部分的知識,結果是被我心愛的C++坑的不淺。本來我一直體會不到指標難用在哪裡,自己開闢的記憶體自己釋放,不是件很簡單的事嗎?在嘗試伺服器端多線程編程的時候才真正發現記憶體管理的痛點。。。多線程下很多東西變得不同了,我一直以來認為正確的析構寫法突然就不正確了。而且往往還要用上很多第三方庫。我不知道第三方庫的寫法,第三方庫的作者也不知道我有沒有按他希望的那樣去用它。其結果就是記憶體報錯不可避,野指標漫天飛。當然,我相信只要耐心認真的學習,這些問題用C++也是一定能解決的。只不過由於畢業之後自己的本職工作成了手遊用戶端開發,也沒那麼多時間去瞭解這些了。
最近工作不太順心,亂七八糟的事情很多。於是就決定再研究一下伺服器端的編程。工作之後接觸的語言多了,才知道用其他語言開發一個伺服器端並沒有過去相信中那麼難。公司的伺服器大神用的是java,我看了一下覺得也不是那麼複雜。更何況手機上的網遊大多數都是弱連網類型的,用一些web架構都夠用了,前段時間我就用django實驗了一下,感覺用這種方式實在是簡單。不過django本身只是個web開發架構,並不是個web伺服器,還是要部署到apache或者nginx上。於是當時我配置各種環境的時間花得比寫代碼的時間長多了。
今天我打算要研究的這門叫go的語言,是google2009年推出的。據說處理多線程有獨到之處。坦白說,簡單的看了一下文法之後我十分討厭她。比如go規定定義函數時後面第一個花括弧不能另起一行寫到下一行去。這個實在是誇張,因為我的習慣一向是寫到下一行去的,而且從沒有哪個語言說你必須寫在這一行,否則不能通過編譯。。。你可能會說區區一個花括弧的寫法這種小事怎麼能成為討厭一門語言的理由,然而翻開曆史就會發現天主教徒和穆斯林們已經為我們這些無神論者認為並不存在的神爭鬥了一千多年。程式設計語言就像女人,你喜不喜歡她的關鍵在於寫法也就是外表。至於你說windows應用編程c#第一啊,erlang處理多線程強大啊,lua與c++配合寫遊戲速度快啊,這些是你在某些領域選擇她的理由,相當於一個女人的內在,也是你最終向選擇和一個女人結婚的理由,但不一定代表你喜歡她。就好像因為花括弧不能寫到下一行去,我現在就很討厭go語言,但還是選擇用它來寫個伺服器一樣。
那麼就讓我開始先瞭解一下它的變數類型,函式宣告,選擇和迴圈這些基礎吧。
先讓我嘗試寫一個用牛頓迭代法求平方根的小東西:
package mainimport ( "fmt" "math")func main(){ var hellostr = "Hello, Go.\n" fmt.Print(hellostr) tipstr := "請給我輸入一個正數讓我來求它的平方根:\n" fmt.Print(tipstr) var clacnum float64 //如果唯寫var clacnum,不明確指定類型要報錯 if clacnum < 0{ fmt.Print("你在逗我,負數並沒有平方根\n") }else if clacnum == 0 { fmt.Print("0的平方根還是0\n") }else { fmt.Printf("%g的平方根是:%g\n", clacnum, NewtonSqrt(clacnum)) }}func NewtonSqrt(x float64) (iguess float64){ //牛頓迭代法求平方根,只是為了學習一下迴圈 iguess = 1 for math.Abs(iguess * iguess - x) > 1e-12{ //與for ;math.Abs(iguess * iguess - x) > 1e-12;{}相同 iguess = (iguess + x /iguess) / 2 } return}
首先是go語言的幾種變數聲明方式:第一種就是var hellostr = "Hello, Go.\n"這樣的,不需要宣告類型,由初始值決定變數的類型。第二種則是像tipstr := ""這樣的,與上面一種等價。第三種則是像var clacnum float64這種不賦初值得聲明方式。float64是go的基本類型之一,沒什麼好說的。需要注意的是go語言聲明過的變數最少必須使用一次,不然編譯會報錯。老實說這個要求雖然是個很有助於編程規範的要求,但是剛剛寫下的變數IDE就在下面畫條紅線提示你”unused“的感覺很不爽有沒有!
接下來是正常的if...else選擇結構,花括弧還是不能另起一行寫,連else if也不能另起一行必須很花括弧寫在同一行,簡直了。go果然是個令人討厭的傢伙。
main下面是個叫做NewtonSqrt的函數,第一個括弧裡是參數列表,後面是傳回值。最後我並沒有寫出return iguess,因為在聲明的時候已經聲明了iguess是傳回值了。當然也可以寫成下面的形式:
func NewtonSqrt(x float64) float64{ //牛頓迭代法求平方根,只是為了學習一下迴圈var iguess float64 = 1for math.Abs(iguess * iguess - x) > 1e-12{//與for ;math.Abs(iguess * iguess - x) > 1e-12;{}相同iguess = (iguess + x /iguess) / 2}return iguess}
函數裡則是用牛頓迭代法求平方根的迴圈,for math.Abs(iguess * iguess - x) > 1e-12這句看上去有點怪,為什麼是for而不是while呢?go語言裡是沒有while的,這個for其實省略了兩個分號,等價於for;math.Abs(iguess * iguess - x) > 1e-12;。這樣看起來就跟C++是一樣的了。
運行一下試試看:
到這裡似乎結束了,然而若是調皮的人不輸入數字而是輸入些奇怪的字串進來呢?
程式倒是沒崩潰,但我們顯然不應該讓這種情況發生。如果是其他語言,你肯定會說try...catch...,然而go語言並沒有try...catch。實際上fmt.Scanf這個方法是有傳回值的,而且是2個。奇怪麼,函數具有有兩個傳回值。實際上,如果是一個把go當作第一門程式設計語言在學習的人,你去問他的對函數能返回兩個值的感受,他肯定不會回答奇怪的。老實說當年初學C語言的時候我還對函數只能返回一個值感動奇怪,為什麼不能直接返回兩個值呢?定義個結構體也好把變數指標傳進去也好,怎麼想都沒這個方便。當然熟悉了C之或java後的人,自然是覺得返回兩個值奇怪了。其實在其他主流的程式設計語言裡,Lua還有蘋果的Swift的函數也是可以返回多個值的。
func Scanf(format string, a ...interface{}) (n int, err error) {return Fscanf(os.Stdin, format, a...)}
前面的int就不說了,後面的err一看就知道是幹什麼的了。於是將代碼改成下面這樣:
package mainimport ( "fmt" "math")func main(){ var hellostr = "Hello, Go.\n" fmt.Print(hellostr) tipstr := "請給我輸入一個正數讓我來求它的平方根:\n" fmt.Print(tipstr) var clacnum float64 //如果唯寫var clacnum,不明確指定類型要報錯 _, err := fmt.Scanf("%g", &clacnum) //_用來佔位,忽略不想要的傳回值 if err != nil{ fmt.Print("不要亂輸入奇怪的東西!") return } if clacnum < 0{ fmt.Print("你在逗我,負數並沒有平方根\n") }else if clacnum == 0 { fmt.Print("0的平方根還是0\n") }else { fmt.Printf("%g的平方根是:%g\n", clacnum, NewtonSqrt(clacnum)) }}func NewtonSqrt(x float64) (iguess float64){ //牛頓迭代法求平方根,只是為了學習一下迴圈 iguess = 1 for math.Abs(iguess * iguess - x) > 1e-12{ //與for ;math.Abs(iguess * iguess - x) > 1e-12;{}相同 iguess = (iguess + x /iguess) / 2 } return}
在接收多個傳回值的時候,可以用底線_來佔位,忽略某些傳回值,這裡我忽略掉了Scanf的第一個int類型的傳回值。然後判斷err是否為空白(nil)。
再運行一下試著輸入一些奇怪的東西:
在go語言中,這種處理方式就是go的設計者們推薦的處理方式。當然還有另一種類似try...catch的異常處理:defer, panic, recover,但是go設計者們似乎不推薦使用,理由是將異常與控制結構混在一起會很容易使得代碼變得混亂,而他們追求簡潔優雅。。。嗯,畢竟是一群不讓人把花括弧寫到下一行的異教徒,才不用理會他們推不推薦呢。
今天就先研究到這裡好了。