這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
1.多值返回
在C/C++裡面如果需要返回多值,一般是在函數傳入指標或者引用,比如
fun(int *a,int *b,int *c),但在go裡面,如果需要返回多值,只需要把函數寫成這樣
1 func test_func()(int,int,int){2 a := 1;3 b := 2;4 c := 3;5 6 return a,b,c;7 }
最後函數會依次返回a,b,c
這個其實在lua中早就有了,所以實際上在go裡面也算不上什麼新的東西,go裡面還有一個傳回值命名的特性
func read_file()(read_count int,err int){
....//代碼需要做的事情
return ;
}
這樣就可以讓函數的代碼更加清晰和更讓人理解,看函式宣告就可以知道傳回值是幹什麼的
2.零值初始化,自動推導類型
C++ 11新增了一個auto,在go裡面也有:=符號可以自動推導,比如a := 1,a會自動被推導成為int類型,還有另外一個特性就是靈芝初始化,我想這個是大部分C程式員喜聞樂見的,因為這樣我們可以少寫幾行代碼跑去初始化一個變數(在我的大部分代碼中也都是用0來初始設定變數),如果C/C++有這個特性,實際上很多錯誤都可以很容易找到,我看很多代碼都是因為沒有初始化造成的
3.記憶體回收
這個不用說了,絕對是個利器,資源管理一直是C/C++頭疼的問題,當然自動記憶體回收肯定會帶來效能下降,甚至會記憶體泄露(據說java也會有記憶體泄露的問題,python是有的,雖然這些都有記憶體回收的機制)
4.slices
說白了slices就是指向一個array底層的指標,不過和array不同,slices在元素加入的時候可以增加長度,並且slices有點類似與指標,一旦修改了slices,那麼slices指向的資料也會改變,go裡面沒有把數組退化成指標的做法,如果你傳入的是個數組,那麼go會複製整個數組的副本,也就是如果有100個元素,那麼go就會複製100個元素的副本
5:defer
defer就是在函數退出的時候會自動調用我們用defer生命的程式碼片段,這個是為了防止我們忘記關閉檔案控制代碼或者釋放資源,C/C++程式員經常犯的錯誤
1 f,_ = os.Open(file_name);2 3 defer f.Close()
函數在開啟檔案後並不會立即執行f.Close,而是會在函數執行完畢後才執行f.Close()
6:介面,自訂類型與方法
1 class Graph 2 { 3 public: 4 int GetWidth(); 5 6 int GetHeight(); 7 8 virtual void Draw(); 9 private:10 int width;11 int height;12 }
我一直不喜歡C++的這種的方式,因為把一大堆的函數和資料放在一起,這樣當代碼多了以後將會變得很混亂,而且因為虛函數的存在,在進行初始化的時候不能直接用memset或者memcpy,如果一個類中有幾百個變數,那麼我們需要一個個去手動初始化,不像C語言裡面,資料結構都是原生的值,可以直接memset初始化,go裡面則是自動幫我們零值初始化
實際上有瞭解C++的應該知道,上面的這個類編譯器在產生代碼的時候還是幫我們進行了分開,比如GetAge()會變成GetAge(Person &person),在go裡面則是將一個類分成三個部分,資料,方法與介面
1 type Graph struct{ 2 width int; 3 height int; 4 } 5 6 7 func (g *Graph)GetWidth()(int){ 8 return g.width; 9 }10 11 func(g *Graph)GetHeight()(int){12 return g.height;13 }14 15 func(g *Graph)Draw(){16 fmt.Printf("graph draw");17 }18 19 type graph_interface interface{20 Draw()21 }22 23 func Draw(g graph_interface){24 g.Draw();25 }
interface就是聲明了一個介面,就是類似與虛函數的vptr,可以把type graph_interface interface這句理解成某個把函數加入虛函數表,使用這個介面就可以調用傳入的參數的Draw這個函數(C++虛函數的實現原理也是利用這個方法)
在函式宣告前面加上(g *Graph)就可以把類的資料與這些方法綁定在一起,其他也沒什麼好說的了,公有和私人資料或者函數都是利用大小寫來區分的,不過go裡面跟C++不同,C++如果是private的話其他類就不能訪問這個變數或者函數,而go則是其他檔案不能訪問,本檔案還是可以訪問,有點類似於C的static
實際上go的C++內部的這些實現原理我估計都是差不多的,只是展現出來的文法的不同而已,當然到現在為止我更喜歡go的文法,我想go的設計思想更符合linus的說法
“爛程式員關心的是代碼。好程式員關心的是資料結構和它們之間的關係。”
git的設計其實非常的簡單,它的資料結構很穩定,並且有豐富的文檔描述。事實上,我非常的贊同應該圍繞我們的資料結構來設計代碼,而不是依據其它的,我認為這也是git之所以成功的原因之一[...]依我的觀點,好程式員和爛程式員之間的差別就在於他們認為是代碼更重要還是資料結構更重要。
我想C++的程式員要看看這篇文章:http://www.aqee.net/torvalds-quote-about-good-programmer/
將資料結構和這些方法分開將會更有助於程式員去理清資料結構與函數的關係
7.goroutine
據說這個是go的最大的"賣點",網上吹捧的文章也很多,不過lua很早就有這個東西了,所以我覺得沒什麼可以吹捧的,就是線上程內再類比單核的電腦去運行程式碼片段,當然這樣很節省資源,不用進行線程的切換
go func_name(),這樣就把一個函數當作goroutine來運行,當然routine並不是並發的,只是並行的,所以說goroutine是線上程內類比單核的電腦去運行代碼(並發和並行的區別)
go routine進行通訊也很簡單,就是利用channel,有用過unix shell的應該很熟悉這個東西了,不多說了
go的缺點:
1.無法與C結合,無法編譯成lib,dll
總之看了這麼多,我覺得go還是很好用的,但是到目前為止,沒發現有什麼可以將go嵌入到c語言裡面,只能把C嵌入到go裡面,這個我想會導致很多公司不願意用go,我本來想將go弄進一個項目裡面,但是發現go無法嵌入到C裡面,也無法編譯成dll就放棄了
2.並不是像網上說的那樣是為了並發而設計的,只是改進的C++
應該能取代C++或者java,但估計不能取代C,網上有人拿go和erlang比較,我還是覺得go無法跟erlang比較,go頂多算是改進的C++,和erlang這種天生為了並行運算的而生的無法比較,go還是沿用了C/C++的物件導向,面向過程,而erlang則是天生面向進程,一切都是進程,甚至線程調度代碼都是自己寫的而不是直接將作業系統的API封裝一層,據說go的線程調度代碼只有幾百行,而erlang有幾萬行,這裡就能看出差別了,甚至我一直覺得erlang完全可以自己單獨出來做作業系統
3.強制性的編碼風格
go也有缺點,比如強制性的一些編碼手段,如果你一個變數不使用,不會給你一個警告,而是會給你一個錯誤,無法通過編譯,據說是為了增加編譯速度,這個讓我很詫異,因為我在編程中經常需要預留介面或者變數,這個對於以後更改很有好處,在這種地方節省編譯時間有效嗎??我估計是go的設計者為了強迫大家寫出好看的代碼,這個在很多無法通過編譯的選項就可以看出,go對程式員的編程風格強制要求讓我很不適應...況且我們工程十多萬行代碼,整個項目重編,10分鐘和20分鐘有區別嗎??我想大部分人都是切出去做其他的事情
4.詭異的文法
var n int;這個是不是很詭異??反正我非常不習慣這個聲明變數的方式,還有大括弧
1 if true {2 //code3 }else if false{4 //code5 }else6 {7 //code8 }
需要強制把大括弧跟條件陳述式在一起,不知道go的設計師是為了標新立異還是懶得寫文法分析??這樣代碼反而沒有c的把大括弧另起一行來的好看,不知道為什麼要強製程序員這樣寫,不過讓我想起當初python的縮排也引起很多爭議...當初在論壇上還跟人爭辯過這個
5.編譯真的比C/C++快??
網上有人說go的編譯速度比C和C++快,go為了增加編譯速度強製程序員不得在代碼中有不使用的變數,import的包如果不使用也會被提示編譯錯誤,但這真的能解讓go編譯速度比C++快??C++因為文法複雜對於編譯器的設計者一直是個噩夢,但是我想go沒有lib或者obj的概念(我沒找到產生的臨時檔案在哪裡,還是說我的產生方式不對??),那我們在build一個項目的時候是不是要把所有的被import的檔案重新編譯一遍??如果項目大起來編譯速度真的會比C++還快??如果C++的整個項目不重編的話go的編譯速度會比C++更有優勢??我記得網上看有人說C++的編譯方式會慢是因為一個檔案一旦被include 46次,那麼他也需要被開啟46次,但我想這個問題go應該也存在,或者已經解決了??