長期從事Delphi
開發,雖不敢說精通,但說很熟悉仍是至關有自信的。不過,只會一門語言,並且仍是這麼老的語言,更是在大天朝很小衆的語言,總感受本身離餓死街頭沒多遠了,因此趁着還沒老再學個潮點的吧。c++
先前考慮過Python
,初步瞭解後以爲不太適合本身:編程
解釋型語言:部署時得先搞個運行環境,發佈的程序就是源碼自己,再加上這個執行效率,怎麼想都仍是編譯型語言更合適。數組
動態語言:無需聲明,拿來就用,這已經很不合習慣了。想一想一個變量,前一秒仍是浮點數,下一秒就成字符串了,再一眨眼又成某個對象了……雖然通常不會有人這麼寫,可是擋不住手誤啊,仍是把這種小細節交給編譯器更讓人放心。併發
因此,對於有點強迫症和潔癖的本身,最後仍是選了Go
,比較符合已有的編程習慣,學習成本應該相對會低些吧。ide
至於Go
嘛,想學是已經好久了,但因爲種種緣由卻遲遲未開啓,不過終究仍是要邁出這一步的,因此就搞這麼個系列來記錄吧,一方面算是自我督促,另外一方面也算是一種交流吧,固然,若一不留神幫上了誰,那自是開心極了。函數
已經初步瞭解過了Go
,說來和Delphi
仍是有很多類似之處呢,從Delphi
轉向Go
應該會比較輕鬆吧。學習
Delphi
的工程算是比較自由的,源碼的話,只要把單元路徑引了或是直接包含進工程單元裏就能夠了,編譯出的dcu
和最終的exe
指定下路徑也就沒問題了,一般我都使用下面這種結構:測試
Project/ bin/ src/ dcu/ mod1/ *.dfm *.pas mod2/ *.dfm *.pas *.dpr
不過,每個工程都要設置,並且我習慣將Debug
和Release
設置徹底同樣,也還真是夠煩的。ui
Go
就沒得選了,只有一種結構:編碼
Project/ bin/ pkg/ src/ *.go mod1/ *.go *_test.go mod2/ *.go *_test.go
總體和我原有的習慣差很少,仍是蠻容易接受的,不過卻是要把這Project
的路徑加入到GOPATH
系統變量裏讓人有一點小不爽。可是Go
能夠直接把測試都寫了,這點仍是蠻讓我驚喜的,畢竟用了這麼多年Delphi
也沒寫過一行像樣的測試。
Delphi
典型的源碼結構是這樣:
unit Unit1; interface uses ...//單元引用 type ...//公開類型定義 const ...//公開常量 var ...//公開變量 procedure Proc1;//公開過程聲明 function Func1: Integer;//公開函數聲明 implementation uses ...//私有單元引用 const ...//私有單元級常量 var ...//私有單元級變量 procedure Proc1; var ...//模塊級變量 begin ...//公開過程實現 end; function Func1: Integer; const ...//模塊級常量 begin ...//公開函數實現 end; procedure Proc2;//單元級私有過程實現 function Func2: Integer;//模塊級私有函數實現 begin ... end; begin ... end; initialization ...//單元初始化代碼 finalization ...//單元反初始化代碼 end.
Go
的源碼結構是這樣:
package pkg1 import "fmt" //導入包 const C1 //公開常量 const c2 //私有常量 var V1 //公開變量 var v2 //私有變量 func Func1(){}//公開方法 func func2(){}//私有方法
總體上來看,Delphi
語意更明確,Go
則更簡潔,Delphi
更集中,Go
則和全部類c語言
同樣比較單1、靈活,各有各的好。卻是Go
這用首字母大小寫來區分公開私有讓我這喜歡pascal命名法
的有點尷尬。
Delphi
//這是單行註釋 { 這是多行註釋第一行 這是多行註釋第二行 } (* 這是另一種多行註釋 與{}能夠互相包含 主要用於註釋內容中有 } 的狀況 *)
Go
//這是單行註釋 /* 這是多行註釋第一行 這是多行註釋第二行 */
Delphi
安裝CnPack
以後,可使用xml風格
的註釋,寫在方法前能夠成爲方法的文檔說明。
Go
提供的godoc
也能夠從代碼中提取頂級聲明的首行註釋以及每一個對象的相關注釋生成相關文檔。
Delphi
:=
+
、-
、*
、/
、div
、mod
not
、and
、or
、xor
not
、and
、or
、xor
、shl
、shr
=
、<>
、>
、<
、>=
、<=
^
、+
、-
、=
、<>
@
+
+
、-
、*
、<=
、>=
、=
、=
、<>
、in
as
、is
、=
、<>
Go
=
、:=
、+=
、-=
、*=
、/=
、%=
、<<=
、>>=
、&=
、|=
、^=
+
、-
、*
、/
、%
、++
、--
!
、&&
、||
<<
、>>
、&
、|
、^
==
、!=
、>
、<
、>=
、<=
*
&
+
Delphi
//整型 ShortInt 1B -128~127 Byte 1B 0~255 SmallInt 2B -32768~32767 Word 2B 0~65535 LongInt 4B -2147483648~2147483647 LongWord 4B 0~4294967295 Int64 8B -9223372036854775808~9223372036854775807 UInt64 8B 0~18446744073709551615 Integer 4B -2147483648~2147483647 Cardinal 4B 0~4294967295 //浮點型 Single 4B Real48 6B //從未用過 Double 8B Real 8B Currency 8B //金融專用 //布爾型 Boolean 1B ByteBool 1B WordBool 2B LongBool 4B //字符型 AnsiChar 1B //ANSI編碼 WideChar 2B //Unicode編碼 Char //早期版本至關於AnsiChar,後期WideChar //字符串 ShortString //兼容老舊版本 AnsiString //ANSI編碼 WideString //Unicode編碼 String //早期版本至關於AnsiString,後期WideString //指針 Pointer //變體類型 Variant
Go
//整型 int8 1B -128~127 uint8 1B 0~255 int16 2B -32768~32767 uint16 2B 0~65535 int32 4B -2147483648~2147483647 uint32 4B 0~4294967295 int64 8B -9223372036854775808~9223372036854775807 uint64 8B 0~18446744073709551615 int //有符號,與平臺相關 uint //無符號,與平臺相關 //浮點型 float32 4B float64 8B //布爾型 bool //字符型 byte //uint8的別名,一般用於代表原始數據 rune //等價於int32,一般用於表示一個UTF-8字符 //字符串 string //UTF-8編碼 //指針 uintptr //複數 complex64 complex128 //map類型 map[鍵類型] 值類型 //接口類型 interface{} //錯誤類型 error
總體上看,二者數據類型基本一致,不過Go
直接支持了map和複數類型。另外,Go
也沒有變體類型,不過能夠用接口類型interface{}
來實現傳任意類型。須要注意的是,Go
的字符串是UTF-8
編碼而不是Unicode
編碼。
Delphi
在數據類型兼容時可直接進行隱式轉換,也可進行顯式(強制)轉換,語法爲:類型B的值 := 類型B(類型A的值)
;Go
不存在隱式轉換,須要轉換時只能使用顯式(強制)轉換,語法爲:類型B的值 = 類型B(類型A的值)
。
Delphi
//聲明變量 var v1 : string; v2, v3 : Integer; //聲明並初始化 var v4 : Double = 3.14; //變量賦值 v1 := 'abc';
Go
//聲明變量 var ( v1 string v2, v3 int ) //聲明並初始化 var v4 float64 = 3.14 var v5 = 1234 //在函數內還能夠這樣 v6 := 2468 //變量賦值 v1 = 'abc' v2, v3 = v3, v2
變量聲明都用了關鍵字var
,且都是變量在前,類型在後,也都支持批量聲明,聲明並初始化的語法也基本一致,賦值也幾乎一致。
不過Go
還多了一些特性:
支持類型推導,聲明並初始化時可省略類型。
在函數內部聲明並初始化變量時還支持用:=
的方式簡寫(不過這和Delphi
的賦值符號同樣,意義卻截然不同,使用時得留意了)。
函數內部的變量能夠像c++
同樣達到語塊級的做用域和生命期。
支持多重賦值,交換變量更簡潔。
支持匿名變量,即用_
來佔位。
Delphi
const c1: string = 'abc'; c2 = '123'; c3 = 456;
Go
const ( c1 string = "abc" c2 = "123" c3 = 456 c4 //與c3同樣 c5, c6 = 1.2, true )
都用了關鍵字const
,都支持批量聲明,也都支持類型推導,並且Go
的類型推導更智能些,換句話說就叫更復雜些(主要是由於與別的混在一塊兒了)。
Delphi
type myEnum=( eA, //0 eB, //1 eC = 10, //10 eD, //11 eE, //12 eF //13 );
Go
const( eA = iota //0 eB //1 eC = 10 //10 eD //10 eE = iota //4 eF //5 )
Delphi
使用關鍵字type
來聲明一個枚舉類型,並且還須要命名,默認以0
開始遞增,也能夠爲某一元素指定值,其後元素依次遞增。
Go
沒有枚舉的關鍵字,但使用const
和iota
也能夠達到目的。iota
每次遇到const
就重置爲0
,每增長一個元素就自增1
,不管元素有沒有取iota
的值。
不過,通常狀況下不多會爲枚舉的元素指定特定值。
Delphi
//定義類型 type myInt = type Integer; //類型別名 type myInt = Integer;
Go
//定義類型 type myInt int //類型別名 type myInt = int
Delphi
//靜態數組 var r1 : array[0..9] of Byte; var r2 : array[0..1] of Boolean = (True, False); var r3 : array[0..3,0..1] of Integer; //二維靜態數組 var r4 : array[0..3] of array[0..1] of Integer; //同上 //動態數組 var r5 : array of Integer; //一維動態數組 var r6 : array of array of Integer; //二維動態數組
Go
//靜態數組 var r1 [10]byte var r2 [2]bool{true,false} var r3, r4 [4][2]int //用切片來代替動態數組 var r5 []int var r6 [][]int
Go
沒有專門的動態數組,不過有更爲靈活、實用的切片,用切片徹底能夠實現動態數組的功能。
Delphi
//定義 type TEmp = record Name : string; Age : Integer; end; PEmp = ^TEmp; var emp : TEmp; p : PEmp; //賦值 p := @emp; emp.Name := 'Sam'; //p^.Name := 'Sam'; emp.Age := 10; //p^.Age := 10; //還有一種帶packed的結構體,因爲不會進行內存對齊而速度略慢,但用於dll時可避免內存混亂 type TRcd = packed record B : Byte; C : Integer; end;
Go
type TEmp struct { Name string Age int } var( emp TEmp p *TEmp ) p = *emp emp.Name = "Sam" //(*p).Name = "Sam" //p.Name = "Sam" emp.Age = 10 //(*p).Age = 10 //p.Age = 10
Delphi
//if if 布爾表達式 then ...; //if ... else ... if 布爾表達式 then ... else ...; //if ... else if ... if 條件表達式1 then ... else if 布爾表達式2 then ... else ...; //case語句 case 選項表達式 of 值1: ...; 值2: ...; else ...; end;
Go
//if if 布爾表達式 { ... } //if ... else ... if 布爾表達式 { ... } else { ... } //if ... else if ... if 布爾表達式1 { ... } else if 布爾表達式2 { ... } else { ... } //switch switch 選項表達式 { case 值1: ... fallthrough //兼容C語言的case,強制執行下一個case case 值2: ... default: ... } switch{ case 選項表達式 == 值1: ... case 選項表達式 == 值2: ... default: ... } switch 類型表達式 { case 類型1: ... case 類型2: ... default: ... }
Delphi
//升冪記數循環 for 記數變量 := 起始值 to 終點值 do //起始值<終點值,變量每循環變化1 語句; //降冪記數循環 for 記數變量 := 起始值 downto 終點值 do //起始值>終點值,變量每循環變化1 語句; //高版本支持,2009+? for 循環變量 in 集合 do 語句; //當型循環,當表達式值爲真時執行循環 while 布爾表達式 do 語句; //直到型循環,當表達式值爲真時退出循環 repeat 語句; until 布爾表達式;
Go
//類C的for循環 for 循環變量初始化; 條件表達式; 循環變量改變 { ... } //類C的while循環 for 條件表達式 { ... } //無限循環 for{ ... } //類C的for each for k, v := range R {//R可爲數組、切片、map、字符串等 ... }
都用來直接跳轉到指定標籤處繼續向下執行。Delphi
須要先使用label
聲明標籤,Go
則直接使用標籤,標籤的語法均爲:
labelA: ...
都用來結束本次循環並進入下一次循環。在多層循環中,Go
還能夠在其後跟標籤,用來結束標籤對應的那層循環並進入下一次循環。
都用來結束當前循環並執行當前循環以後的代碼。在多層循環中,Go
還能夠在其後跟標籤,用來結束標籤對應的那層循環並執行循環以後的代碼。
Delphi
//過程,無返回值 procedure Proc1; begin ... end; procedure Proc2(i1, i2: Integer; s1, s2: string); begin ... Proc1; end; //函數,有返回值 function Func1: string; begin ... Result := '返回值'; Proc2(1, 2, 'abc', 'def'); end; function Func2(i1, i2: Integer; s1, s2: string): string; begin ... Result := Func1(); end;
Go
//無返回值 func f1() { ... } func f2(i1, i2 int, s1, s2 string) { ... f1() } //有返回值 func f3() string { r := "返回值" return r //必須帶 r } func f4() (s string) { s = f3() return s //可省略 s } func f5() (r1 int, r2 string) { r1, r2 = f6(1, 2, 'abc', 'def') return r1, r2 //可省略 r1, r2,若不省略,則順序必須一致 } func f6(i1, i2 int, s1, s2 string) (r1 int, r2 string) { r1 = i1 + i2 r2 = s1 + s2 return }
Delphi
無返回值的用procedure
,有返回值的用function
;Go
統一用func
。
都接受0個或多個參數,Delphi
同類型參數間用,
分隔,不一樣類型參數間用;
分隔;Go
統一用,
分隔。
Delphi
函數只能有1個返回值,且返回值固定由Result
隱藏變量攜帶,Result
可屢次賦值,以後也可有其它語句;Go
能夠有0個或多個返回值,返回值變量能夠聲明也能夠不聲明,但必須由return
返回且必須爲最後一條語句,若返回值變量有多個且已聲明,return
後跟的返回值變量順序必須與定義一致。
Delphi
無參過程/函數調用時,括號()
可帶可不帶;Go
無參函數調用時必須帶()
。
Delphi
被調用的過程/函數必須在主調過程/函數以前實現或在interface
區已聲明;Go
函數沒有聲明只有實現,且不要求被調函數在主調函數以前定義。
如下概念比較龐雜,三言兩語難以說清楚,暫且告一段落