從Delphi到Go——基礎

廢話

長期從事Delphi開發,雖不敢說精通,但說很熟悉仍是至關有自信的。不過,只會一門語言,並且仍是這麼老的語言,更是在大天朝很小衆的語言,總感受本身離餓死街頭沒多遠了,因此趁着還沒老再學個潮點的吧。c++

先前考慮過Python,初步瞭解後以爲不太適合本身:編程

  1. 解釋型語言:部署時得先搞個運行環境,發佈的程序就是源碼自己,再加上這個執行效率,怎麼想都仍是編譯型語言更合適。數組

  2. 動態語言:無需聲明,拿來就用,這已經很不合習慣了。想一想一個變量,前一秒仍是浮點數,下一秒就成字符串了,再一眨眼又成某個對象了……雖然通常不會有人這麼寫,可是擋不住手誤啊,仍是把這種小細節交給編譯器更讓人放心。併發

因此,對於有點強迫症潔癖的本身,最後仍是選了Go,比較符合已有的編程習慣,學習成本應該相對會低些吧。ide

至於Go嘛,想學是已經好久了,但因爲種種緣由卻遲遲未開啓,不過終究仍是要邁出這一步的,因此就搞這麼個系列來記錄吧,一方面算是自我督促,另外一方面也算是一種交流吧,固然,若一不留神幫上了誰,那自是開心極了。函數

言歸正傳

已經初步瞭解過了Go,說來和Delphi仍是有很多類似之處呢,從Delphi轉向Go應該會比較輕鬆吧。學習

工程結構

Delphi的工程算是比較自由的,源碼的話,只要把單元路徑引了或是直接包含進工程單元裏就能夠了,編譯出的dcu和最終的exe指定下路徑也就沒問題了,一般我都使用下面這種結構:測試

Project/
  bin/
  src/
    dcu/
    mod1/
      *.dfm
      *.pas
    mod2/
      *.dfm
      *.pas
    *.dpr

不過,每個工程都要設置,並且我習慣將DebugRelease設置徹底同樣,也還真是夠煩的。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

  • 賦值運算符::=
  • 算術運算符:+-*/divmod
  • 邏輯運算符:notandorxor
  • 位運算符:notandorxorshlshr
  • 關係運算符:=<>><>=<=
  • 指針運算符:^+-=<>
  • 地址運算符:@
  • 字符串鏈接符:+
  • 集合運算符:+-*<=>===<>in
  • 類運算符:asis=<>

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還多了一些特性:

  1. 支持類型推導,聲明並初始化時可省略類型。

  2. 在函數內部聲明並初始化變量時還支持用:=的方式簡寫(不過這和Delphi的賦值符號同樣,意義卻截然不同,使用時得留意了)。

  3. 函數內部的變量能夠像c++同樣達到語塊級的做用域和生命期。

  4. 支持多重賦值,交換變量更簡潔。

  5. 支持匿名變量,即用_來佔位。

常量

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沒有枚舉的關鍵字,但使用constiota也能夠達到目的。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、字符串等
  ...
}

控制語句

goto

都用來直接跳轉到指定標籤處繼續向下執行。Delphi須要先使用label聲明標籤,Go則直接使用標籤,標籤的語法均爲:

labelA:
  ...

continue

都用來結束本次循環並進入下一次循環。在多層循環中,Go還能夠在其後跟標籤,用來結束標籤對應的那層循環並進入下一次循環。

break

都用來結束當前循環並執行當前循環以後的代碼。在多層循環中,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
}
  1. Delphi無返回值的用procedure,有返回值的用functionGo統一用func

  2. 都接受0個或多個參數,Delphi同類型參數間用,分隔,不一樣類型參數間用;分隔;Go統一用,分隔。

  3. Delphi函數只能有1個返回值,且返回值固定由Result隱藏變量攜帶,Result可屢次賦值,以後也可有其它語句;Go能夠有0個或多個返回值,返回值變量能夠聲明也能夠不聲明,但必須由return返回且必須爲最後一條語句,若返回值變量有多個且已聲明,return後跟的返回值變量順序必須與定義一致。

  4. Delphi無參過程/函數調用時,括號()可帶可不帶;Go無參函數調用時必須帶()

  5. Delphi被調用的過程/函數必須在主調過程/函數以前實現或在interface區已聲明;Go函數沒有聲明只有實現,且不要求被調函數在主調函數以前定義。


如下概念比較龐雜,三言兩語難以說清楚,暫且告一段落

錯誤處理

接口

面向對象

併發

相關文章
相關標籤/搜索