从其它平台迁移而来
长期从事Delphi
开发,虽不敢说精通,但说很熟悉还是相当有自信的。不过,只会一门语言,而且还是这么老的语言,更是在大天朝很小众的语言,总感觉自己离饿死街头没多远了,所以趁着还没老再学个潮点的吧。
先前考虑过Python
,初步了解后觉得不太适合自己:
-
解释型语言:部署时得先搞个运行环境,发布的程序就是源码本身,再加上这个执行效率,怎么想都还是编译型语言更合适。
-
动态语言:无需声明,拿来就用,这已经很不合习惯了。想想一个变量,前一秒还是浮点数,下一秒就成字符串了,再一眨眼又成某个对象了……虽然一般不会有人这么写,但是挡不住手误啊,还是把这种小细节交给编译器更让人放心。
所以,对于有点强迫症和洁癖的自己,最后还是选了Go
,比较符合已有的编程习惯,学习成本应该相对会低些吧。
至于Go
嘛,想学是已经很久了,但由于种种原因却迟迟未开启,不过终究还是要迈出这一步的,所以就搞这么个系列来记录吧,一方面算是自我督促,另一方面也算是一种交流吧,当然,若一不留神帮上了谁,那自是开心极了。
言归正传#
已经初步了解过了Go
,说来和Delphi
还是有不少相似之处呢,从Delphi
转向Go
应该会比较轻松吧。
工程结构#
Delphi
的工程算是比较自由的,源码的话,只要把单元路径引了或是直接包含进工程单元里就可以了,编译出的dcu
和最终的exe
指定下路径也就没问题了,通常我都使用下面这种结构:
1
2
3
4
5
6
7
8
9
10
11
|
Project/
bin/
src/
dcu/
mod1/
*.dfm
*.pas
mod2/
*.dfm
*.pas
*.dpr
|
不过,每一个工程都要设置,而且我习惯将Debug
和Release
设置完全一样,也还真是够烦的。
Go
就没得选了,只有一种结构:
1
2
3
4
5
6
7
8
9
10
11
|
Project/
bin/
pkg/
src/
*.go
mod1/
*.go
*_test.go
mod2/
*.go
*_test.go
|
整体和我原有的习惯差不多,还是蛮容易接受的,不过倒是要把这Project
的路径加入到GOPATH
系统变量里让人有一点小不爽。但是Go
可以直接把测试都写了,这点还是蛮让我惊喜的,毕竟用了这么多年Delphi
也没写过一行像样的测试。
源码结构#
Delphi
典型的源码结构是这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
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
的源码结构是这样:
1
2
3
4
5
6
7
8
|
package pkg1
import "fmt" //导入包
const C1 //公开常量
const c2 //私有常量
var V1 //公开变量
var v2 //私有变量
func Func1(){}//公开方法
func func2(){}//私有方法
|
整体上来看,Delphi
语意更明确,Go
则更简洁,Delphi
更集中,Go
则和所有类c语言
一样比较单一、灵活,各有各的好。倒是Go
这用首字母大小写来区分公开私有让我这喜欢pascal命名法
的有点尴尬。
Delphi
1
2
3
4
5
6
7
8
9
10
|
//这是单行注释
{
这是多行注释第一行
这是多行注释第二行
}
(*
这是另外一种多行注释
与{}可以互相包含
主要用于注释内容中有 } 的情况
*)
|
Go
1
2
3
4
5
|
//这是单行注释
/*
这是多行注释第一行
这是多行注释第二行
*/
|
Delphi
安装CnPack
之后,可以使用xml风格
的注释,写在方法前可以成为方法的文档说明。
Go
提供的godoc
也可以从代码中提取顶级声明的首行注释以及每个对象的相关注释生成相关文档。
运算符#
Delphi
- 赋值运算符:
:=
- 算术运算符:
+
、-
、*
、/
、div
、mod
- 逻辑运算符:
not
、and
、or
、xor
- 位运算符:
not
、and
、or
、xor
、shl
、shr
- 关系运算符:
=
、<>
、>
、<
、>=
、<=
- 指针运算符:
^
、+
、-
、=
、<>
- 地址运算符:
@
- 字符串连接符:
+
- 集合运算符:
+
、-
、*
、<=
、>=
、=
、=
、<>
、in
- 类运算符:
as
、is
、=
、<>
Go
- 赋值运算符:
=
、:=
、+=
、-=
、*=
、/=
、%=
、<<=
、>>=
、&=
、|=
、^=
- 算术运算符:
+
、-
、*
、/
、%
、++
、--
- 逻辑运算符:
!
、&&
、||
- 位运算符:
<<
、>>
、&
、|
、^
- 关系运算符:
==
、!=
、>
、<
、>=
、<=
- 指针运算符:
*
- 地址运算符:
&
- 字符串连接符:
+
基本数据类型#
Delphi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
//整型
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
//整型
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
1
2
3
4
5
6
7
8
9
|
//声明变量
var
v1 : string;
v2, v3 : Integer;
//声明并初始化
var
v4 : Double = 3.14;
//变量赋值
v1 := 'abc';
|
Go
1
2
3
4
5
6
7
8
9
10
11
12
13
|
//声明变量
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
1
2
3
4
|
const
c1: string = 'abc';
c2 = '123';
c3 = 456;
|
Go
1
2
3
4
5
6
7
|
const (
c1 string = "abc"
c2 = "123"
c3 = 456
c4 //与c3一样
c5, c6 = 1.2, true
)
|
都用了关键字const
,都支持批量声明,也都支持类型推导,而且Go
的类型推导更智能些,换句话说就叫更复杂些(主要是因为与别的混在一起了)。
Delphi
1
2
3
4
5
6
7
8
|
type myEnum=(
eA, //0
eB, //1
eC = 10, //10
eD, //11
eE, //12
eF //13
);
|
Go
1
2
3
4
5
6
7
8
|
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
1
2
3
4
|
//定义类型
type myInt = type Integer;
//类型别名
type myInt = Integer;
|
Go
1
2
3
4
|
//定义类型
type myInt int
//类型别名
type myInt = int
|
Delphi
1
2
3
4
5
6
7
8
|
//静态数组
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
1
2
3
4
5
6
7
|
//静态数组
var r1 [10]byte
var r2 [2]bool{true,false}
var r3, r4 [4][2]int
//用切片来代替动态数组
var r5 []int
var r6 [][]int
|
Go
没有专门的动态数组,不过有更为灵活、实用的切片,用切片完全可以实现动态数组的功能。
结构体#
Delphi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
//定义
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
1
2
3
4
5
6
7
8
9
10
11
12
|
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
//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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
//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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
//升幂记数循环
for 记数变量 := 起始值 to 终点值 do //起始值<终点值,变量每循环变化1
语句;
//降幂记数循环
for 记数变量 := 起始值 downto 终点值 do //起始值>终点值,变量每循环变化1
语句;
//高版本支持,2009+?
for 循环变量 in 集合 do
语句;
//当型循环,当表达式值为真时执行循环
while 布尔表达式 do
语句;
//直到型循环,当表达式值为真时退出循环
repeat
语句;
until 布尔表达式;
|
Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
//类C的for循环
for 循环变量初始化; 条件表达式; 循环变量改变 {
...
}
//类C的while循环
for 条件表达式 {
...
}
//无限循环
for{
...
}
//类C的for each
for k, v := range R {//R可为数组、切片、map、字符串等
...
}
|
控制语句#
goto#
都用来直接跳转到指定标签处继续向下执行。Delphi
需要先使用label
声明标签,Go
则直接使用标签,标签的语法均为:
continue#
都用来结束本次循环并进入下一次循环。在多层循环中,Go
还可以在其后跟标签,用来结束标签对应的那层循环并进入下一次循环。
break#
都用来结束当前循环并执行当前循环之后的代码。在多层循环中,Go
还可以在其后跟标签,用来结束标签对应的那层循环并执行循环之后的代码。
Delphi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
//过程,无返回值
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
//无返回值
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
函数没有声明只有实现,且不要求被调函数在主调函数之前定义。
以下概念比较庞杂,三言两语难以说清楚,暂且告一段落
错误处理#
面向对象#