程序结构与基础
程序结构与单元
Delphi 程序以 'program' 开始,以 'end.'(带句点)结束。{$APPTYPE CONSOLE} 是编译器指令,将其标记为控制台应用。'uses' 导入单元(模块)—— System.SysUtils 有 Format、IntToStr 等。单元有 interface 部分(公共声明)和 implementation 部分(代码)。WriteLn 输出带换行的文本;Write 输出不带换行。ReadLn 读取输入(或暂停)。主 begin..end 块是程序的入口点。
program Demo; // program keyword starts a console app
{$APPTYPE CONSOLE} // compiler directive: console application
uses // import units (like #include or import)
System.SysUtils, // utilities (Format, IntToStr, etc.)
System.Classes; // TList, TStrings, etc.
// a unit has interface (declarations) and implementation (bodies)
// unit Math;
// interface
// function Add(A, B: Integer): Integer;
// implementation
// function Add(A, B: Integer): Integer;
// begin Result := A + B; end;
// end.
var
Name: string;
begin
Name := 'Alice';
WriteLn('Hello, ', Name, '!'); // WriteLn = output with newline
Write('No newline'); // Write = output without newline
ReadLn; // wait for Enter (pause)
end.变量、类型与常量
Delphi 是强类型的。常见类型:Integer(32 位)、Int64(64 位)、Double(64 位浮点)、Extended(x86 上 80 位浮点)、Single(32 位浮点)、string(Unicode,引用计数)、Char(WideChar,2 字节)、Boolean、Byte(0-255)。TDateTime 实际上是 Double(自 1899-12-30 以来的天数)。常量使用 'const' —— 类型化常量有类型,非类型化常量灵活。子范围类型(0..150)限制值。枚举(TDay)定义命名常量。Format() 类似 sprintf:%s(字符串)、%d(整数)、%f(浮点)。
var
Name: string = 'Alice'; // string (managed, reference-counted)
Age: Integer = 30; // 32-bit signed integer
BigNum: Int64 = 9223372036854775807; // 64-bit
Pi: Double = 3.14159; // 64-bit float (IEEE 754)
E: Extended = 2.71828; // 80-bit float (x87)
Rate: Single = 0.5; // 32-bit float
Ch: Char = 'A'; // 2-byte Unicode char (WideChar)
IsDev: Boolean = True; // True/False
Bytes: Byte = 255; // 0..255
Date: TDateTime; // date/time (double)
// constants
const
MaxRetries = 3; // untyped constant
Pi: Double = 3.14159265; // typed constant
Greeting: string = 'Hello';
// type aliases
type
TAge = 0..150; // subrange type
TDay = (Mon, Tue, Wed, Thu, Fri, Sat, Sun); // enumeration
begin
Date := Now; // current date/time
WriteLn(Format('%s is %d', [Name, Age])); // formatted output
WriteLn('Pi = ', Pi:0:2); // 3.14 (width:decimals)
end;运算符与表达式
Delphi 使用 := 进行赋值,使用 = 进行相等比较(与类 C 语言相反)。div 是整数除法;/ 是实数除法(始终返回 Extended/Double)。mod 是取余。and/or/not/xor 适用于布尔值(逻辑)和整数(按位)—— 由上下文决定。shl/shr 是位移。Inc/Dec 是高效的就地递增/递减(避免写 A := A + 1)。字符串连接使用 +。Power() 在 System.Math 中。:= vs = 的区别是初学者错误的头号来源。
var
A, B: Integer;
X, Y: Double;
S1, S2: string;
begin
A := 10; B := 3;
WriteLn(A + B); // 13 (addition)
WriteLn(A - B); // 7
WriteLn(A * B); // 30
WriteLn(A div B); // 3 (integer division)
WriteLn(A mod B); // 1 (remainder)
WriteLn(A / B); // 3.33 (real division, always Extended)
X := 2.0; Y := 3.0;
WriteLn(X + Y); // 5.0
WriteLn(Power(X, Y)); // 8.0 (needs System.Math)
// comparison (return Boolean)
WriteLn(A > B); // TRUE
WriteLn(A = B); // FALSE (= is equality, not assignment!)
WriteLn(A <> B); // TRUE (not equal)
WriteLn(A >= B); // TRUE
// logical operators
WriteLn(True and False); // FALSE
WriteLn(True or False); // TRUE
WriteLn(not True); // FALSE
WriteLn(True xor False); // TRUE
// string concatenation
S1 := 'Hello'; S2 := 'World';
WriteLn(S1 + ', ' + S2 + '!'); // Hello, World!
// bitwise (on integers)
WriteLn(5 and 3); // 1
WriteLn(5 or 3); // 7
WriteLn(5 shl 1); // 10 (shift left = *2)
WriteLn(5 shr 1); // 2 (shift right = /2)
// Inc and Dec (modify in place)
Inc(A); // A := A + 1
Inc(A, 5); // A := A + 5
Dec(A); // A := A - 1
end;输入、输出与格式化
Format() 是 Delphi 的 sprintf —— 使用 %s(字符串)、%d(整数)、%f(浮点)、%x(十六进制)、%m(货币),带宽度/精度修饰符。WriteLn(value:width:decimals) 直接格式化浮点数。ReadLn 将输入读入变量。StrToInt/StrToFloat 将字符串转换为数字(失败时抛出 EConvertError);TryStrToInt 返回 Boolean,更安全。IntToStr/FloatToStr 将数字转换为字符串。FormatDateTime 格式化日期(yyyy、mm、dd、hh、nn、ss)。FloatToStrF 提供精确控制(ffFixed、ffCurrency、ffExponent)。
var
Name: string;
Age: Integer;
Salary: Double;
begin
// console output
WriteLn('Hello, World!'); // with newline
Write('No newline'); // without
WriteLn; // just a newline
// formatted output with Format (like sprintf)
WriteLn(Format('Name: %s, Age: %d', ['Alice', 30]));
WriteLn(Format('Pi: %.4f', [3.14159])); // Pi: 3.1416
WriteLn(Format('Hex: %x', [255])); // Hex: FF
WriteLn(Format('Pad: %10d', [42])); // right-aligned
WriteLn(Format('Left: %-10d|', [42])); // left-aligned
WriteLn(Format('Money: %m', [1234.56])); // currency
// WriteLn with format specifiers (width:decimals)
WriteLn(3.14159:0:2); // 3.14
WriteLn(42:5); // 42 (width 5)
// console input
Write('Enter your name: ');
ReadLn(Name);
Write('Enter your age: ');
ReadLn(Age);
// type conversion functions
Salary := StrToFloat('50000.50');
Age := StrToInt('30');
WriteLn(IntToStr(42)); // '42'
WriteLn(FloatToStr(3.14)); // '3.14'
WriteLn(FloatToStrF(3.14159, ffFixed, 8, 2)); // '3.14'
WriteLn(FormatDateTime('yyyy-mm-dd', Now)); // '2024-06-18'
// TryStrToInt (safe parsing)
if TryStrToInt('123', Age) then
WriteLn('Parsed: ', Age);
end;单元、作用域与可见性
单元是 Delphi 的模块。interface 部分声明公共内容(对用户可见);implementation 包含代码,可以有私有类型/变量。initialization/finalization 部分在单元加载/卸载时运行(类似单元的构造函数/析构函数)。interface 中声明的变量是全局的;implementation 中的是单元私有的。interface 中的类型是公共的;implementation 中的是私有的。这种两段式设计在单元级别强制封装。'uses' 子句导入其他单元 —— 用 UnitName.Identifier 解决命名冲突。
// Unit declaration
unit MathHelper;
interface
uses
System.SysUtils;
// public types (visible to users of the unit)
type
TCalculator = class
public
function Add(A, B: Integer): Integer;
end;
// public constants
const
Pi = 3.14159265358979;
// public variables
var
Counter: Integer;
// public function declarations
function Multiply(A, B: Integer): Integer;
implementation
// private types (only visible inside this unit's implementation)
type
TInternal = record
Value: Integer;
end;
// private variables
var
InternalCount: Integer;
// function body (implementation)
function Multiply(A, B: Integer): Integer;
begin
Result := A * B;
end;
function TCalculator.Add(A, B: Integer): Integer;
begin
Result := A + B;
end;
initialization
// runs when the unit loads
Counter := 0;
InternalCount := 0;
finalization
// runs when the unit unloads (cleanup)
// free resources here
end.控制流
If...Then...Else
If...Then...Else 是 Delphi 的条件语句。关键:'else' 前没有分号 —— 分号结束语句,而 else 是 if 的一部分。对于多语句分支,用 begin..end 包装(else 前仍无分号)。and/or/not 是逻辑运算符(对整数也是按位)。使用括号分组条件:(A > 0) and (B > 0)。else 前缺少分号是初学者最常见的 Delphi 语法错误。
var
Score: Integer;
Grade: string;
begin
Score := 85;
// simple if
if Score >= 60 then
WriteLn('Pass');
// if-else (no semicolon before 'else'!)
if Score >= 90 then
Grade := 'A'
else if Score >= 80 then
Grade := 'B'
else if Score >= 70 then
Grade := 'C'
else
Grade := 'F';
WriteLn('Grade: ', Grade);
// multi-statement if (needs begin..end)
if Score > 50 then
begin
WriteLn('Passed');
WriteLn('Congratulations');
end
else
begin
WriteLn('Failed');
WriteLn('Try again');
end;
// nested with logical operators
if (Score >= 0) and (Score <= 100) then
WriteLn('Valid score');
end;Case(Switch)语句
Case 是 Delphi 的 switch —— 适用于序数类型(Integer、Char、枚举、子范围)。每个分支可以是单个值、逗号分隔列表('D', 'F')或范围(1..5)。else 子句是默认分支。Case 不会 fall-through(不同于 C)。对于多语句分支,使用 begin..end。对于离散值,Case 比链式 if-else 更清晰。你不能直接对字符串使用 case(使用 if-else 或查找表)。
var
Grade: Char;
Day: Integer;
begin
Grade := 'B';
// case on ordinal (integer, char, enum)
case Grade of
'A': WriteLn('Excellent');
'B': WriteLn('Good');
'C': WriteLn('Average');
'D', 'F': WriteLn('Poor'); // multiple values
else
WriteLn('Invalid grade'); // default (else clause)
end;
// case with ranges
Day := 3;
case Day of
1..5: WriteLn('Weekday'); // range
6, 7: WriteLn('Weekend');
else
WriteLn('Invalid day');
end;
// case with enums
type TColor = (Red, Green, Blue);
var C: TColor;
C := Green;
case C of
Red: WriteLn('Stop');
Green: WriteLn('Go');
Blue: WriteLn('Relax');
end;
// case with multi-statement branches
case Grade of
'A': begin
WriteLn('Excellent');
WriteLn('Keep it up');
end;
'B': WriteLn('Good');
end;
end;For 循环(To、Downto、In)
For...to 升序迭代;For...downto 降序迭代。循环变量不能在循环内修改。For...in(现代 Delphi)迭代数组、字符串(逐字符)、集合和任何可枚举对象。Break 退出循环;Continue 跳到下一次迭代。没有内置步长 —— 使用条件或 while 循环。循环变量在循环后未定义(不要依赖其值)。For...in 优先用于集合(更清晰,无索引错误)。
var
i: Integer;
Fruits: array of string;
S: string;
begin
// for...to (ascending)
for i := 1 to 5 do
WriteLn(i); // 1 2 3 4 5
// for...downto (descending)
for i := 5 downto 1 do
WriteLn(i); // 5 4 3 2 1
// for with step (no built-in step — use a while or compute)
for i := 0 to 9 do
if i mod 2 = 0 then
WriteLn(i); // 0 2 4 6 8
// nested loops
for i := 1 to 3 do
for j := 1 to 3 do
Write(i * j, ' ');
WriteLn;
// for...in (iterates collections, modern Delphi)
Fruits := ['apple', 'banana', 'cherry'];
for S in Fruits do
WriteLn(S);
// for...in on string (iterates characters)
for S in 'Hello' do
Write(S, ' '); // H e l l o
WriteLn;
// for...in on set
type TDigits = set of 1..5;
var D: TDigits := [1, 3, 5];
for i in D do
WriteLn(i); // 1 3 5
// Break and Continue
for i := 1 to 100 do
begin
if i > 10 then Break; // exit loop
if i mod 2 = 0 then Continue; // skip to next iteration
WriteLn(i); // 1 3 5 7 9
end;
end;While 与 Repeat...Until
While 在循环体前测试(可能永不执行);repeat...until 在之后测试(始终至少运行一次)。关键:while 在条件为 TRUE 时继续;repeat 在条件为 TRUE 时停止(相反的逻辑!)。repeat...until 不需要 begin..end(它本身就是一个块)。使用 while 表示"零次或多次",使用 repeat 表示"一次或多次"。Break 退出;Continue 跳到测试。while True 配合 Break 是带复杂退出条件循环的常见惯用法。
var
Count: Integer;
Line: string;
begin
// while: test BEFORE (may never run)
Count := 0;
while Count < 3 do
begin
WriteLn(Count);
Inc(Count);
end;
// 0 1 2
// repeat...until: test AFTER (runs at least once)
Count := 0;
repeat
WriteLn(Count);
Inc(Count);
until Count >= 3;
// 0 1 2
// KEY DIFFERENCE: while continues while TRUE; until stops when TRUE
// while X < 3 do ... == repeat ... until X >= 3
// repeat doesn't need begin..end (it's already a block)
Count := 5;
repeat
WriteLn(Count);
Dec(Count);
until Count = 0;
// reading input until a condition
repeat
Write('Enter "quit" to stop: ');
ReadLn(Line);
until (Line = 'quit') or (Line = 'exit');
// infinite loop with Break
while True do
begin
WriteLn('Running...');
if SomeCondition then Break;
end;
// Continue in while
Count := 0;
while Count < 10 do
begin
Inc(Count);
if Count mod 2 = 0 then Continue;
WriteLn(Count); // 1 3 5 7 9
end;
end;With...Do 与 Goto
With...Do 访问记录/对象的成员而无需重复变量名 —— 适用于初始化和减少冗余。避免嵌套 With(关于成员属于哪个对象的歧义)。Goto 跳转到标签 —— 在现代 Delphi 中很少使用(优先使用 Break/Continue/Exit);用 'label' 声明标签。Exit 立即离开过程;Exit(value) 从函数返回值(现代语法)。With 如果过度使用会使代码可读性降低 —— 谨慎用于简单情况。
type
TPerson = record
Name: string;
Age: Integer;
Email: string;
end;
var
P: TPerson;
i: Integer;
label
RetryPoint; // declare a label for Goto
begin
// With...Do: access record/object members without repeating the name
with P do
begin
Name := 'Alice';
Age := 30;
Email := '[email protected]';
WriteLn(Name, ' is ', Age); // instead of P.Name, P.Age
end;
// With on a function result
with TStringList.Create do
try
Add('line 1');
Add('line 2');
SaveToFile('output.txt');
finally
Free;
end;
// nested With (avoid — ambiguous)
with P, TStringList.Create do
try
Add(Name); // Name could be P.Name or TStringList.Name
finally
Free;
end;
// Goto (rarely used — prefer structured alternatives)
i := 0;
RetryPoint:
Inc(i);
WriteLn('Attempt ', i);
if i < 3 then
Goto RetryPoint;
WriteLn('Done after ', i, ' attempts');
// Exit: leave the current procedure/function
if P.Age < 0 then
begin
WriteLn('Invalid age');
Exit; // return immediately
end;
// Exit with a value (for functions)
// Exit(42); // returns 42 from the function
end;字符串与文本处理
字符串类型与操作
Delphi 的默认字符串是 UnicodeString(UTF-16,引用计数,写时复制)。字符串从 1 开始索引(S[1] 是第一个字符)—— C 程序员常见的 bug 来源。Length() 返回字符数。Pos() 查找子字符串(未找到返回 0,而非 -1)。Copy() 提取子字符串(Start, Count)。StringReplace 替换(rfReplaceAll 替换所有出现)。Trim/TrimLeft/TrimRight 移除空白。Split/Join 是现代方法(TArray<string>)。使用 SameText 进行不区分大小写的比较。
var
S: string; // UnicodeString (default, UTF-16, reference-counted)
A: AnsiString; // 8-bit string (legacy, codepage-aware)
W: WideString; // COM-compatible (not reference-counted)
SB: StringBuilder; // mutable, for heavy concatenation
begin
S := 'Hello, World';
// length and indexing (1-indexed!)
WriteLn(Length(S)); // 12
WriteLn(S[1]); // 'H' (first char — 1-indexed!)
WriteLn(S[Length(S)]); // 'd' (last char)
// case conversion
WriteLn(UpperCase(S)); // HELLO, WORLD
WriteLn(LowerCase(S)); // hello, world
// searching
WriteLn(Pos('World', S)); // 8 (1-indexed position, 0 if not found)
WriteLn(Pos('xyz', S)); // 0
// substring
WriteLn(Copy(S, 1, 5)); // 'Hello' (Start, Length)
WriteLn(Copy(S, 8, 5)); // 'World'
// modify (creates new string — strings are immutable-ish)
S := StringReplace(S, 'World', 'Delphi', [rfReplaceAll]);
WriteLn(S); // Hello, Delphi
// trim
WriteLn(Trim(' hi ')); // 'hi'
WriteLn(TrimLeft(' hi ')); // 'hi '
WriteLn(TrimRight(' hi ')); // ' hi'
// split
var Parts: TArray<string>;
Parts := 'a,b,c'.Split([',']);
WriteLn(Length(Parts)); // 3
// join
WriteLn(string.Join('-', Parts)); // 'a-b-c'
// comparison
WriteLn('abc' = 'abc'); // TRUE (case-sensitive)
WriteLn(AnsiCompareText('ABC', 'abc')); // 0 (case-insensitive)
WriteLn(SameText('ABC', 'abc')); // TRUE
end;字符串格式化与转换
Format() 是 Delphi 的 sprintf:%d(整数)、%f(浮点)、%s(字符串)、%x(十六进制)、%m(货币),带宽度/精度修饰符。FloatToStrF 提供精确控制(ffFixed、ffCurrency、ffNumber、ffExponent)。FormatDateTime 格式化日期:yyyy(4 位年)、mm(月)、dd(日)、hh(小时)、nn(分钟)、ss(秒)、dddd(完整星期名)、mmmm(完整月份名)。StrToInt/StrToFloat 在无效输入时抛出 EConvertError;TryStrToInt 返回 Boolean(更安全)。对用户输入始终使用 Try...。
var
N: Integer := 42;
F: Double := 3.14159;
S: string;
D: TDateTime := Now;
begin
// Format (like sprintf)
S := Format('Integer: %d', [N]); // 'Integer: 42'
S := Format('Float: %f', [F]); // 'Float: 3.14'
S := Format('Float: %.4f', [F]); // 'Float: 3.1416'
S := Format('Hex: %x', [N]); // 'Hex: 2a'
S := Format('String: %s', ['Hello']); // 'String: Hello'
S := Format('Padded: %10d', [N]); // ' 42'
S := Format('Left: %-10d|', [N]); // '42 |'
S := Format('Multiple: %s=%d, %.2f', ['x', N, F]);
// FloatToStrF (precise float formatting)
S := FloatToStrF(F, ffFixed, 8, 2); // '3.14'
S := FloatToStrF(F, ffCurrency, 8, 2); // '$3.14'
S := FloatToStrF(1234567, ffNumber, 10, 0); // '1,234,567'
// date/time formatting
S := FormatDateTime('yyyy-mm-dd', D); // '2024-06-18'
S := FormatDateTime('hh:nn:ss', D); // '14:30:00'
S := FormatDateTime('dddd, mmmm d, yyyy', D); // 'Tuesday, June 18, 2024'
// string to number (throws on invalid)
N := StrToInt('123');
F := StrToFloat('3.14');
D := StrToDateTime('2024-06-18');
// safe parsing (TryStrTo...)
if TryStrToInt('123', N) then
WriteLn('Parsed: ', N);
if TryStrToInt('abc', N) then
WriteLn('Valid')
else
WriteLn('Invalid number');
// IntToStr, FloatToStr
WriteLn(IntToStr(42));
WriteLn(FloatToStr(3.14));
end;StringBuilder 与 TStringList
StringBuilder(可变)对构建大字符串的循环很高效 —— Append 就地修改而非创建新字符串。TStringList 是 Delphi 的瑞士军刀:字符串列表,可以排序、搜索、持有键值对(Values[])、加载/保存文件(每项一行)、拆分分隔文本(CommaText、DelimitedText)。TStringList 从 0 开始索引(SL[0]),不同于字符串(S[1])。始终包装在 try..finally 中以 Free。它是 Delphi 中处理文本文件和简单配置最常见的方式。
uses
System.SysUtils, System.Classes;
var
sb: StringBuilder;
SL: TStringList;
i: Integer;
begin
// StringBuilder: efficient concatenation (mutable)
sb := StringBuilder.Create;
try
for i := 1 to 1000 do
sb.Append('Line ').Append(i).AppendLine; // chainable
WriteLn(sb.ToString);
finally
sb.Free;
end;
// TStringList: versatile string collection
SL := TStringList.Create;
try
// add items
SL.Add('apple');
SL.Add('banana');
SL.Add('cherry');
WriteLn(SL.Count); // 3
WriteLn(SL[0]); // 'apple' (0-indexed!)
// sort and find
SL.Sort;
SL.Sorted := True; // auto-sort on Add
idx := SL.IndexOf('banana'); // find (returns -1 if not found)
// comma-separated text
SL.CommaText := 'red,green,blue'; // split into items
WriteLn(SL.CommaText); // 'blue,green,red'
// key=value pairs
SL.Clear;
SL.Values['name'] := 'Alice';
SL.Values['age'] := '30';
WriteLn(SL.Values['name']); // 'Alice'
// file I/O (one line per item)
SL.SaveToFile('items.txt');
SL.LoadFromFile('items.txt');
// delimited text
SL.Delimiter := ';';
SL.DelimitedText := 'a;b;c';
finally
SL.Free;
end;
end;字符操作与编码
Char 是 2 字节 Unicode 字符。Ord() 获取码点;Char() 转换回来。IsDigit/IsLetter/IsWhiteSpace/IsUpper/IsLower 分类字符。ToUpper/ToLower 转换大小写。TEncoding.UTF8.GetBytes 将字符串转换为字节数组(对文件 I/O 和网络必不可少)—— UTF-8 每字符使用 1-4 字节。TEncoding.Unicode 是 UTF-16 LE(始终 2 字节/字符)。Base64(TNetEncoding.Base64)将二进制数据编码为文本以传输。String 和 Char 内部是 UTF-16;为文件存储和网络协议转换为 UTF-8。
var
C: Char;
S: string;
Bytes: TBytes;
i: Integer;
begin
C := 'A';
WriteLn(Ord(C)); // 65 (ASCII/Unicode code point)
WriteLn(Char(66)); // 'B' (code point to char)
// char classification
WriteLn(IsDigit('5')); // TRUE
WriteLn(IsLetter('A')); // TRUE
WriteLn(IsWhiteSpace(' ')); // TRUE
WriteLn(IsUpper('A')); // TRUE
WriteLn(IsLower('a')); // TRUE
// case conversion
WriteLn(ToUpper('a')); // 'A'
WriteLn(ToLower('A')); // 'a'
// iterate characters
S := 'Hello';
for C in S do
Write(C, '(', Ord(C), ') ');
WriteLn; // H(72) e(101) l(108) l(108) o(111)
// string <-> bytes (encoding)
Bytes := TEncoding.UTF8.GetBytes('Hello');
WriteLn(Length(Bytes)); // 5 (ASCII chars are 1 byte in UTF-8)
Bytes := TEncoding.UTF8.GetBytes('héllo');
WriteLn(Length(Bytes)); // 6 (é is 2 bytes in UTF-8)
S := TEncoding.UTF8.GetString(Bytes); // back to string
// other encodings
Bytes := TEncoding.ASCII.GetBytes('Hello');
Bytes := TEncoding.Unicode.GetBytes('Hello'); // UTF-16 LE (2 bytes/char)
// Base64 encoding (for binary in text)
uses System.NetEncoding;
var B64: string := TNetEncoding.Base64.EncodeBytesToString(Bytes);
var Decoded: TBytes := TNetEncoding.Base64.DecodeStringToBytes(B64);
// char arrays
var Chars: array[0..4] of Char;
Chars[0] := 'H'; Chars[1] := 'e'; Chars[2] := 'l'; Chars[3] := 'l'; Chars[4] := 'o';
S := String(Chars); // convert char array to string
end;正则表达式
System.RegularExpressions 提供 TRegex 进行模式匹配。IsMatch 测试;Match 查找第一个;Matches 查找所有。组用括号捕获部分 —— 通过 Groups[1]、Groups[2] 访问(1 开始索引)。Replace 替换匹配($1、$2 引用组)。Split 按模式拆分。常见正则:\d(数字)、\w(单词字符)、\s(空白)、+(一个或多个)、*(零个或多个)、{n}(恰好 n 个)、^/$(开始/结束)。roCompiled 编译以加快重复使用。始终用正则验证用户输入(电子邮件、电话)。
uses
System.RegularExpressions;
var
Input, Pattern: string;
Match: TMatch;
Matches: TMatchCollection;
Result: string;
begin
Input := 'Phone: 123-456-7890, Zip: 10001';
// check if matches
if TRegEx.IsMatch(Input, 'd{3}-d{3}-d{4}') then
WriteLn('Found a phone number');
// find first match
Match := TRegEx.Match(Input, 'd{5}');
if Match.Success then
WriteLn('Zip: ', Match.Value); // '10001'
// find all matches
Matches := TRegEx.Matches(Input, 'd+');
for Match in Matches do
WriteLn(Match.Value); // 123, 456, 7890, 10001
// capture groups
Match := TRegEx.Match('2024-06-18', '(d{4})-(d{2})-(d{2})');
if Match.Success then
begin
WriteLn(Match.Groups[1].Value); // '2024' (year)
WriteLn(Match.Groups[2].Value); // '06' (month)
WriteLn(Match.Groups[3].Value); // '18' (day)
end;
// replace
Result := TRegEx.Replace(Input, 'd', 'X');
// 'Phone: XXX-XXX-XXXX, Zip: XXXXX'
// replace with match reference ($1, $2)
Result := TRegEx.Replace('John Doe', '(w+) (w+)', '$2, $1');
// 'Doe, John'
// split
var Parts: TArray<string>;
Parts := TRegEx.Split('a,b;;c', '[,;]+');
// common patterns
Pattern := '^[w.-]+@[w.-]+.w+$'; // email
Pattern := '^https?://[w./-]+$'; // URL
Pattern := '^d{3}-d{3}-d{4}$'; // US phone
// compiled regex (faster for repeated use)
var Regex := TRegEx.Create('d+', [roCompiled]);
try
Match := Regex.Match(Input);
finally
Regex.Free;
end;
end;数组、记录与集合
静态与动态数组
静态数组在编译时固定大小,带自定义索引范围(array[0..4] 或 array[1..7])。动态数组(array of T)可用 SetLength 调整大小 —— 它们从 0 开始索引且引用计数。High() 返回最后一个索引;Length() 返回计数。对现有动态数组 SetLength 调整大小(增长时保留现有值)。设置为 nil 以释放。动态数组字面量使用 [1, 2, 3]。多维动态数组是"数组的数组"(锯齿)—— 每行可以有不同的长度。
var
// static array (fixed size, compile-time)
Nums: array[0..4] of Integer; // 5 elements, indices 0..4
Matrix: array[0..2, 0..2] of Double; // 3x3 2D array
Days: array[1..7] of string; // 1-indexed (custom range)
// dynamic array (resizable at runtime)
Dyn: array of Integer;
Dyn2D: array of array of Integer; // jagged 2D
i, j: Integer;
begin
// static array
Nums[0] := 10;
Nums[1] := 20;
for i := 0 to High(Nums) do // High(Nums) = 4
WriteLn(Nums[i]);
WriteLn(Length(Nums)); // 5
// dynamic array
SetLength(Dyn, 5); // allocate 5 elements (0-indexed)
Dyn[0] := 1;
Dyn[1] := 2;
for i := 0 to High(Dyn) do
WriteLn(Dyn[i]);
SetLength(Dyn, 10); // resize (preserves existing values)
WriteLn(Length(Dyn)); // 10
Dyn := nil; // free memory
// dynamic array literal (modern Delphi)
Dyn := [1, 2, 3, 4, 5];
WriteLn(Length(Dyn)); // 5
// 2D dynamic array
SetLength(Dyn2D, 3); // 3 rows
for i := 0 to 2 do
begin
SetLength(Dyn2D[i], 3); // 3 cols per row
for j := 0 to 2 do
Dyn2D[i][j] := i * 3 + j;
end;
// array slice (Open Array)
WriteLn(Length(Dyn)); // 5
end;记录(结构体)
记录是值类型(赋值时复制,栈分配)—— 类似 C 的结构体。现代 Delphi 记录可以有方法、属性和可见性(private/public)。记录不需要释放(无堆分配)。变体记录(case...of)创建字段共享内存的联合 —— 适用于类型标签。对小型、轻量级数据(点、坐标、配置)使用记录。对需要继承或多态的较大对象使用类。记录更快(无堆分配)但不能被继承。
type
// simple record (like a struct)
TPoint = record
X, Y: Integer;
end;
// record with methods (modern Delphi)
TPerson = record
Name: string;
Age: Integer;
// methods
function Greet: string;
procedure Birthday;
// properties
property IsAdult: Boolean read GetIsAdult;
private
function GetIsAdult: Boolean;
end;
// variant record (union — fields share memory)
TValue = record
case IsInt: Boolean of
True: (IntVal: Integer);
False: (FloatVal: Double);
end;
// implementing record methods
function TPerson.Greet: string;
begin
Result := 'Hi, I am ' + Name;
end;
procedure TPerson.Birthday;
begin
Inc(Age);
end;
function TPerson.GetIsAdult: Boolean;
begin
Result := Age >= 18;
end;
var
P: TPerson;
Pt: TPoint;
V: TValue;
begin
// record assignment copies all fields (value type)
P.Name := 'Alice';
P.Age := 30;
WriteLn(P.Greet); // Hi, I am Alice
P.Birthday;
WriteLn(P.Age); // 31
WriteLn(P.IsAdult); // TRUE
// record constructors (modern Delphi)
P := TPerson.Create; // zero-initializes
P.Name := 'Bob';
// variant record
V.IsInt := True;
V.IntVal := 42;
WriteLn(V.IntVal); // 42
V.IsInt := False;
V.FloatVal := 3.14;
WriteLn(V.FloatVal); // 3.14
end;集合与枚举
集合是 Delphi 的独特特性 —— 来自枚举或子范围的值集合(最多 256 个元素)。运算符:+(并集)、-(差集)、*(交集)、=(相等)、<=(子集)、in(成员资格)。Include/Exclude 是高效的单元素添加/删除。集合存储为位图(非常快)。常见用途:TFontStyles(fsBold、fsItalic)、用于验证的字符集合(['0'..'9'])、星期几。枚举是序数类型 —— 用 Low() 到 High() 迭代,用 GetEnumName 转换为字符串。集合使标志组合优雅且类型安全。
type
// enumeration
TDay = (Mon, Tue, Wed, Thu, Fri, Sat, Sun);
TColor = (Red, Green, Blue);
// set type (collection of enum values)
TDays = set of TDay;
TColors = set of TColor;
TChars = set of Char; // set of characters
var
Weekdays: TDays;
MyColors: TColors;
D: TDay;
Digits: TChars;
C: Char;
begin
// set operations
Weekdays := [Mon, Tue, Wed, Thu, Fri];
MyColors := [Red, Blue];
// add and remove
Include(Weekdays, Sat); // Weekdays := Weekdays + [Sat]
Exclude(Weekdays, Sun); // Weekdays := Weekdays - [Sun]
// set operators
Weekdays := Weekdays + [Sat]; // union
Weekdays := Weekdays - [Sat]; // difference
Weekend := [Sat, Sun];
if Weekdays * Weekend = [] then // intersection is empty
WriteLn('No overlap');
// membership test
if Mon in Weekdays then
WriteLn('Monday is a weekday');
// iterate enum
for D := Low(TDay) to High(TDay) do
WriteLn(D); // 0 1 2 3 4 5 6 (ord values)
// convert enum to string
WriteLn(GetEnumName(TypeInfo(TDay), Ord(Mon))); // 'Mon'
// set of Char (common for validation)
Digits := ['0'..'9'];
C := '5';
if C in Digits then
WriteLn('Is a digit');
// set comparison
if MyColors = [Red, Blue] then
WriteLn('Equal sets');
if [Red] <= MyColors then // subset
WriteLn('Red is included');
end;TList、TDictionary 与泛型
System.Generics.Collections 提供类型安全的集合:TList<T>(动态数组)、TDictionary<K,V>(哈希映射)、TQueue<T>(FIFO)、TStack<T>(LIFO)、THashSet<T>(唯一元素)。所有都是泛型的(编译时类型检查,无转换)。TList 有 Add/Remove/Delete/Sort/Contains/IndexOf。TDictionary 有 Add/Remove/TryGetValue/Keys/Values。TObjectList<T> 拥有其对象(自动释放它们)—— 当列表应管理对象生命周期时使用它。始终包装在 try..finally 中以 Free(这些是对象,不是记录)。
uses
System.Generics.Collections;
var
Nums: TList<Integer>;
Ages: TDictionary<string, Integer>;
Unique: THashSet<string>;
Queue: TQueue<string>;
Stack: TStack<Integer>;
i: Integer;
K: string;
V: Integer;
begin
// TList<T>: dynamic array (generic)
Nums := TList<Integer>.Create;
try
Nums.Add(1);
Nums.Add(2);
Nums.AddRange([3, 4, 5]);
WriteLn(Nums.Count); // 5
WriteLn(Nums[0]); // 1 (0-indexed)
Nums[0] := 100;
Nums.Remove(2); // by value
Nums.Delete(0); // by index
Nums.Sort; // sort in place
Nums.Reverse;
if Nums.Contains(3) then
WriteLn('Found');
for i in Nums do
WriteLn(i);
finally
Nums.Free;
end;
// TDictionary<TKey, TValue>: hash map
Ages := TDictionary<string, Integer>.Create;
try
Ages.Add('Alice', 30);
Ages.Add('Bob', 25);
Ages['Eve'] := 28; // add or update
if Ages.TryGetValue('Alice', V) then
WriteLn('Alice is ', V); // 30
for K in Ages.Keys do
WriteLn(K);
for V in Ages.Values do
WriteLn(V);
Ages.Remove('Bob');
finally
Ages.Free;
end;
// TQueue<T> (FIFO) and TStack<T> (LIFO)
Queue := TQueue<string>.Create;
try
Queue.Enqueue('first');
Queue.Enqueue('second');
WriteLn(Queue.Dequeue); // 'first'
finally
Queue.Free;
end;
// TObjectList<T> (owns its objects — frees them on Clear/Free)
// uses System.Generics.Collections;
// var People: TObjectList<TPerson>;
// People := TObjectList<TPerson>.Create;
// People.Add(TPerson.Create); // freed when People is freed
end;数组算法与排序
TArray 是数组操作的实用工具类:Sort(带可选自定义 IComparer)、BinarySearch(在排序数组上快速搜索)、Reverse、Copy。TComparer<T>.Construct 内联创建比较函数(匿名方法)。按字段排序需要自定义比较器。BinarySearch 返回 Boolean 和找到的索引 —— 数组必须先排序。对于复杂搜索,带 Break 的线性循环简单清晰。TArray.Sort 是快速排序(平均 O(n log n))。
uses
System.Generics.Collections, System.Generics.Defaults;
var
Nums: TArray<Integer>;
People: TArray<TPerson>;
i: Integer;
begin
// sort an array
Nums := TArray<Integer>.Create(5, 3, 1, 4, 2);
TArray.Sort<Integer>(Nums);
// Nums = [1, 2, 3, 4, 5]
// sort descending (custom comparer)
TArray.Sort<Integer>(Nums, TComparer<Integer>.Construct(
function(const L, R: Integer): Integer
begin
Result := R - L; // reverse comparison
end));
// binary search (array must be sorted)
TArray.Sort<Integer>(Nums);
var Found: Boolean := TArray.BinarySearch<Integer>(Nums, 3, i);
if Found then
WriteLn('Found at index ', i);
// sort array of records by a field
type TPerson = record Name: string; Age: Integer; end;
SetLength(People, 3);
People[0] := TPerson.Create('Alice', 30);
People[1] := TPerson.Create('Bob', 25);
People[2] := TPerson.Create('Carol', 28);
TArray.Sort<TPerson>(People, TComparer<TPerson>.Construct(
function(const L, R: TPerson): Integer
begin
Result := L.Age - R.Age; // sort by age
end));
for i := 0 to High(People) do
WriteLn(People[i].Name, ': ', People[i].Age);
// reverse an array
TArray.Reverse<Integer>(Nums);
// copy an array
var Copy: TArray<Integer>;
Copy := Copy(Nums, 0, Length(Nums));
// find with a predicate
var FoundIdx: Integer := -1;
for i := 0 to High(People) do
if People[i].Age > 28 then
begin
FoundIdx := i;
Break;
end;
end;过程、函数与参数
过程与函数
过程(无返回值)和函数(返回值)是 Delphi 的子程序。Result 变量是返回值 —— 分配给它(函数在结束时返回)。Exit() 立即返回带值(现代语法)。前向声明让你在函数体定义之前调用它(适用于相互递归)。函数可以返回任何类型,包括记录、数组和对象。不带值的 Exit 只是离开过程。Result 变量是隐式声明的,匹配返回类型。
// Procedure: performs an action (no return value)
procedure Greet(Name: string);
begin
WriteLn('Hello, ', Name, '!');
end;
// Function: returns a value
function Add(A, B: Integer): Integer;
begin
Result := A + B; // Result is the return variable
// Exit(42); // alternative: return immediately with 42
end;
// Function with multiple return paths
function Classify(Score: Integer): string;
begin
if Score >= 90 then
Exit('A'); // return immediately
if Score >= 80 then
Exit('B');
Result := 'F'; // default return
end;
// Function returning a record
function MakePoint(X, Y: Integer): TPoint;
begin
Result.X := X;
Result.Y := Y;
end;
// forward declaration (use before full definition)
function Calc(X: Integer): Integer; forward;
procedure Demo;
var
Sum: Integer;
P: TPoint;
begin
Greet('Alice'); // procedure call
Sum := Add(3, 4); // function call
WriteLn(Sum); // 7
WriteLn(Classify(85)); // B
P := MakePoint(3, 4);
WriteLn(Calc(10));
end;
function Calc(X: Integer): Integer;
begin
Result := X * 2;
end;参数:Const、Var、Out、Default
const:只读参数(也避免复制字符串/数组 —— 高效)。var:按引用传递(修改调用者的变量 —— 类似 C# 的 ref)。out:仅输出(调用者不初始化;函数设置它)。默认参数必须最后。开放数组参数(array of T)接受任何数组或字面量 [1,2,3] —— 使用 const 提高效率。const 优先用于字符串和数组(无复制);仅在需要修改调用者的值时使用 var。开放数组从 0 开始索引,无论源数组的边界如何。
// const: can't be modified (also efficient for strings/arrays)
procedure Show(const S: string);
begin
WriteLn(S);
// S := 'new'; // ERROR: can't modify const
end;
// var: pass by reference (can modify caller's variable)
procedure Swap(var A, B: Integer);
var
Temp: Integer;
begin
Temp := A;
A := B;
B := Temp;
end;
// out: output-only parameter (caller doesn't need to initialize)
procedure GetValues(out X, Y: Integer);
begin
X := 10;
Y := 20;
end;
// default parameters (must be at the end)
function Power(Base: Double; Exp: Integer = 2): Double;
begin
Result := Power(Base, Exp); // Math.Power
end;
// open array parameter (accepts any array)
function Sum(const Values: array of Integer): Integer;
var
i: Integer;
begin
Result := 0;
for i := 0 to High(Values) do
Result := Result + Values[i];
end;
// 'const' for open arrays (efficient — no copy)
procedure ShowAll(const Items: array of string);
var
S: string;
begin
for S in Items do
WriteLn(S);
end;
var
X, Y: Integer;
Nums: array[0..4] of Integer;
begin
Swap(X, Y); // var: modifies X and Y
GetValues(X, Y); // out: sets X and Y
WriteLn(Power(3)); // 9 (Exp defaults to 2)
WriteLn(Power(2, 10)); // 1024
WriteLn(Sum([1, 2, 3, 4, 5])); // 15 (open array literal)
ShowAll(['a', 'b', 'c']);
end;重载与默认参数
重载允许多个例程共享一个名称但参数列表不同 —— 编译器选择最佳匹配。需要 'overload' 指令。重载比发明不同名称(AddInt、AddDouble)更清晰。默认参数是替代方案 —— 调用者可以省略它们。当逻辑因类型而异时优先使用重载;对可选值使用默认值。歧义(两个匹配相同的重载)是编译错误。重载必须在参数数量或类型上不同(仅返回类型不够)。
// overloading: same name, different parameters
function Add(A, B: Integer): Integer; overload;
begin
Result := A + B;
end;
function Add(A, B: Double): Double; overload;
begin
Result := A + B;
end;
function Add(A, B, C: Integer): Integer; overload;
begin
Result := A + B + C;
end;
function Add(const Values: array of Integer): Integer; overload;
var
i: Integer;
begin
Result := 0;
for i := 0 to High(Values) do
Result := Result + Values[i];
end;
// default parameters (alternative to some overloads)
function CreateRect(Left, Top: Integer; Width: Integer = 100;
Height: Integer = 50): TRect;
begin
Result := Rect(Left, Top, Left + Width, Top + Height);
end;
var
R: TRect;
begin
WriteLn(Add(1, 2)); // 3 (Integer overload)
WriteLn(Add(1.5, 2.5)); // 4.0 (Double overload)
WriteLn(Add(1, 2, 3)); // 6 (3-arg overload)
WriteLn(Add([1, 2, 3, 4])); // 10 (array overload)
R := CreateRect(10, 20); // uses defaults: 100x50
R := CreateRect(10, 20, 200); // Width=200, Height=50
R := CreateRect(10, 20, 200, 100); // all specified
end;匿名方法与闭包
匿名方法(闭包)是分配给 'reference to' 类型的内联函数/过程。它们从封闭作用域捕获变量(闭包)。'reference to function'/'reference to procedure' 是委托类型。匿名方法启用函数式编程:高阶函数(Apply 接受一个函数)、闭包(MakeMultiplier 返回一个记住 Factor 的函数)和自定义比较器(TComparer<T>.Construct)。它们对泛型排序、事件处理器和回调至关重要。捕获的变量是堆分配的(它们比封闭函数寿命长)。
type
TMathFunc = reference to function(X: Integer): Integer;
TNotifyProc = reference to procedure(Msg: string);
// function that takes a function
function Apply(Func: TMathFunc; Values: array of Integer): Integer;
var
i: Integer;
begin
Result := 0;
for i := 0 to High(Values) do
Result := Result + Func(Values[i]);
end;
// function that returns a function (closure)
function MakeMultiplier(Factor: Integer): TMathFunc;
begin
Result := function(X: Integer): Integer
begin
Result := X * Factor; // captures Factor
end;
end;
var
Double: TMathFunc;
Triple: TMathFunc;
begin
// anonymous method (inline function)
Double := function(X: Integer): Integer
begin
Result := X * 2;
end;
WriteLn(Double(21)); // 42
// use with higher-order functions
WriteLn(Apply(Double, [1, 2, 3, 4])); // 20 (2+4+6+8)
// closure: captures the Factor variable
Triple := MakeMultiplier(3);
WriteLn(Triple(5)); // 15
// anonymous procedure
var Log: TNotifyProc := procedure(Msg: string)
begin
WriteLn('[LOG] ', Msg);
end;
Log('Hello');
// use with TList.Sort (custom comparison)
var Nums: TList<Integer>;
Nums := TList<Integer>.Create;
try
Nums.AddRange([5, 3, 1, 4, 2]);
Nums.Sort(TComparer<Integer>.Construct(
function(const L, R: Integer): Integer
begin
Result := L - R;
end));
finally
Nums.Free;
end;
end;递归与辅助例程
递归是函数调用自身 —— 需要基本情况来终止。阶乘和斐波那契是经典示例。尾递归(递归调用是最后操作)可由编译器优化。嵌套过程/函数在另一个例程内声明,可以访问其变量(词法作用域)—— 适用于不需要在外部可见的辅助函数。注意深度递归的栈溢出(对大输入使用迭代)。记忆化(缓存结果)可以加速斐波那契等递归算法。
// classic recursion
function Factorial(N: Integer): Integer;
begin
if N <= 1 then
Result := 1
else
Result := N * Factorial(N - 1);
end;
// tail recursion (compiler may optimize)
function SumRange(N: Integer; Acc: Integer = 0): Integer;
begin
if N = 0 then
Result := Acc
else
Result := SumRange(N - 1, Acc + N);
end;
// Fibonacci (naive — exponential time)
function Fib(N: Integer): Integer;
begin
if N < 2 then
Result := N
else
Result := Fib(N - 1) + Fib(N - 2);
end;
// nested procedure (helper with access to outer variables)
procedure ProcessData(Data: array of Integer);
var
Total: Integer;
procedure SumAll; // nested, sees Total and Data
var
i: Integer;
begin
Total := 0;
for i := 0 to High(Data) do
Total := Total + Data[i];
end;
function Average: Double; // nested function
begin
if Length(Data) = 0 then
Result := 0
else
Result := Total / Length(Data);
end;
begin
SumAll; // calls nested procedure
WriteLn('Sum: ', Total);
WriteLn('Avg: ', Average:0:2);
end;
begin
WriteLn(Factorial(5)); // 120
WriteLn(SumRange(10)); // 55
WriteLn(Fib(10)); // 55
ProcessData([1, 2, 3, 4, 5]);
end;类与 OOP
类定义、构造函数与析构函数
类是引用类型(堆分配,通过指针访问)。Create 是构造函数;Destroy 是析构函数(始终重写;由 Free 调用)。'inherited' 调用基类的方法。字段按约定使用 F 前缀。属性(property X: Type read GetX write SetX)提供受控访问 —— 调用者使用 P.Age 但 setter 进行验证。可见性:private(旧版 Delphi 中仅单元;strict private 是真正的私有)、protected(子类)、public(所有人)、published(RTTI,用于窗体/检查器)。始终将对象创建包装在 try..finally 中以确保调用 Free。
type
TPerson = class
private
FName: string; // private field (convention: F prefix)
FAge: Integer;
procedure SetAge(Value: Integer); // setter for validation
protected
// visible to subclasses
function GetDescription: string; virtual;
public
constructor Create(Name: string; Age: Integer); // constructor
destructor Destroy; override; // destructor
// properties (with getters/setters)
property Name: string read FName; // read-only
property Age: Integer read FAge write SetAge; // validated
property Description: string read GetDescription;
// method
function Greet: string; virtual;
end;
constructor TPerson.Create(Name: string; Age: Integer);
begin
inherited Create; // call base constructor (TObject.Create)
FName := Name;
FAge := Age;
end;
destructor TPerson.Destroy;
begin
// free owned objects here
inherited; // call base destructor
end;
procedure TPerson.SetAge(Value: Integer);
begin
if (Value < 0) or (Value > 150) then
raise ERangeError.Create('Invalid age');
FAge := Value;
end;
function TPerson.GetDescription: string;
begin
Result := Format('%s (%d)', [FName, FAge]);
end;
function TPerson.Greet: string;
begin
Result := 'Hi, I am ' + FName;
end;
var
P: TPerson;
begin
P := TPerson.Create('Alice', 30);
try
WriteLn(P.Greet); // Hi, I am Alice
WriteLn(P.Description); // Alice (30)
P.Age := 31; // uses setter
// P.Age := 200; // raises ERangeError
finally
P.Free; // calls destructor
end;
end;属性与索引属性
属性用 getter/setter 封装字段访问。只读属性只有 'read' 说明符。'default' 指令使索引属性成为默认 —— 这样 L[i] 可用而非 L.Items[i]。属性可以有直接字段访问(read FCount)或方法访问(read GetItem write SetItem)进行验证/计算。索引属性启用类似数组的语法。Published 属性(published 部分)对 RTTI 和窗体设计器可见。属性是 Delphi 安全公开数据的方式 —— 始终优先使用它们而非公共字段。
type
TList = class
private
FItems: array of Integer;
FCount: Integer;
function GetItem(Index: Integer): Integer;
procedure SetItem(Index: Integer; Value: Integer);
public
constructor Create;
destructor Destroy; override;
procedure Add(Value: Integer);
// default array property (enables List[i] syntax)
property Items[Index: Integer]: Integer read GetItem write SetItem; default;
property Count: Integer read FCount;
end;
constructor TList.Create;
begin
inherited Create;
FCount := 0;
end;
destructor TList.Destroy;
begin
SetLength(FItems, 0);
inherited;
end;
function TList.GetItem(Index: Integer): Integer;
begin
if (Index < 0) or (Index >= FCount) then
raise ERangeError.Create('Index out of range');
Result := FItems[Index];
end;
procedure TList.SetItem(Index: Integer; Value: Integer);
begin
if (Index < 0) or (Index >= FCount) then
raise ERangeError.Create('Index out of range');
FItems[Index] := Value;
end;
procedure TList.Add(Value: Integer);
begin
Inc(FCount);
SetLength(FItems, FCount);
FItems[FCount - 1] := Value;
end;
var
L: TList;
begin
L := TList.Create;
try
L.Add(10);
L.Add(20);
WriteLn(L[0]); // 10 (default property — no need for L.Items[0])
L[1] := 99; // uses setter
WriteLn(L.Count); // 2
finally
L.Free;
end;
end;继承与多态
继承:TDog = class(TAnimal) 表示 TDog 继承自 TAnimal。'virtual' 标记方法用于多态;'override' 在子类中替换它。运行时,实际对象的方法运行(虚拟分派)—— 在持有 TDog 的 TAnimal 引用上调用 Speak 会调用 TDog.Speak。静态方法(Move)由变量的类型决定,而非对象的。'inherited' 调用基方法。构造函数可以是虚拟的(工厂模式)。使用 virtual/override 实现多态;行为固定时使用静态方法。始终释放你创建的对象。
type
TAnimal = class
public
constructor Create; virtual; // virtual constructor (factory pattern)
function Speak: string; virtual; // virtual: can be overridden
function Move: string; // static: can't be overridden
end;
TDog = class(TAnimal)
public
constructor Create; override;
function Speak: string; override; // override the virtual method
end;
TCat = class(TAnimal)
public
function Speak: string; override;
end;
constructor TAnimal.Create;
begin
inherited;
end;
function TAnimal.Speak: string;
begin
Result := '...';
end;
function TAnimal.Move: string;
begin
Result := 'Moving';
end;
constructor TDog.Create;
begin
inherited Create; // call TAnimal.Create
WriteLn('Dog created');
end;
function TDog.Speak: string;
begin
Result := 'Woof';
end;
function TCat.Speak: string;
begin
Result := 'Meow';
end;
// polymorphism: array of base class, different behaviors
var
Animals: array of TAnimal;
i: Integer;
begin
SetLength(Animals, 3);
Animals[0] := TDog.Create;
Animals[1] := TCat.Create;
Animals[2] := TAnimal.Create;
for i := 0 to High(Animals) do
begin
WriteLn(Animals[i].Speak); // Woof, Meow, ... (virtual dispatch)
WriteLn(Animals[i].Move); // Moving, Moving, Moving (static)
end;
for i := 0 to High(Animals) do
Animals[i].Free;
end;抽象方法与类方法
抽象类(class abstract)不能被实例化 —— 它们为子类定义契约。抽象方法(virtual; abstract)没有实现 —— 子类必须重写它们。这强制每个形状提供 Area/Perimeter。类方法(class function/procedure)不需要实例 —— 通过 TShape.ShapeCount 调用。类变量(class var)在所有实例间共享。模板方法模式:TShape.Describe 调用抽象的 Area/Perimeter,由子类填充。抽象方法定义"做什么";子类定义"如何做"。
type
TShape = class abstract // can't be instantiated directly
public
function Area: Double; virtual; abstract; // must be overridden
function Perimeter: Double; virtual; abstract;
procedure Describe; virtual;
// class method (no instance needed)
class function ShapeCount: Integer; static;
class var FCount: Integer; // class variable (shared)
end;
TCircle = class(TShape)
private
FRadius: Double;
public
constructor Create(Radius: Double);
function Area: Double; override;
function Perimeter: Double; override;
end;
TRectangle = class(TShape)
private
FWidth, FHeight: Double;
public
constructor Create(W, H: Double);
function Area: Double; override;
function Perimeter: Double; override;
end;
class function TShape.ShapeCount: Integer;
begin
Result := FCount;
end;
procedure TShape.Describe;
begin
WriteLn(Format('Area: %.2f, Perimeter: %.2f', [Area, Perimeter]));
end;
constructor TCircle.Create(Radius: Double);
begin
inherited Create;
FRadius := Radius;
Inc(FCount);
end;
function TCircle.Area: Double;
begin
Result := Pi * FRadius * FRadius;
end;
function TCircle.Perimeter: Double;
begin
Result := 2 * Pi * FRadius;
end;
var
S: TShape;
begin
// TShape.Create; // ERROR: abstract class can't be instantiated
S := TCircle.Create(5);
try
S.Describe; // Area: 78.54, Perimeter: 31.42
WriteLn(TShape.ShapeCount); // class method (no instance)
finally
S.Free;
end;
end;接口与多重继承
接口是纯契约(无字段,无实现)—— Delphi 实现类型多重继承的方式。一个类可以实现多个接口(TButton 实现 IComparable、IDrawable、IDisposable)。接口可以有 GUID 用于 QueryInterface/as 转换。TInterfacedObject 提供引用计数 —— 当最后一个接口引用超出作用域时,对象自动释放(不要调用 Free!)。使用接口解耦:代码依赖 IDrawable,而非 TButton。'as' 运算符转换为接口(不支持时抛出)。接口是 Delphi COM 支持和现代插件架构的支柱。
type
// interface: pure contract (no implementation, no fields)
IComparable = interface
function CompareTo(Other: TObject): Integer;
end;
IDrawable = interface
procedure Draw;
end;
// interfaces have GUIDs (for QueryInterface / as operator)
IDisposable = interface
['{12345678-1234-1234-1234-123456789012}']
procedure Dispose;
end;
// class implementing multiple interfaces
TButton = class(TInterfacedObject, IComparable, IDrawable, IDisposable)
private
FLabel: string;
public
constructor Create(ALabel: string);
function CompareTo(Other: TObject): Integer;
procedure Draw;
procedure Dispose;
end;
constructor TButton.Create(ALabel: string);
begin
FLabel := ALabel;
end;
function TButton.CompareTo(Other: TObject): Integer;
begin
Result := CompareText(FLabel, (Other as TButton).FLabel);
end;
procedure TButton.Draw;
begin
WriteLn('Drawing button: ', FLabel);
end;
procedure TButton.Dispose;
begin
WriteLn('Disposing ', FLabel);
end;
var
Btn: TButton;
Drawable: IDrawable;
Comp: IComparable;
begin
Btn := TButton.Create('OK');
Btn.Draw;
// assign to interface variable (reference counting!)
Drawable := Btn as IDrawable;
Drawable.Draw;
Comp := Btn;
WriteLn(Comp.CompareTo(Btn)); // 0
// TInterfacedObject uses reference counting
// when the last interface reference is released, the object is freed
// (don't call Free on interface-referenced objects!)
end;异常与错误处理
Try...Except...Finally
try...except 捕获异常(类似 C# 的 try/catch)。每个 'on E: ExceptionType do' 处理特定异常。try...finally 确保无论是否有异常都运行清理(无异常处理 —— 用于 Free 调用)。模式是 try...try...except...finally(内部 except 处理,外部 finally 清理)。'raise'(裸)重新引发当前异常。Exception 是基类;EFileNotFoundException、EInOutError 是子类。始终先放最特定的异常,Exception(基类)最后。切勿留空 except(静默吞掉错误)。
var
F: TextFile;
S: string;
begin
// try...except: catch exceptions
try
AssignFile(F, 'nonexistent.txt');
Reset(F);
ReadLn(F, S);
CloseFile(F);
except
on E: EFileNotFoundException do
WriteLn('File not found: ', E.Message);
on E: EInOutError do
WriteLn('I/O error: ', E.Message);
on E: Exception do // catch-all (must be last)
WriteLn('Unexpected: ', E.ClassName, ': ', E.Message);
end;
// try...finally: cleanup (always runs, even on exception)
var SL: TStringList;
SL := TStringList.Create;
try
SL.LoadFromFile('data.txt');
WriteLn(SL.Text);
finally
SL.Free; // ALWAYS runs, even if an exception occurred
end;
// combined: try...try...except...finally
SL := TStringList.Create;
try
try
SL.LoadFromFile('data.txt');
except
on E: Exception do
begin
WriteLn('Error loading: ', E.Message);
SL.Clear; // fallback
end;
end;
WriteLn(SL.Text);
finally
SL.Free;
end;
// re-raise
try
RiskyOperation;
except
on E: Exception do
begin
WriteLn('Logging: ', E.Message);
raise; // re-raise the same exception
end;
end;
end;引发与自定义异常
Raise 创建异常:raise ExceptionType.Create('message')。CreateFmt 类似 Format + Create。自定义异常继承自 Exception(或特定子类),可以携带额外数据(TransactionId)。包装时,通过 SetInner 或构造函数参数保留原始异常。自定义异常让调用者捕获特定错误类型:将 ETransactionError 与 ERangeError 分开捕获。始终包含有意义的消息。常见内置异常:ERangeError、EDivByZero、EConvertError、EFileNotFoundException、EAccessViolation、EListError。
uses
System.SysUtils;
// raise built-in exceptions
procedure CheckAge(Age: Integer);
begin
if Age < 0 then
raise ERangeError.CreateFmt('Age cannot be negative: %d', [Age]);
if Age > 150 then
raise ERangeError.Create('Age unrealistic');
end;
// raise with inner exception (wrapping)
function LoadConfig(Path: string): string;
begin
try
Result := TFile.ReadAllText(Path);
except
on E: Exception do
raise EConfigError.Create('Config load failed').SetInner(E);
end;
end;
// custom exception class
type
ETransactionError = class(Exception)
private
FTransactionId: string;
public
constructor Create(const Msg, TxnId: string);
property TransactionId: string read FTransactionId;
end;
constructor ETransactionError.Create(const Msg, TxnId: string);
begin
inherited Create(Msg);
FTransactionId := TxnId;
end;
// using the custom exception
procedure ProcessPayment(Amount: Double; TxnId: string);
begin
if Amount <= 0 then
raise ETransactionError.Create('Amount must be positive', TxnId);
// ... process
end;
var
E: Exception;
begin
try
CheckAge(-5);
except
on E: ERangeError do
WriteLn('Range error: ', E.Message);
end;
try
ProcessPayment(-100, 'TXN-001');
except
on E: ETransactionError do
WriteLn('Transaction ', E.TransactionId, ' failed: ', E.Message);
end;
end;断言与调试
Assert 检查条件,如果为 false 则引发 EAssertionFailed —— 用于不变量(必须始终为真的条件)。断言用 {$C-} 禁用(或在发布构建中移除)—— 不要用于输入验证(使用异常)。OutputDebugString 记录到 IDE 的事件日志(无文件 I/O)。TStopwatch 精确测量经过的时间。Exception.StackTrace 需要调试信息(.map 文件或 JCLDebug/FastMM)。{$IFDEF DEBUG} 启用仅调试代码。对内部逻辑错误使用断言,对用户/外部错误使用异常。
uses
System.SysUtils, System.Diagnostics;
var
Age: Integer;
SW: TStopwatch;
begin
// Assert: checks a condition (only in {$C+} / debug builds)
Age := 30;
Assert(Age >= 0, 'Age should be non-negative');
// Assert(Age < 0, 'This will raise EAssertionFailed');
// {$C+} / {$C-}: enable/disable assertions
{$C-} // disable assertions (release builds)
Assert(False, 'This won''t fire');
{$C+} // re-enable
// OutputDebugString (visible in IDE debugger)
OutputDebugString('Processing started');
// TStopwatch for timing
SW := TStopwatch.StartNew;
Sleep(100);
SW.Stop;
WriteLn(Format('Elapsed: %d ms', [SW.ElapsedMilliseconds]));
// raise with stack trace (uses System.DebugUtils / JCLDebug)
try
raise Exception.Create('Test error');
except
on E: Exception do
begin
WriteLn(E.Message);
WriteLn(E.StackTrace); // needs debug info / map file
end;
end;
// conditional compilation
{$IFDEF DEBUG}
WriteLn('Debug build');
{$ELSE}
WriteLn('Release build');
{$ENDIF}
// Trace (simple logging)
{$IFDEF DEBUG}
WriteLn('[TRACE] Entering ProcessData');
{$ENDIF}
end;异常处理模式
常见异常模式:(1) 重试循环 —— 在 while 循环内的 try/except 中包装易失败操作,MaxRetries 后重新引发。(2) 回退值 —— 捕获特定异常(EConvertError)并返回默认值;仅吞掉你真正预期的异常。(3) 资源保护 —— 始终在 try/finally 中包装 Create/Free,以便即使在异常时对象也被释放(这是最重要的 Delphi 惯用法)。(4) 多个资源 —— 嵌套 try/finally 块;在每个自己的保护块中获取每个资源。(5) 验证 —— 尽早引发特定异常类型(EArgumentException、ERangeError),带描述性消息。切勿捕获 Exception 并静默继续 —— 至少记录它。优先使用 try/finally 进行清理,try/except 进行真正的恢复。
uses
System.SysUtils, System.Classes;
// Pattern 1: Retry with backoff
function DownloadWithRetry(const URL: string; MaxRetries: Integer): string;
var
Attempt: Integer;
Done: Boolean;
begin
Attempt := 0;
Done := False;
while (not Done) and (Attempt < MaxRetries) do
begin
Inc(Attempt);
try
Result := DoDownload(URL); // may raise EDownloadError
Done := True;
except
on E: Exception do
begin
if Attempt >= MaxRetries then
raise; // re-raise after final attempt
Sleep(Attempt * 500); // exponential-ish backoff
end;
end;
end;
end;
// Pattern 2: Fallback / default value
function SafeReadInt(const SL: TStringList; const Key: string; Default: Integer): Integer;
begin
try
Result := StrToInt(SL.Values[Key]);
except
on EConvertError do
Result := Default; // swallow and use default
end;
end;
// Pattern 3: Resource protection (always Free)
procedure ProcessFile(const Path: string);
var
SL: TStringList;
begin
SL := TStringList.Create;
try
SL.LoadFromFile(Path);
Transform(SL);
SL.SaveToFile(Path + '.bak');
finally
SL.Free; // guaranteed cleanup
end;
end;
// Pattern 4: Acquire multiple resources safely
procedure CopyFile(const Src, Dst: string);
var
SrcList, DstList: TStringList;
begin
SrcList := TStringList.Create;
try
SrcList.LoadFromFile(Src);
DstList := TStringList.Create;
try
DstList.Assign(SrcList);
DstList.SaveToFile(Dst);
finally
DstList.Free;
end;
finally
SrcList.Free;
end;
end;
// Pattern 5: Validation with multiple checks
procedure ValidateUser(const Name: string; Age: Integer);
begin
if Name = '' then
raise EArgumentException.Create('Name required');
if Length(Name) > 50 then
raise EArgumentException.Create('Name too long');
if (Age < 0) or (Age > 150) then
raise ERangeError.CreateFmt('Invalid age: %d', [Age]);
end;日志与错误报告
生产日志器需要:(1) 线程安全 —— TCriticalSection 序列化写入(多线程可能并发记录)。(2) 严重性级别 —— TLogLevel 枚举让你过滤(例如,在生产中抑制 llDebug)。(3) 格式化输出 —— 每行 DateTime + 级别 + 消息,稍后可解析。(4) 每次写入后刷新 —— 以便日志在崩溃中存活(未刷新的缓冲写入在 AV 时丢失)。(5) 异常日志记录 —— LogException 捕获 ClassName + Message + 上下文。记录并重新引发模式记录错误但仍让上层处理它。对于高性能日志记录,考虑无锁队列或外部库(如 Log4Delphi)。始终在 finally 中 Free 日志器以关闭文件句柄。
uses
System.SysUtils, System.Classes, System.IOUtils, System.SyncObjs;
type
TLogLevel = (llDebug, llInfo, llWarning, llError, llFatal);
TLogger = class
private
FLock: TCriticalSection;
FFile: TextFile;
FMinLevel: TLogLevel;
function LevelToStr(L: TLogLevel): string;
public
constructor Create(const LogPath: string; MinLevel: TLogLevel);
destructor Destroy; override;
procedure Log(Level: TLogLevel; const Msg: string); overload;
procedure Log(Level: TLogLevel; const Fmt: string; const Args: array of const); overload;
procedure LogException(E: Exception; const Context: string);
end;
constructor TLogger.Create(const LogPath: string; MinLevel: TLogLevel);
begin
FLock := TCriticalSection.Create;
FMinLevel := MinLevel;
AssignFile(FFile, LogPath);
if FileExists(LogPath) then
Append(FFile)
else
Rewrite(FFile);
end;
destructor TLogger.Destroy;
begin
CloseFile(FFile);
FLock.Free;
inherited;
end;
function TLogger.LevelToStr(L: TLogLevel): string;
begin
case L of
llDebug: Result := 'DEBUG';
llInfo: Result := 'INFO';
llWarning: Result := 'WARN';
llError: Result := 'ERROR';
llFatal: Result := 'FATAL';
end;
end;
procedure TLogger.Log(Level: TLogLevel; const Msg: string);
begin
if Level < FMinLevel then Exit;
FLock.Enter;
try
WriteLn(FFile, Format('%s [%s] %s', [DateTimeToStr(Now), LevelToStr(Level), Msg]));
Flush(FFile); // ensure written to disk
finally
FLock.Leave;
end;
end;
procedure TLogger.Log(Level: TLogLevel; const Fmt: string; const Args: array of const);
begin
Log(Level, Format(Fmt, Args));
end;
procedure TLogger.LogException(E: Exception; const Context: string);
begin
Log(llError, '%s: %s: %s', [Context, E.ClassName, E.Message]);
end;
// Usage
var
Logger: TLogger;
begin
Logger := TLogger.Create('app.log', llInfo);
try
Logger.Log(llInfo, 'Application started');
try
RiskyOperation;
except
on E: Exception do
begin
Logger.LogException(E, 'RiskyOperation');
raise; // log and re-raise
end;
end;
finally
Logger.Free;
end;
end;文件 I/O 与流
文本文件(旧版与现代)
两种方法:旧版(AssignFile/Reset/Rewrite/ReadLn/WriteLn/CloseFile)是经典 Pascal —— 适用于简单文本 I/O 但容易出错(默认无异常)。现代(System.IOUtils 中的 TFile)更清晰:WriteAllText、ReadAllText、ReadAllLines、AppendAllText、Exists。TFile 方法在错误时引发异常(使用 try...except)。对于大文件,使用 StreamReader/StreamWriter(逐行,低内存)。始终关闭文件(旧版用 CloseFile,或使用 try..finally)。TFile 优先用于新代码 —— 它更安全且更一致。
uses
System.SysUtils, System.Classes, System.IOUtils;
// LEGACY: AssignFile / ReadLn / WriteLn (Pascal-style)
var
F: TextFile;
Line: string;
begin
// write
AssignFile(F, 'output.txt');
Rewrite(F); // create/overwrite
try
WriteLn(F, 'Hello, File!');
WriteLn(F, 'Second line');
finally
CloseFile(F);
end;
// append
AssignFile(F, 'output.txt');
Append(F);
try
WriteLn(F, 'Appended line');
finally
CloseFile(F);
end;
// read line by line
AssignFile(F, 'output.txt');
Reset(F); // open for reading
try
while not EOF(F) do
begin
ReadLn(F, Line);
WriteLn(Line);
end;
finally
CloseFile(F);
end;
end;
// MODERN: TFile (System.IOUtils)
var
Content: string;
Lines: TArray<string>;
begin
// write all text
TFile.WriteAllText('output.txt', 'Hello, World!');
// append
TFile.AppendAllText('log.txt', 'New entry' + sLineBreak);
// read all text
Content := TFile.ReadAllText('output.txt');
// read all lines
Lines := TFile.ReadAllLines('data.csv');
for Line in Lines do
WriteLn(Line);
// write all lines
TFile.WriteAllLines('nums.txt', ['one', 'two', 'three']);
// file exists?
if TFile.Exists('data.txt') then
WriteLn('Found');
end;用于文件与 CSV 的 TStringList
TStringList 是处理文本文件和简单 CSV 的最简单方式。LoadFromFile/SaveToFile 读/写整个文件(每项一行)。CommaText 拆分/连接逗号分隔值;DelimitedText 使用自定义 Delimiter。Values[] 处理键值对(类似简单 INI 文件)。Sorted=True 自动排序;Find 执行二分搜索(比排序列表上的 IndexOf 快)。Duplicates 控制添加重复项的行为(dupIgnore、dupAccept、dupError)。对于复杂 CSV(带逗号的引号字段),使用专用 CSV 解析器。TStringList 从 0 开始索引。
uses
System.Classes;
var
SL: TStringList;
i: Integer;
begin
SL := TStringList.Create;
try
// load a text file (one line per item)
SL.LoadFromFile('data.txt');
// iterate lines
for i := 0 to SL.Count - 1 do
WriteLn(SL[i]);
// add and save
SL.Add('New line');
SL.SaveToFile('output.txt');
// CSV handling (CommaText)
SL.Clear;
SL.CommaText := 'Alice,30,NYC';
WriteLn(SL[0]); // Alice
WriteLn(SL[1]); // 30
WriteLn(SL[2]); // NYC
// custom delimiter
SL.Clear;
SL.Delimiter := '|';
SL.DelimitedText := 'a|b|c';
// key=value pairs (INI-style)
SL.Clear;
SL.Values['name'] := 'Alice';
SL.Values['age'] := '30';
WriteLn(SL.Values['name']); // Alice
SL.SaveToFile('config.ini');
// sorted list (auto-sorts on Add)
SL.Clear;
SL.Sorted := True;
SL.Add('cherry');
SL.Add('apple');
SL.Add('banana');
// SL is now: apple, banana, cherry
// find (binary search — list must be sorted)
if SL.Find('banana', i) then
WriteLn('Found at ', i);
// duplicate handling
SL.Duplicates := dupIgnore; // ignore duplicates (sorted only)
SL.Duplicates := dupError; // raise on duplicates
finally
SL.Free;
end;
end;流与二进制 I/O
TFileStream 是低级字节 I/O(Read/Write 缓冲区,Position 用于定位)。TBinaryWriter/Reader 写/读类型化值(Int32、Double、String、Boolean)—— 读取顺序必须匹配写入顺序。TStreamReader/Writer 处理带编码的文本(UTF-8、ASCII、Unicode)—— 用于带非 ASCII 字符的文本文件。所有流都必须释放(try..finally)。fmCreate 创建/覆盖;fmOpenRead 只读打开;fmOpenWrite 为写入打开。对于大文件,使用 StreamReader 逐行读取(低内存)而非 LoadFromFile(加载整个文件)。
uses
System.Classes, System.SysUtils;
var
FS: TFileStream;
BR: TBinaryReader;
BW: TBinaryWriter;
SR: TStreamReader;
SW: TStreamWriter;
Buffer: TBytes;
i: Integer;
begin
// TFileStream: low-level file access
FS := TFileStream.Create('data.bin', fmCreate); // fmCreate, fmOpenRead, fmOpenWrite
try
// write bytes
SetLength(Buffer, 4);
Buffer[0] := 1; Buffer[1] := 2; Buffer[2] := 3; Buffer[3] := 4;
FS.Write(Buffer[0], Length(Buffer));
// read
FS.Position := 0; // rewind
SetLength(Buffer, 4);
FS.Read(Buffer[0], 4);
finally
FS.Free;
end;
// TBinaryWriter / TBinaryReader (typed binary I/O)
BW := TBinaryWriter.Create('data.bin');
try
BW.Write(42); // Integer
BW.Write(3.14); // Double
BW.Write('Hello'); // length-prefixed string
BW.Write(True); // Boolean
finally
BW.Free;
end;
BR := TBinaryReader.Create('data.bin');
try
WriteLn(BR.ReadInt32); // 42
WriteLn(BR.ReadDouble); // 3.14
WriteLn(BR.ReadString); // Hello
WriteLn(BR.ReadBoolean); // TRUE
finally
BR.Free;
end;
// TStreamReader / TStreamWriter (text, with encoding)
SW := TStreamWriter.Create('utf8.txt', False, TEncoding.UTF8);
try
SW.WriteLine('Hello, UTF-8!');
SW.WriteLine('héllo wörld');
finally
SW.Free;
end;
SR := TStreamReader.Create('utf8.txt', TEncoding.UTF8);
try
while not SR.EndOfStream do
WriteLn(SR.ReadLine);
finally
SR.Free;
end;
end;目录与路径操作
System.IOUtils 为现代文件操作提供 TPath、TFile、TDirectory。TPath.Combine 安全连接路径(跨平台)。TPath.GetTempFileName 创建唯一的临时文件。TDirectory.GetFiles 支持搜索模式和递归搜索(soAllDirectories)。TFile.Copy/Move/Delete 是简单的文件操作。TFileInfo 提供文件元数据(大小、时间戳)。始终使用 TPath 方法而非字符串连接处理路径(正确处理分隔符)。这些类在 Windows、macOS 和 Linux 上工作(FireMonkey/FMX)。
uses
System.IOUtils, System.SysUtils;
var
Files: TArray<string>;
Dirs: TArray<string>;
Path: string;
i: Integer;
begin
// TPath (cross-platform path handling)
Path := TPath.Combine('folder', 'sub', 'file.txt'); // folder/sub/file.txt
WriteLn(TPath.GetFileName('C:\temp\data.txt')); // data.txt
WriteLn(TPath.GetExtension('photo.JPG')); // .JPG
WriteLn(TPath.GetFileNameWithoutExtension('data.txt')); // data
WriteLn(TPath.GetDirectoryName('C:\temp\data.txt')); // C:\temp
WriteLn(TPath.GetFullPath('data.txt')); // absolute path
// temp files
WriteLn(TPath.GetTempFileName); // creates a temp file
WriteLn(TPath.GetTempPath); // temp directory
// special folders
WriteLn(TPath.GetDocumentsPath);
WriteLn(TPath.GetHomePath);
// TDirectory
TDirectory.CreateDirectory('backup\2024\june');
Files := TDirectory.GetFiles('C:\temp', '*.txt');
for i := 0 to High(Files) do
WriteLn(Files[i]);
// recursive search
Files := TDirectory.GetFiles('C:\temp', '*.*', TSearchOption.soAllDirectories);
Dirs := TDirectory.GetDirectories('C:\temp');
for i := 0 to High(Dirs) do
WriteLn(Dirs[i]);
if TDirectory.Exists('old') then
TDirectory.Delete('old', True); // recursive delete
// TFile operations
TFile.Copy('source.txt', 'dest.txt', True); // overwrite
TFile.Move('old.txt', 'new.txt');
TFile.Delete('unwanted.txt');
// file info
var Info: TFileInfo := TFileInfo.Create('data.txt');
try
WriteLn(Info.Length); // size in bytes
WriteLn(Info.CreationTime);
WriteLn(Info.LastWriteTime);
WriteLn(Info.Extension);
finally
Info.Free;
end;
end;INI 文件与 JSON
TIniFile 读/写 INI 配置文件([方括号] 中的节,键=值)。ReadString/ReadInteger/ReadBool 有默认值(键缺失时返回)。INI 文件是简单、人类可读的配置 —— 适用于用户首选项。对于结构化数据,使用 JSON(System.JSON)。TJSONObject 构建/解析 JSON 对象;TJSONArray 用于数组。AddPair 添加键值;GetValue<T> 检索类型化值。ParseJSONValue 解析 JSON 字符串。JSON 非常适合 API、复杂配置和数据交换。对于 REST 客户端,使用 TRESTClient 或 Indy 组件。
uses
System.IniFiles, System.JSON, System.SysUtils;
// INI files (simple config)
var
Ini: TIniFile;
begin
Ini := TIniFile.Create('config.ini');
try
// write
Ini.WriteString('User', 'Name', 'Alice');
Ini.WriteInteger('User', 'Age', 30);
Ini.WriteBool('User', 'Active', True);
Ini.WriteDateTime('Session', 'LastLogin', Now);
// read (with defaults)
WriteLn(Ini.ReadString('User', 'Name', 'Unknown')); // Alice
WriteLn(Ini.ReadInteger('User', 'Age', 0)); // 30
WriteLn(Ini.ReadBool('User', 'Active', False)); // TRUE
// read a whole section
var SL: TStringList;
SL := TStringList.Create;
try
Ini.ReadSection('User', SL);
// SL = ['Name', 'Age', 'Active']
finally
SL.Free;
end;
finally
Ini.Free;
end;
end;
// JSON (System.JSON)
var
Obj: TJSONObject;
Arr: TJSONArray;
JSON: string;
i: Integer;
begin
// build JSON
Obj := TJSONObject.Create;
try
Obj.AddPair('name', 'Alice');
Obj.AddPair('age', TJSONNumber.Create(30));
Obj.AddPair('active', TJSONBool.Create(True));
var Hobbies := TJSONArray.Create;
Hobbies.Add('reading').Add('coding');
Obj.AddPair('hobbies', Hobbies);
JSON := Obj.ToJSON;
// {"name":"Alice","age":30,"active":true,"hobbies":["reading","coding"]}
finally
Obj.Free;
end;
// parse JSON
Obj := TJSONObject.ParseJSONValue(JSON) as TJSONObject;
try
WriteLn(Obj.GetValue<string>('name')); // Alice
WriteLn(Obj.GetValue<Integer>('age')); // 30
Arr := Obj.GetValue<TJSONArray>('hobbies');
for i := 0 to Arr.Count - 1 do
WriteLn(Arr.Items[i].Value); // reading, coding
finally
Obj.Free;
end;
end;VCL 组件深入
窗体与组件生命周期
VCL 窗体遵循严格的生命周期:OnCreate(分配资源,初始化)→ OnShow(窗体变为可见)→ OnActivate → OnResize → OnPaint → ... → OnCloseQuery(可取消关闭)→ OnClose → OnDestroy(释放资源)。始终将 OnCreate 与 OnDestroy 配对进行资源管理。OnCloseQuery 让你阻止关闭(设置 CanClose := False)。Sender 是触发事件的组件。组件拥有其子组件 —— 释放窗体会自动释放其所有组件。
type
TMainForm = class(TForm)
Edit1: TEdit;
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
FData: TStringList;
public
property Data: TStringList read FData;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
FData := TStringList.Create; // allocate in OnCreate
Caption := 'My App v1.0';
end;
procedure TMainForm.FormShow(Sender: TObject);
begin
Edit1.SetFocus; // focus when form is visible
end;
procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
CanClose := MessageDlg('Close?', mtConfirmation, [mbYes, mbNo], 0) = mrYes;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
FData.Free; // free in OnDestroy (pairs with OnCreate)
end;常用 VCL 控件
VCL 提供丰富的控件集:TEdit(单行文本)、TMemo(多行文本)、TLabel(不可编辑文本)、TButton、TCheckBox、TRadioButton、TComboBox(下拉)、TListBox(可选列表)。TStrings 是基础集合(Lines、Items 是 TStrings)。ItemIndex 选择项(0 开始,-1 = 无)。ComboBox 样式:csDropDown(可编辑)、csDropDownList(只读)。RadioGroup 用 ItemIndex 分组单选按钮。Sorted 自动排序项。PasswordChar 在 TEdit 中掩码输入。
// Edit, Memo, Label, Button, CheckBox, RadioButton
Edit1.Text := 'Hello';
Edit1.MaxLength := 50;
Edit1.PasswordChar := '*'; // mask input
Memo1.Lines.Add('Line 1'); // TStrings collection
Memo1.Lines.LoadFromFile('notes.txt');
Memo1.WordWrap := True;
Memo1.ScrollBars := ssVertical;
// ComboBox & ListBox
ComboBox1.Items.Add('Option A');
ComboBox1.ItemIndex := 0; // select first
ComboBox1.Style := csDropDownList; // read-only selection
ListBox1.Items.Add('Item 1');
ListBox1.Sorted := True;
ShowMessage(ListBox1.Items[ListBox1.ItemIndex]);
// CheckBox & RadioButton
if CheckBox1.Checked then
ShowMessage('Checked');
RadioGroup1.Items.Add('Red');
RadioGroup1.Items.Add('Green');
RadioGroup1.ItemIndex := 0;StringGrid 与 DBGrid
TStringGrid 在类似电子表格的网格中显示表格数据。Cells[Col, Row] 访问单个单元格(0 开始索引)。FixedRows/FixedCols 创建非滚动标题。ColWidths/RowHeights 自定义大小。Options 如 goEditing(可编辑单元格)、goColSizing(调整列大小)、goRowSelect 启用行为。OnDrawCell 允许使用 Canvas 自定义渲染。TDBGrid 通过 TDataSource 直接连接到 DataSet(TTable、TQuery)—— 它自动显示和编辑数据库记录。对数据库数据使用 TDBGrid,对内存数据使用 TStringGrid。
// TStringGrid - spreadsheet-like grid
StringGrid1.RowCount := 5;
StringGrid1.ColCount := 4;
StringGrid1.FixedRows := 1; // header row
StringGrid1.FixedCols := 0;
// set headers
StringGrid1.Cells[0, 0] := 'Name';
StringGrid1.Cells[1, 0] := 'Age';
StringGrid1.Cells[2, 0] := 'City';
// populate data
StringGrid1.Cells[0, 1] := 'Alice';
StringGrid1.Cells[1, 1] := '30';
StringGrid1.Cells[2, 1] := 'NYC';
// customize appearance
StringGrid1.ColWidths[0] := 120;
StringGrid1.RowHeights[0] := 30;
StringGrid1.Options := StringGrid1.Options + [goEditing, goColSizing];
// onDrawCell for custom rendering
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
begin
if ARow = 0 then
StringGrid1.Canvas.Font.Style := [fsBold];
StringGrid1.Canvas.TextRect(Rect, Rect.Left + 4, Rect.Top + 2,
StringGrid1.Cells[ACol, ARow]);
end;TTreeView 与 TListView
TTreeView 使用 TTreeNode 对象显示层次(树)数据。AddChild 创建嵌套节点。Expand(True) 递归展开。GetNext 深度优先遍历;GetNextSibling 逐级遍历。BeginUpdate/EndUpdate 批量更改以提高性能。TListView 以各种视图样式显示项:vsIcon、vsSmallIcon、vsList、vsReport(列)。Caption 是第一列;SubItems 持有后续列。两者都通过 OnGetNodeData/OnData 事件支持 owner-data(虚拟)模式用于大型数据集。
// TTreeView - hierarchical data
var
RootNode, ChildNode: TTreeNode;
begin
TreeView1.Items.BeginUpdate;
try
TreeView1.Items.Clear;
RootNode := TreeView1.Items.Add(nil, 'Root');
ChildNode := TreeView1.Items.AddChild(RootNode, 'Child 1');
TreeView1.Items.AddChild(RootNode, 'Child 2');
TreeView1.Items.AddChild(ChildNode, 'Grandchild');
RootNode.Expand(True); // expand all children
finally
TreeView1.Items.EndUpdate;
end;
// iterate
var Node := TreeView1.Items.GetFirstNode;
while Node <> nil do
begin
ShowMessage(Node.Text);
Node := Node.GetNext; // depth-first traversal
end;
end;
// TListView - report view with columns
ListView1.ViewStyle := vsReport;
ListView1.Columns.Add.Caption := 'Name';
ListView1.Columns.Add.Caption := 'Size';
var Item := ListView1.Items.Add;
Item.Caption := 'file.txt';
Item.SubItems.Add('1.2 KB');对话框与常用组件
Delphi 提供标准对话框组件:TOpenDialog/TSaveDialog(文件选择)、TOpenPictureDialog(图像预览)、TColorDialog、TFontDialog、TPrintDialog。Execute 在用户点击 OK 时返回 True。Filter 设置文件类型模式('Description|*.ext')。MessageDlg 显示带类型(mtInformation、mtWarning、mtError、mtConfirmation)和按钮集([mbYes, mbNo, mbOK, mbCancel])的模态消息框。InputBox/InputQuery 获取用户文本输入。TPageControl 用 TTabSheet 页面管理选项卡式界面。所有对话框都是放置在窗体上的非可视组件。
// File dialogs
if OpenDialog1.Execute then
ShowMessage('Selected: ' + OpenDialog1.FileName);
if SaveDialog1.Execute then
ShowMessage('Save to: ' + SaveDialog1.FileName);
OpenDialog1.Filter := 'Text files (*.txt)|*.txt|All files (*.*)|*.*';
OpenDialog1.DefaultExt := 'txt';
OpenDialog1.Options := [ofFileMustExist, ofAllowMultiSelect];
// Color & Font dialogs
if ColorDialog1.Execute then
Panel1.Color := ColorDialog1.Color;
if FontDialog1.Execute then
Label1.Font := FontDialog1.Font;
// Message dialogs
case MessageDlg('Delete file?', mtWarning, [mbYes, mbNo, mbCancel], 0) of
mrYes: DeleteFile('temp.txt');
mrNo: ShowMessage('Cancelled');
end;
// InputBox & InputQuery
var Name := InputBox('Login', 'Enter name:', 'guest');
var Value: string;
if InputQuery('Settings', 'Port:', Value) then
ShowMessage('Port: ' + Value);
// TPageControl (tabs)
var TabSheet := TTabSheet.Create(PageControl1);
TabSheet.PageControl := PageControl1;
TabSheet.Caption := 'Tab 1';事件驱动编程
事件与事件处理器
Delphi 中的事件是方法指针(procedure of object)。TNotifyEvent 是标准事件类型:procedure(Sender: TObject) of object。事件是属性 —— 在设计时(对象检查器)或运行时分配处理器。调用事件处理器前始终检查 Assigned()(如果未分配,它可能是 nil)。Sender 是触发事件的对象。自定义事件使用 'of object' 绑定到实例方法。var 参数(如 OnKeyPress 中的 var Key: Char)让处理器修改值 —— 设置 Key := #0 以抑制输入。
// Event type declaration
type
TNotifyEvent = procedure(Sender: TObject) of object;
TKeyPressEvent = procedure(Sender: TObject; var Key: Char) of object;
TCounter = class
private
FValue: Integer;
FOnChange: TNotifyEvent;
FOnThresholdReached: TThresholdEvent;
public
property Value: Integer read FValue write SetValue;
property OnChange: TNotifyEvent read FOnChange write FOnChange;
end;
procedure TCounter.SetValue(const NewValue: Integer);
begin
if FValue <> NewValue then
begin
FValue := NewValue;
if Assigned(FOnChange) then // check before calling
FOnChange(Self); // trigger event
end;
end;
// assigning handler at runtime
Counter1.OnChange := CounterChangeHandler;
procedure TForm1.CounterChangeHandler(Sender: TObject);
begin
Label1.Caption := 'Value: ' + IntToStr((Sender as TCounter).Value);
end;委托与方法指针
方法指针('of object')携带方法地址和对象实例 —— 它们是对 Self 的闭包。常规过程指针(不带 'of object')指向独立函数。方法指针启用回调、策略模式和事件系统。分配 Op := Calc.Add 存储引用;调用 Op(10, 20) 在 Calc 实例上调用 Calc.Add。匿名方法(reference to function)是带闭包语义的现代替代方案。方法指针是 Delphi 事件驱动 VCL/FMX 架构的支柱。
type
TMathFunc = function(X, Y: Integer): Integer of object;
TCalculator = class
function Add(X, Y: Integer): Integer;
function Subtract(X, Y: Integer): Integer;
function Multiply(X, Y: Integer): Integer;
end;
function TCalculator.Add(X, Y: Integer): Integer;
begin
Result := X + Y;
end;
// store and invoke method reference
var
Calc: TCalculator;
Op: TMathFunc;
begin
Calc := TCalculator.Create;
try
Op := Calc.Add; // method pointer
ShowMessage(IntToStr(Op(10, 20))); // 30
Op := Calc.Subtract;
ShowMessage(IntToStr(Op(10, 20))); // -10
finally
Calc.Free;
end;
end;
// regular procedure pointers (not of object)
type
TSimpleFunc = function(X: Integer): Integer;
function DoubleIt(X: Integer): Integer;
begin
Result := X * 2;
end;
var F: TSimpleFunc := DoubleIt;匿名方法与闭包
匿名方法(reference to function/procedure)是内联闭包,从封闭作用域捕获变量。'reference to' 类型是方法指针的现代替代方案 —— 它们按引用捕获变量,因此对捕获变量的更改会影响闭包。这启用了函数式模式:map/filter/reduce、回调和延迟执行。TFunc<T,TResult> 和 TProc<T> 是 System.SysUtils 中的泛型别名。匿名方法对并行编程(PPL)和现代 Delphi 惯用法至关重要。捕获的变量比其声明作用域寿命长。
type
TFuncInt = reference to function(X: Integer): Integer;
TProcStr = reference to procedure(const S: string);
procedure Apply(const Func: TFuncInt; Values: array of Integer);
var
I: Integer;
begin
for I := 0 to High(Values) do
WriteLn(Func(Values[I]));
end;
var
Multiplier: Integer;
Double: TFuncInt;
begin
Multiplier := 2;
// anonymous method captures Multiplier (closure)
Double := function(X: Integer): Integer
begin
Result := X * Multiplier;
end;
Apply(Double, [1, 2, 3, 4, 5]); // 2, 4, 6, 8, 10
Multiplier := 3;
Apply(Double, [1, 2, 3]); // 3, 6, 9 (captures by reference!)
// anonymous procedure
var Log: TProcStr := procedure(const S: string)
begin
WriteLn('[LOG] ' + S);
end;
Log('Hello');
end;消息处理与 Windows 消息
VCL 基于 Windows 消息构建。'message' 指令处理特定消息(WM_LBUTTONDOWN、WM_KEYDOWN 等)。消息记录(TWMMouse、TWMKeyDown)是 TMessage 上的类型化覆盖。始终调用 inherited 让默认处理发生(除非你想抑制消息)。WndProc 在分派前拦截所有消息 —— 谨慎用于横切关注点。PostMessage 是异步的(立即返回);SendMessage 是同步的(等待处理器)。WM_USER + N 定义自定义消息。这是 Windows 事件驱动模型的基础。
type
TMyForm = class(TForm)
private
procedure WMMouseDown(var Msg: TWMMouse); message WM_LBUTTONDOWN;
procedure WMKeyDown(var Msg: TWMKeyDown); message WM_KEYDOWN;
procedure WMNCHitTest(var Msg: TWMNCHitTest); message WM_NCHITTEST;
protected
procedure WndProc(var Message: TMessage); override;
end;
// handle specific Windows message
procedure TMyForm.WMMouseDown(var Msg: TWMMouse);
begin
inherited; // call default handler
ShowMessage(Format('Click at %d, %d', [Msg.XPos, Msg.YPos]));
end;
// intercept all messages
procedure TMyForm.WndProc(var Message: TMessage);
begin
if Message.Msg = WM_CLOSE then
begin
if MessageDlg('Close?', mtConfirmation, [mbYes, mbNo], 0) = mrNo then
Exit; // swallow the message
end;
inherited WndProc(Message); // pass to default
end;
// post/send custom messages
const
WM_MYMESSAGE = WM_USER + 100;
PostMessage(Handle, WM_MYMESSAGE, 0, 0); // async, returns immediately
SendMessage(Handle, WM_MYMESSAGE, 0, 0); // sync, waits for handler应用事件与空闲处理
TApplicationEvents 集中应用级事件:OnIdle(消息队列为空时触发)、OnException(全局异常处理器)、OnMinimize/OnRestore、OnHint(状态栏提示)、OnMessage(所有 Windows 消息)。带 Done := False 的 OnIdle 创建紧密循环;谨慎使用。TTimer 以间隔触发 OnTimer(Interval 以毫秒为单位)—— 它基于消息,因此在阻塞操作期间不会触发。Application.ProcessMessages 在长时间操作期间泵送消息队列(防止"无响应")但可能导致重入 bug。TThread.Queue/Synchronize 将 UI 更新从后台线程封送到主线程。
type
TForm1 = class(TForm)
ApplicationEvents1: TApplicationEvents;
procedure AppIdle(Sender: TObject; var Done: Boolean);
procedure AppException(Sender: TObject; E: Exception);
procedure AppMinimize(Sender: TObject);
end;
// OnIdle - runs when app has no pending messages
procedure TForm1.AppIdle(Sender: TObject; var Done: Boolean);
begin
Label1.Caption := 'Idle...';
Done := True; // False = keep calling Idle
end;
// global exception handler
procedure TForm1.AppException(Sender: TObject; E: Exception);
begin
LogError(E.Message);
ShowMessage('Error: ' + E.Message);
end;
// TTimer - periodic events
procedure TForm1.Timer1Timer(Sender: TObject);
begin
StatusBar1.Panels[0].Text := TimeToStr(Now);
end;
// TThread.Queue / TThread.Synchronize - marshal to main thread
TThread.Queue(nil,
procedure
begin
Label1.Caption := 'Updated from background';
end);
// ProcessMessages - pump message queue
while LongOperationRunning do
begin
DoChunk;
Application.ProcessMessages; // keep UI responsive
end;使用 FireDAC 进行数据库访问
连接与查询基础
FireDAC 是 Delphi 的现代通用数据访问框架,支持 SQLite、PostgreSQL、MySQL、SQL Server、Oracle、InterBase 等。TFDConnection 管理数据库连接(设置 DriverName 和 Params)。TFDQuery 执行带参数的 SQL(:param 语法)—— 始终使用参数防止 SQL 注入。ExecSQL 运行 INSERT/UPDATE/DELETE/DDL(无结果集);Open 运行 SELECT(返回游标)。FieldByName('col').AsString/AsInteger 读取值。用 Next/Prev/First/Last 导航;Eof 标记结束。FireDAC 替代了旧版 dbExpress 和 BDE 技术。
uses
FireDAC.Comp.Client, FireDAC.Comp.DataSet, FireDAC.Stan.Param;
var
FDConn: TFDConnection;
Query: TFDQuery;
begin
FDConn := TFDConnection.Create(nil);
Query := TFDQuery.Create(nil);
try
// connection string (SQLite example)
FDConn.DriverName := 'SQLite';
FDConn.Params.Database := 'app.db';
FDConn.Connected := True;
Query.Connection := FDConn;
// execute non-query (DDL/DML)
Query.ExecSQL('CREATE TABLE IF NOT EXISTS users ' +
'(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)');
// parameterized insert (prevents SQL injection)
Query.SQL.Text := 'INSERT INTO users (name, age) VALUES (:name, :age)';
Query.ParamByName('name').AsString := 'Alice';
Query.ParamByName('age').AsInteger := 30;
Query.ExecSQL;
// select with parameters
Query.SQL.Text := 'SELECT * FROM users WHERE age > :minAge';
Query.ParamByName('minAge').AsInteger := 18;
Query.Open;
while not Query.Eof do
begin
ShowMessage(Query.FieldByName('name').AsString);
Query.Next;
end;
finally
Query.Free;
FDConn.Free;
end;
end;TFDTable 与 Live Bindings
TFDTable 打开整个表(SELECT * FROM tablename)—— 对简单 CRUD 方便但对大表不如 TFDQuery 高效。数据集导航:First/Next/Prior/Last/MoveBy。Locate 按字段值搜索(找到返回 True)。编辑:Append/Insert(新行)或 Edit(现有),然后设置字段,然后 Post(提交)或 Cancel(还原)。Filter 限制可见行(客户端)。IndexFieldNames 排序记录。将 TFDTable/TFDQuery 连接到 TDataSource,然后到 TDBGrid/TDBEdit 实现自动数据感知 UI。Live Bindings(FMX)提供控件到数据字段的可视化绑定。
var
Table: TFDTable;
begin
Table := TFDTable.Create(nil);
try
Table.Connection := FDConn;
Table.TableName := 'users';
Table.Open; // SELECT * FROM users
// navigate
Table.First;
while not Table.Eof do
begin
ShowMessage(Table.FieldByName('name').AsString);
Table.Next;
end;
// locate a record
if Table.Locate('name', 'Alice', []) then
ShowMessage('Found Alice');
// edit/insert/post
Table.Append; // or Insert / Edit
Table.FieldByName('name').AsString := 'Bob';
Table.FieldByName('age').AsInteger := 25;
Table.Post; // commit to dataset
// filter
Table.Filter := 'age > 20';
Table.Filtered := True;
// index for sorting
Table.IndexFieldNames := 'name';
finally
Table.Free;
end;
end;
// connect to DBGrid via DataSource
DataSource1.DataSet := Table;
DBGrid1.DataSource := DataSource1;事务与批量操作
事务确保原子性 —— 所有操作成功或都不成功。StartTransaction/Commit/Rollback 包装相关操作。没有显式事务时,FireDAC 自动提交每条语句(对批量插入慢)。Array DML(Execute(count, startAt))在一次往返中发送参数化批次 —— 对批量插入显著更快(10-100 倍加速)。始终将事务包装在 try/except 中以便在失败时 Rollback。对于长事务,考虑隔离级别(xiReadCommitted、xiRepeatableRead)。连接池(TFDManager)提高多线程性能。
var
FDConn: TFDConnection;
Query: TFDQuery;
I: Integer;
begin
FDConn := TFDConnection.Create(nil);
Query := TFDQuery.Create(nil);
try
FDConn.DriverName := 'SQLite';
FDConn.Params.Database := 'app.db';
FDConn.Connected := True;
Query.Connection := FDConn;
// explicit transaction
FDConn.StartTransaction;
try
Query.SQL.Text := 'INSERT INTO users (name, age) VALUES (:n, :a)';
for I := 1 to 1000 do
begin
Query.ParamByName('n').AsString := 'User' + IntToStr(I);
Query.ParamByName('a').AsInteger := 20 + (I mod 50);
Query.ExecSQL;
end;
FDConn.Commit; // commit all
except
FDConn.Rollback; // undo all on error
raise;
end;
// batch execute (Array DML - very fast)
Query.SQL.Text := 'INSERT INTO logs (msg) VALUES (:m)';
Query.Params.ArraySize := 100;
for I := 0 to 99 do
Query.Params[0].AsStrings[I] := 'Log entry ' + IntToStr(I);
Query.Execute(100, 0); // execute 100 times at once
finally
Query.Free;
FDConn.Free;
end;
end;存储过程与元数据
TFDStoredProc 调用数据库存储过程。设置 StoredProcName 和参数(ParamType:ptInput、ptOutput、ptInputOutput、ptResult)。ExecProc 运行不返回游标的过程;Open 运行返回结果集的过程。存储过程在服务器端封装业务逻辑以提高性能和安全性。TFDMetaInfoQuery 查询数据库架构(表、列、索引、约束)—— 适用于构建动态工具、ORM 或架构浏览器。MetaInfoKind 选项:mkTables、mkColumns、mkIndexes、mkPrimaryKey、mkForeignKeys。FireDAC 还支持架构缓存以进行离线元数据访问。
// call stored procedure
var
SP: TFDStoredProc;
begin
SP := TFDStoredProc.Create(nil);
try
SP.Connection := FDConn;
SP.StoredProcName := 'get_user_by_id';
SP.Params.ParamByName('@user_id').AsInteger := 42;
// output parameter
SP.Params.ParamByName('@name').ParamType := ptOutput;
SP.ExecProc; // execute (no cursor)
ShowMessage(SP.ParamByName('@name').AsString);
// or Open if it returns a result set
SP.Open;
ShowMessage(SP.FieldByName('name').AsString);
finally
SP.Free;
end;
end;
// metadata - list tables
var
Meta: TFDMetaInfoQuery;
begin
Meta := TFDMetaInfoQuery.Create(nil);
try
Meta.Connection := FDConn;
Meta.MetaInfoKind := mkTables; // or mkColumns, mkIndexes
Meta.Open;
while not Meta.Eof do
begin
ShowMessage(Meta.FieldByName('TABLE_NAME').AsString);
Meta.Next;
end;
finally
Meta.Free;
end;
end;FireDAC 内存表与本地 SQL
TFDMemTable 是内存数据集 —— 非常适合缓存、临时数据和无需数据库的单元测试。用 FieldDefs 定义字段,然后 CreateDataSet。AppendRecord 添加行。支持索引、过滤器和所有数据集导航。本地 SQL(TFDLocalSQL)让你对任何 TDataSet 运行 SQL 查询(包括 TFDMemTable、TClientDataSet,甚至通过 ODBC 的 Excel)—— 启用内存表和数据库表之间的连接。这对 ETL、报表和构建离线工作的数据层非常强大。TFDMemTable 还可以加载/保存到二进制或 JSON 文件以持久化。
uses FireDAC.Comp.Client, FireDAC.Stan.Intf;
var
MemTable: TFDMemTable;
begin
MemTable := TFDMemTable.Create(nil);
try
// define schema in code
MemTable.FieldDefs.Add('id', ftInteger);
MemTable.FieldDefs.Add('name', ftString, 50);
MemTable.FieldDefs.Add('salary', ftCurrency);
MemTable.CreateDataSet; // create in-memory table
// populate
MemTable.AppendRecord([1, 'Alice', 75000]);
MemTable.AppendRecord([2, 'Bob', 68000]);
MemTable.AppendRecord([3, 'Carol', 82000]);
// index & filter
MemTable.IndexFieldNames := 'salary';
MemTable.Filter := 'salary > 70000';
MemTable.Filtered := True;
// Local SQL - query any TDataSet with SQL
var LocalSQL := TFDLocalSQL.Create(nil);
try
LocalSQL.Connection := FDConn; // or a dedicated connection
LocalSQL.DataSets.AddDataSet(MemTable, 'employees');
LocalSQL.Active := True;
var Q := TFDQuery.Create(nil);
try
Q.Connection := FDConn;
Q.Open('SELECT * FROM employees WHERE salary > :min ORDER BY name');
// query in-memory data with full SQL!
finally
Q.Free;
end;
finally
LocalSQL.Free;
end;
finally
MemTable.Free;
end;
end;泛型与匿名方法
泛型类与方法
泛型(在 Delphi 2009 中引入)启用类型安全的容器和算法。TStack<T> 适用于任何类型 T —— 编译器生成专用版本。这消除了运行时转换(无 TObject 转换)并在编译时捕获类型错误。泛型类型参数使用 <T> 语法。支持泛型方法、类、记录和接口。约束(class、constructor、interface)限制可使用的类型。RTL 在 System.Generics.Collections 中提供 TList<T>、TDictionary<TKey,TValue>、TQueue<T>、TStack<T>、TObjectList<T>。
type
TStack<T> = class
private
FItems: array of T;
FCount: Integer;
public
procedure Push(const Value: T);
function Pop: T;
function Peek: T;
function Count: Integer;
end;
procedure TStack<T>.Push(const Value: T);
begin
if FCount = Length(FItems) then
SetLength(FItems, FCount * 2 + 4);
FItems[FCount] := Value;
Inc(FCount);
end;
function TStack<T>.Pop: T;
begin
if FCount = 0 then
raise Exception.Create('Stack empty');
Dec(FCount);
Result := FItems[FCount];
end;
// usage - type-safe, no casts
var
IntStack: TStack<Integer>;
StrStack: TStack<string>;
begin
IntStack := TStack<Integer>.Create;
IntStack.Push(42);
IntStack.Push(99);
ShowMessage(IntToStr(IntStack.Pop)); // 99
StrStack := TStack<string>.Create;
StrStack.Push('Hello');
ShowMessage(StrStack.Pop); // Hello
end;泛型约束
泛型约束限制类型参数:'class'(必须是类类型)、'constructor'(必须有无参 Create 构造函数 —— 启用 T.Create)、'record'(必须是值类型)、接口名(必须实现该接口)。多个约束逗号分隔。约束启用在 T 上调用方法(例如,带 constructor 约束的 T.Create)。没有约束,你只能分配/比较 T(无方法调用)。类型推断有时让你省略显式类型参数。约束对构建类型安全框架和 ORM 至关重要。
type
// T must be a class
TRepository<T: class> = class
function Find(Id: Integer): T;
end;
// T must be a class with a parameterless constructor
TFactory<T: class, constructor> = class
function CreateInstance: T;
end;
// T must implement IComparable
TSorter<T: IComparable> = class
procedure Sort(var Arr: array of T);
end;
// multiple constraints
TManager<T: class, constructor, IComparable> = class
end;
function TFactory<T>.CreateInstance: T;
begin
Result := T.Create; // allowed because of 'constructor' constraint
end;
// type inference
type
TPair<TKey, TValue> = class
Key: TKey;
Value: TValue;
constructor Create(const K: TKey; const V: TValue);
end;
var
P: TPair<string, Integer>;
begin
P := TPair<string, Integer>.Create('age', 30);
end;TList<T> 与 TDictionary<TKey,TValue>
System.Generics.Collections 提供类型安全的容器:TList<T>(动态数组)、TDictionary<TKey,TValue>(哈希映射)、TQueue<T>(FIFO)、TStack<T>(LIFO)、TObjectList<T>(拥有其对象 —— 自动释放它们)。Sort 使用默认比较;TComparer<T>.Construct 用匿名方法创建自定义比较器。FindIndex/基于谓词的搜索使用匿名函数谓词。TryGetValue 找到时返回 True 并输出值(避免异常)。AddOrSetValue 更新或插入。带 OwnsObjects := True 的 TObjectList<T> 在列表释放时自动释放包含的对象 —— 防止内存泄漏。
uses System.Generics.Collections, System.Generics.Defaults;
var
List: TList<Integer>;
Dict: TDictionary<string, Integer>;
ObjList: TObjectList<TPerson>;
begin
// TList<T>
List := TList<Integer>.Create;
try
List.AddRange([3, 1, 4, 1, 5, 9, 2, 6]);
List.Sort; // 1, 1, 2, 3, 4, 5, 6, 9
List.BinarySearch(5, var Idx); // fast lookup in sorted list
// custom comparer
List.Sort(TComparer<Integer>.Construct(
function(const L, R: Integer): Integer
begin
Result := R - L; // descending
end));
// find with predicate
var Found := List.FindIndex(
function(const X: Integer): Boolean
begin
Result := X > 4;
end);
finally
List.Free;
end;
// TDictionary
Dict := TDictionary<string, Integer>.Create;
try
Dict.Add('apple', 5);
Dict.Add('banana', 3);
Dict.AddOrSetValue('apple', 10); // update or insert
if Dict.TryGetValue('banana', var Count) then
ShowMessage(Count.ToString);
// iterate
for var Pair in Dict do
ShowMessage(Pair.Key + ': ' + Pair.Value.ToString);
finally
Dict.Free;
end;
end;作为回调的匿名方法
匿名方法在 Delphi 中启用函数式编程。'reference to function' 类型是闭包 —— 它们从封闭作用域捕获变量。Map 和 Filter 等高阶函数接受函数作为参数,实现简洁的数据转换。TFunc<T,TResult> 和 TProc<T> 是内置泛型委托类型。闭包按引用捕获变量,因此它们反映后续更改。此模式替代了冗长的回调接口,对 PPL(并行编程库)、事件处理器和 LINQ 风格操作至关重要。匿名方法是引用计数并自动管理的。
uses System.SysUtils;
type
TFunc<T, TResult> = reference to function(Arg: T): TResult;
TProc<T> = reference to procedure(Arg: T);
// higher-order functions
function Map<T, TResult>(const Source: array of T;
const Mapper: TFunc<T, TResult>): TArray<TResult>;
var
I: Integer;
begin
SetLength(Result, Length(Source));
for I := 0 to High(Source) do
Result[I] := Mapper(Source[I]);
end;
function Filter<T>(const Source: array of T;
const Predicate: TFunc<T, Boolean>): TArray<T>;
var
I, Count: Integer;
begin
Count := 0;
SetLength(Result, Length(Source));
for I := 0 to High(Source) do
if Predicate(Source[I]) then
begin
Result[Count] := Source[I];
Inc(Count);
end;
SetLength(Result, Count);
end;
// usage with closures
var
Numbers: array of Integer;
Doubled, Evens: TArray<Integer>;
Threshold: Integer;
begin
Numbers := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
Threshold := 5;
Doubled := Map<Integer, Integer>(Numbers,
function(X: Integer): Integer
begin
Result := X * 2;
end);
// closure captures Threshold
Evens := Filter<Integer>(Numbers,
function(X: Integer): Boolean
begin
Result := (X > Threshold) and (X mod 2 = 0);
end);
end;泛型接口与 TComparer
泛型接口启用类型安全契约:IRepository<T> 适用于任何实体类型。与引用计数(TInterfacedObject)结合,这提供自动内存管理 —— 接口引用计数,最后一个引用释放时释放。TComparer<T>.Construct 从匿名比较函数创建 IComparer<T> —— 由 Sort、BinarySearch 和 SortedDictionary 使用。泛型接口是 Delphi 中依赖注入的基础(注册 IRepository<TUser>,注入到服务中)。Spring4D 框架用完整的 DI 容器扩展了这一点。泛型约束(class、constructor)确保 T 可以被实例化。
type
IComparable<T> = interface
function CompareTo(const Other: T): Integer;
end;
IRepository<T> = interface
function GetById(Id: Integer): T;
function GetAll: TArray<T>;
procedure Save(const Entity: T);
procedure Delete(Id: Integer);
end;
TMemoryRepository<T: class, constructor> = class(TInterfacedObject, IRepository<T>)
private
FItems: TList<T>;
public
constructor Create;
destructor Destroy; override;
function GetById(Id: Integer): T;
function GetAll: TArray<T>;
procedure Save(const Entity: T);
procedure Delete(Id: Integer);
end;
// custom comparer for sorting objects
type
TPerson = class
Name: string;
Age: Integer;
end;
var
People: TObjectList<TPerson>;
begin
People := TObjectList<TPerson>.Create;
People.Sort(TComparer<TPerson>.Construct(
function(const L, R: TPerson): Integer
begin
Result := CompareText(L.Name, R.Name); // sort by name
end));
end;RTTI 与反射
扩展 RTTI 基础
扩展 RTTI(运行时类型信息),在 Delphi 2010 中引入,提供完整反射:在运行时检查类型、属性、方法和字段。TRTTIContext 是入口点。GetType 返回类的 TRttiType。GetProperties 枚举 published 属性。GetValue/SetValue 使用 TValue(类似变体的类型)动态读/写属性值。默认只有 'published' 成员有 RTTI(使用 {$RTTI EXPLICIT ...} 指令获取更多)。RTTI 驱动序列化(JSON/XML)、ORM、依赖注入和可视化设计器。它有小的性能开销但启用强大的元编程。
uses System.RTTI, System.TypInfo;
type
TPerson = class
private
FName: string;
FAge: Integer;
published
property Name: string read FName write FName;
property Age: Integer read FAge write FAge;
end;
var
Ctx: TRTTIContext;
RType: TRttiType;
Prop: TRttiProperty;
Person: TPerson;
begin
Person := TPerson.Create;
try
Person.Name := 'Alice';
Person.Age := 30;
Ctx := TRTTIContext.Create;
try
RType := Ctx.GetType(TPerson);
// enumerate properties
for Prop in RType.GetProperties do
begin
WriteLn(Prop.Name, ': ', Prop.PropertyType.Name);
// read value
if Prop.IsReadable then
WriteLn(' Value: ', Prop.GetValue(Person).ToString);
// write value
if Prop.IsWritable then
Prop.SetValue(Person, TValue.From<string>('Bob'));
end;
// get specific property
Prop := RType.GetProperty('Name');
ShowMessage(Prop.GetValue(Person).AsString);
finally
Ctx.Free;
end;
finally
Person.Free;
end;
end;方法调用与特性
RTTI 可以通过 TRttiMethod.Invoke 动态调用方法 —— 将参数作为 TValue 数组传递。特性(TCustomAttribute 子类)使用 [Attribute] 语法将元数据附加到类型、属性和方法。GetAttributes 在运行时检索它们。这启用验证框架([Required]、[MaxLength])、ORM 映射([Table]、[Column])和序列化控制([JsonProperty])。特性是强大的元编程功能 —— 编译器将它们存储在 RTTI 中,框架读取它们以驱动行为。通过 RTTI 的方法调用比直接调用慢,但对脚本、DI 和动态分派至关重要。
uses System.RTTI;
type
TValidatorAttribute = class(TCustomAttribute)
private
FMaxLen: Integer;
public
constructor Create(MaxLen: Integer);
property MaxLen: Integer read FMaxLen;
end;
TUser = class
private
FName: string;
public
[Validator(50)]
property Name: string read FName write FName;
function Greet(const Greeting: string): string;
end;
constructor TValidatorAttribute.Create(MaxLen: Integer);
begin
FMaxLen := MaxLen;
end;
var
Ctx: TRTTIContext;
RType: TRttiType;
Prop: TRttiProperty;
Attr: TCustomAttribute;
Method: TRttiMethod;
User: TUser;
Result: TValue;
begin
User := TUser.Create;
User.Name := 'Alice';
Ctx := TRTTIContext.Create;
try
RType := Ctx.GetType(TUser);
// read attributes
Prop := RType.GetProperty('Name');
for Attr in Prop.GetAttributes do
begin
if Attr is TValidatorAttribute then
WriteLn('Max length: ', TValidatorAttribute(Attr).MaxLen);
end;
// invoke method by name
Method := RType.GetMethod('Greet');
Result := Method.Invoke(User, ['Hello']);
ShowMessage(Result.AsString); // Hello, Alice
finally
Ctx.Free;
User.Free;
end;
end;类型发现与枚举
TRTTIContext.GetTypes 枚举编译程序中所有带 RTTI 的类型 —— 适用于插件发现、ORM 模型扫描和构建类型浏览器。FindType 按限定名定位类型('UnitName.TypeName')。TRttiType 提供 GetFields(所有字段)、GetMethods(所有方法)、GetProperties(published 属性)。TypeKind 区分类、记录、接口、枚举等。AsInstance.MetaclassType 给出用于实例化的类引用。这启用自动发现并连接组件的框架。Spring4D 和 DORM 框架使用此进行自动 ORM 映射。RTTI 枚举很慢 —— 缓存结果以供重复使用。
uses System.RTTI;
var
Ctx: TRTTIContext;
Types: TArray<TRttiType>;
T: TRttiType;
Field: TRttiField;
Method: TRttiMethod;
begin
Ctx := TRTTIContext.Create;
try
// enumerate ALL types in the program
Types := Ctx.GetTypes;
// find types by name
T := Ctx.FindType('Unit1.TPerson');
if T <> nil then
ShowMessage('Found: ' + T.QualifiedName);
// filter: all classes in a unit
for T in Types do
begin
if (T.TypeKind = tkClass) and T.QualifiedName.StartsWith('MyApp.') then
begin
WriteLn('Class: ', T.Name);
// enumerate fields
for Field in T.GetFields do
WriteLn(' Field: ', Field.Name, ': ', Field.FieldType.Name);
// enumerate methods
for Method in T.GetMethods do
WriteLn(' Method: ', Method.Name,
' - ', Method.MethodType.ToString);
end;
end;
// create instance via RTTI
var Instance := T.AsInstance.MetaclassType.Create;
try
// use instance...
finally
Instance.Free;
end;
finally
Ctx.Free;
end;
end;TValue 与动态类型
TValue 是 Delphi 的动态值类型 —— 一个标记联合,持有任何类型及其类型信息。From<T> 包装值;AsType<T>/AsInteger/AsString 解包它。IsType<T> 检查类型。TryAsType 尝试安全转换。TValue 对 RTTI(属性值、方法参数)至关重要,并在静态类型语言中启用动态类型。它类似 C# 带类型信息的 'object',或 Python 的动态特性。TValue 处理原语、字符串、对象、数组和记录。在构建序列化器、脚本引擎或通用数据层时使用它。它有相比直接类型的开销但提供最大灵活性。
uses System.RTTI;
var
V: TValue;
I: Integer;
S: string;
D: Double;
Obj: TObject;
begin
// wrap values
V := TValue.From<Integer>(42);
ShowMessage(V.ToString); // '42'
I := V.AsInteger; // unwrap
V := TValue.From<string>('Hello');
S := V.AsString;
// type checking
if V.IsType<string> then
ShowMessage('It is a string');
// conversion
V := TValue.From<Integer>(100);
D := V.AsExtended; // 100.0
// boxing objects
var Person := TPerson.Create;
try
V := TValue.From<TPerson>(Person);
if V.IsObject then
ShowMessage(V.AsObject.ClassName); // 'TPerson'
finally
Person.Free;
end;
// array of TValue for method invocation
var Args: array of TValue;
SetLength(Args, 2);
Args[0] := TValue.From<Integer>(10);
Args[1] := TValue.From<Integer>(20);
// try conversion
V := TValue.From<string>('123');
if V.TryAsType<Integer>(I) then
ShowMessage(IntToStr(I)); // 123
end;使用 RTTI 序列化
RTTI 启用自动序列化 —— 将对象转换为/从 JSON、XML 或任何格式,无需手动映射代码。ObjectToJSON 迭代 published 属性,通过 RTTI 读取值,并构建 TJSONObject。JSONToObject 反转过程。此模式驱动 REST 客户端、配置系统和 ORM 层。REST.Json 单元提供 TJson.ObjectToJsonString 和 TJson.JsonToObject 开箱即用。对于生产使用,添加特性([JsonProperty('name')])控制字段名,并处理嵌套对象、数组和自定义类型。基于 RTTI 的序列化比手写映射器慢但更易维护。
uses System.RTTI, System.JSON;
function ObjectToJSON(Obj: TObject): TJSONObject;
var
Ctx: TRTTIContext;
RType: TRttiType;
Prop: TRttiProperty;
Val: TValue;
begin
Result := TJSONObject.Create;
Ctx := TRTTIContext.Create;
try
RType := Ctx.GetType(Obj.ClassType);
for Prop in RType.GetProperties do
begin
if not Prop.IsReadable then Continue;
Val := Prop.GetValue(Obj);
case Val.Kind of
tkString, tkUString:
Result.AddPair(Prop.Name, Val.AsString);
tkInteger:
Result.AddPair(Prop.Name, TJSONNumber.Create(Val.AsInteger));
tkFloat:
Result.AddPair(Prop.Name, TJSONNumber.Create(Val.AsExtended));
tkEnumeration:
Result.AddPair(Prop.Name, TJSONBool.Create(Val.AsBoolean));
end;
end;
finally
Ctx.Free;
end;
end;
procedure JSONToObject(Obj: TObject; const JSON: TJSONObject);
var
Ctx: TRTTIContext;
RType: TRttiType;
Prop: TRttiProperty;
Pair: TJSONPair;
begin
Ctx := TRTTIContext.Create;
try
RType := Ctx.GetType(Obj.ClassType);
for Prop in RType.GetProperties do
begin
if not Prop.IsWritable then Continue;
Pair := JSON.FindPair(Prop.Name);
if Pair <> nil then
begin
case Prop.PropertyType.TypeKind of
tkString, tkUString:
Prop.SetValue(Obj, TValue.From<string>(Pair.JsonValue.Value));
tkInteger:
Prop.SetValue(Obj, TValue.From<Integer>((Pair.JsonValue as TJSONNumber).AsInt));
end;
end;
end;
finally
Ctx.Free;
end;
end;接口与 COM
接口基础与引用计数
接口定义契约(方法签名)而无实现。TInterfacedObject 提供引用计数 —— 当最后一个接口引用释放时,对象自动释放(无需调用 Free)。这是 Delphi 对接口对象的自动内存管理。GUID(['{...}'])启用 COM 互操作和 InterfaceAs/Supports 检查。一个类可以实现多个接口(TShape 同时实现 IMovable 和 IDrawable)。接口属性是允许的(必须有 read/write 方法)。始终使用接口类型(IMovable)而非类类型(TShape)以使引用计数工作。混合对象和接口引用可能导致过早释放。
type
IMovable = interface
['{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}'] // GUID (optional)
procedure MoveTo(X, Y: Integer);
function GetPosition: TPoint;
property Position: TPoint read GetPosition;
end;
IDrawable = interface
procedure Draw(Canvas: TCanvas);
end;
TShape = class(TInterfacedObject, IMovable, IDrawable)
private
FX, FY: Integer;
public
procedure MoveTo(X, Y: Integer);
function GetPosition: TPoint;
procedure Draw(Canvas: TCanvas);
end;
procedure TShape.MoveTo(X, Y: Integer);
begin
FX := X;
FY := Y;
end;
// usage - reference counted automatically
var
Shape: IMovable;
begin
Shape := TShape.Create; // ref count = 1
Shape.MoveTo(100, 200);
ShowMessage(Format('%d, %d', [Shape.Position.X, Shape.Position.Y]));
// when Shape goes out of scope, ref count drops to 0, object freed
end;使用接口的依赖注入
接口启用依赖注入 —— 通过构造函数传递依赖项(ILogger、IUserDataAccess)而非硬编码它们。这解耦 TUserService 与具体实现:无需更改 TUserService 即可将 TConsoleLogger 替换为 TFileLogger。TUserService 本身不引用计数(继承自 TObject,而非 TInterfacedObject),因此需要手动 Free。对于完整 DI,使用按接口类型解析依赖项的容器(Spring4D、DSharp):Container.RegisterType<ILogger, TConsoleLogger>; Container.Build; Service := Container.Resolve<TUserService>。DI 提高可测试性(注入模拟)、可维护性和模块性。始终依赖抽象(接口),而非具体实现。
type
ILogger = interface
procedure Log(const Msg: string);
end;
IUserDataAccess = interface
function GetUser(Id: Integer): string;
end;
TConsoleLogger = class(TInterfacedObject, ILogger)
procedure Log(const Msg: string);
end;
TDatabaseAccess = class(TInterfacedObject, IUserDataAccess)
function GetUser(Id: Integer): string;
end;
TUserService = class
private
FLogger: ILogger;
FDataAccess: IUserDataAccess;
public
constructor Create(ALogger: ILogger; ADataAccess: IUserDataAccess);
function GetUserName(Id: Integer): string;
end;
constructor TUserService.Create(ALogger: ILogger; ADataAccess: IUserDataAccess);
begin
FLogger := ALogger;
FDataAccess := ADataAccess;
end;
function TUserService.GetUserName(Id: Integer): string;
begin
FLogger.Log('Fetching user ' + IntToStr(Id));
Result := FDataAccess.GetUser(Id);
end;
// wire up dependencies (manual DI)
var
Logger: ILogger;
DataAccess: IUserDataAccess;
Service: TUserService;
begin
Logger := TConsoleLogger.Create;
DataAccess := TDatabaseAccess.Create;
Service := TUserService.Create(Logger, DataAccess);
try
ShowMessage(Service.GetUserName(42));
finally
Service.Free; // TUserService is not ref-counted (not TInterfacedObject)
end;
end;COM 互操作
COM(组件对象模型)让 Delphi 与 Windows 应用和库交互。CreateOleObject 通过后期绑定创建 COM 对象(Variant 类型 —— 无编译时检查,但简单)。导入类型库生成带类型化接口的早期绑定单元(IntelliSense、类型检查、更好性能)。IUnknown 是基础 COM 接口,带 AddRef/Release/QueryInterface 用于引用计数。stdcall 是 COM 调用约定。CoCreateInstance 是低级 API。常见 COM 用途:Office 自动化(Excel、Word)、ADO(数据库)、shell 集成、WMI 查询。在线程中 COM 操作前始终调用 CoInitialize。COM 对象是单元线程的 —— 谨慎在线程间封送。
uses
Winapi.ActiveX, System.Win.ComObj;
// create COM object (e.g., Excel)
var
Excel: Variant;
Workbook: Variant;
Sheet: Variant;
begin
Excel := CreateOleObject('Excel.Application');
try
Excel.Visible := True;
Workbook := Excel.Workbooks.Add;
Sheet := Workbook.Worksheets[1];
// write data
Sheet.Cells[1, 1].Value := 'Name';
Sheet.Cells[1, 2].Value := 'Score';
Sheet.Cells[2, 1].Value := 'Alice';
Sheet.Cells[2, 2].Value := 95;
// formula
Sheet.Cells[3, 2].Value := '=AVERAGE(B2:B2)';
Workbook.SaveAs('C:\report.xlsx');
finally
Excel.Quit;
end;
end;
// import type library for early binding
// Component → Import Component → Import Type Library
// generates a unit with typed interfaces (early binding, IntelliSense)
// IUnknown - base COM interface
type
IMyComObject = interface(IUnknown)
['{...}']
function DoSomething: HResult; stdcall;
end;
// CoCreateInstance for low-level COM
var
Obj: IUnknown;
MyObj: IMyComObject;
begin
CoCreateInstance(CLASS_MyComObject, nil, CLSCTX_INPROC_SERVER,
IMyComObject, MyObj);
MyObj.DoSomething;
end;Implements 与聚合
'implements' 指令将接口委托给属性 —— 组合优于继承。TDataService 通过委托给 FCache(TMemoryCache)公开 ICache。这比继承更清晰,让你混合匹配行为。Supports() 检查对象是否实现接口(内部使用 QueryInterface)。As 运算符执行检查的接口转换。接口委托启用装饰器模式(用日志记录包装缓存)、策略模式(交换缓存实现)和清晰的关注点分离。COM 的 QueryInterface 是底层机制 —— 每个接口对象都可以查询其支持的任何接口。
type
ICache = interface
function Get(const Key: string): string;
procedure Put(const Key, Value: string);
end;
TMemoryCache = class(TInterfacedObject, ICache)
private
FDict: TDictionary<string, string>;
public
constructor Create;
destructor Destroy; override;
function Get(const Key: string): string;
procedure Put(const Key, Value: string);
end;
TDataService = class(TInterfacedObject, ICache)
private
FCache: ICache;
public
constructor Create(ACache: ICache);
// 'implements' delegates ICache to FCache
property Cache: ICache read FCache implements ICache;
end;
// usage - TDataService exposes ICache via delegation
var
Service: ICache;
begin
Service := TDataService.Create(TMemoryCache.Create);
Service.Put('key1', 'value1'); // delegates to TMemoryCache
ShowMessage(Service.Get('key1'));
end;
// QueryInterface / Supports
var
Obj: TInterfacedObject;
Intf: ICache;
begin
Obj := TMemoryCache.Create;
if Supports(Obj, ICache, Intf) then
Intf.Put('a', 'b');
end;弱引用与不安全引用
引用计数可能因循环引用(父↔子)导致内存泄漏。[Weak] 打破循环 —— 它跟踪引用但不增加引用计数,并在目标释放时自动置 nil。[Unsafe] 是原始指针(无跟踪,无引用计数)—— 最快但危险(悬空指针)。对父/反向引用、观察者模式和事件订阅使用 [Weak]。默认(强)引用增加引用计数并保持对象存活。Delphi 的 ARC(已弃用,改用 [Weak])曾经在移动设备上自动处理此问题。在桌面上,接口使用手动引用计数 —— [Weak] 对无循环设计至关重要。始终正确配对强引用和弱引用。
type
TParent = class;
TChild = class;
TParent = class(TInterfacedObject)
private
FChild: TChild;
procedure ChildCallback(const Msg: string);
public
destructor Destroy; override;
property Child: TChild read FChild;
end;
TChild = class(TInterfacedObject)
private
// [Weak] avoids circular reference counting (parent-child cycle)
[Weak] FParent: TParent;
FCallback: TProc<string>;
public
constructor Create(AParent: TParent);
property Parent: TParent read FParent;
end;
// [Unsafe] - raw pointer, no ref counting at all
// [Weak] - tracked but doesn't increment ref count
// (default) - strong reference, increments ref count
destructor TParent.Destroy;
begin
FChild := nil; // releases strong ref
inherited;
end;
// without [Weak], this creates a memory leak:
// Parent holds Child (ref=1), Child holds Parent (ref=1)
// neither ref count ever reaches 0 → leak
var
Parent: TParent;
begin
Parent := TParent.Create;
// when Parent goes out of scope, both are freed correctly
end;多线程与 PPL
TThread 基础
TThread 是 Delphi 多线程的基础。用后台工作重写 Execute。定期检查 Terminated 进行优雅取消。TThread.Synchronize 在主线程上执行代码(阻塞 —— 等待完成);TThread.Queue 是异步的(发布并立即返回)。切勿从后台线程访问 UI 控件 —— 始终使用 Synchronize 或 Queue。FreeOnTerminate := True 在 Execute 完成时自动释放线程。CreateAnonymousThread 从匿名方法创建一次性线程 —— 对简单任务方便。对于生产代码,优先使用 PPL(TTask)而非原始 TThread,以获得更好的组合和错误处理。
type
TWorkerThread = class(TThread)
private
FResult: Integer;
protected
procedure Execute; override;
public
property Result: Integer read FResult;
end;
procedure TWorkerThread.Execute;
var
I: Integer;
begin
FResult := 0;
for I := 1 to 100 do
begin
if Terminated then Break; // check for cancellation
FResult := FResult + I;
Sleep(10); // simulate work
end;
// update UI from background thread
TThread.Synchronize(nil,
procedure
begin
Form1.Label1.Caption := 'Done: ' + IntToStr(FResult);
end);
end;
// create and run
var
Worker: TWorkerThread;
begin
Worker := TWorkerThread.Create(True); // suspended
Worker.FreeOnTerminate := True; // auto-free when done
Worker.Start; // begin execution
end;
// TThread.CreateAnonymousThread - quick one-off
TThread.CreateAnonymousThread(
procedure
var I: Integer;
begin
for I := 1 to 10 do
TThread.Queue(nil,
procedure
begin
Form1.Label1.Caption := IntToStr(I);
end);
end).Start;并行编程库(PPL)
System.Threading 中的并行编程库(PPL)提供高级并发:TTask(即发即弃异步)、TTask.Future<T>(带返回值的异步)和并行循环。任务自动使用线程池 —— 无需管理线程。WaitForAll/WaitForAny 组合多个任务。Future.Value 阻塞直到结果就绪(类似 promise)。PPL 是原始 TThread 的现代替代方案 —— 更清晰、可组合,与 async/await 模式集成。任务捕获异常并在你访问 .Value 时重新引发它们,启用正确的错误传播。使用 TEvent/TCountdownEvent 进行任务间的细粒度同步。
uses System.Threading, System.SyncObjs;
// TTask - async operations
var
Task: ITask;
begin
Task := TTask.Create(
procedure
begin
Sleep(2000); // simulate work
TThread.Queue(nil,
procedure
begin
ShowMessage('Task done');
end);
end);
Task.Start;
end;
// TTask.WaitForAll - wait for multiple tasks
var
Tasks: array of ITask;
begin
SetLength(Tasks, 3);
Tasks[0] := TTask.Create(procedure begin DownloadFile('a.txt'); end);
Tasks[1] := TTask.Create(procedure begin DownloadFile('b.txt'); end);
Tasks[2] := TTask.Create(procedure begin DownloadFile('c.txt'); end);
for var T in Tasks do T.Start;
// wait for all (with timeout)
if TTask.WaitForAll(Tasks, 30000) then
ShowMessage('All downloads complete')
else
ShowMessage('Timeout');
end;
// TTask.Future<T> - async with return value
var
Future: IFuture<string>;
begin
Future := TTask.Future<string>(
function: string
begin
Result := FetchDataFromServer; // long operation
end);
// do other work...
ShowMessage('Result: ' + Future.Value); // blocks until ready
end;并行 For 与循环
TParallel.For 跨线程池并行化循环 —— 迭代在多个核心上并发运行。使用 &For(转义关键字),因为 'for' 是保留字。对于带独立迭代的 CPU 密集型循环,这可以在多核机器上提供接近线性的加速。关键:共享状态(如 Sum)必须用锁(TCriticalSection)保护或使用 TInterlocked.Increment 进行原子操作。State.Break 停止循环(类似 break)。State.ShouldExit 检查是否调用了 Break。避免并行化少量迭代或重 I/O 的循环(线程池耗尽)。Stride 控制迭代步进。嵌套并行循环很少有帮助 —— 外循环已经饱和核心。
uses System.Threading, System.SyncObjs;
// TParallel.For - parallelized loop
var
Sum: Integer;
Lock: TCriticalSection;
I: Integer;
begin
Sum := 0;
Lock := TCriticalSection.Create;
try
TParallel.&For(1, 1000000,
procedure(Index: Integer)
begin
// thread-safe accumulation
Lock.Enter;
try
Sum := Sum + Index;
finally
Lock.Leave;
end;
end);
ShowMessage('Sum: ' + IntToStr(Sum));
finally
Lock.Free;
end;
end;
// with stride and state
TParallel.&For(1, 100,
procedure(Index: Integer; var State: TParallelLoopState)
begin
if Index = 50 then
State.Break; // stop after current iterations
// process Index...
end);
// TParallel.For with step (stride)
TParallel.&For(0, 99, 2, // 0, 2, 4, 6, ...
procedure(Index: Integer)
begin
ProcessEven(Index);
end);
// nested parallel loops (use sparingly)
TParallel.&For(0, 9,
procedure(I: Integer)
begin
TParallel.&For(0, 9,
procedure(J: Integer)
begin
Matrix[I, J] := Compute(I, J);
end);
end);同步原语
System.SyncObjs 提供同步原语:TCriticalSection(互斥 —— 一次只有一个线程进入)、TEvent(线程间信号 —— SetEvent 唤醒,WaitFor 阻塞)、TMonitor(锁定任何对象 —— 类似 Java/C# 带有 Wait/Pulse 的监视器)、TInterlocked(原子 Increment/Decrement/Exchange/CompareExchange —— 无锁)。TCriticalSection 最常见 —— 始终将 Enter/Leave 与 try/finally 配对。TEvent.WaitFor 返回 wrSignaled、wrTimeout 或 wrAbandoned。TMonitor.Wait 临时释放锁并阻塞;Pulse/PulseAll 唤醒等待者。TInterlocked 对简单计数器最快 —— 无锁开销。选择正确的原语:CriticalSection 用于独占访问,Event 用于信号,Interlocked 用于原子计数器。
uses System.SyncObjs;
// TCriticalSection - mutual exclusion
var
CS: TCriticalSection;
begin
CS := TCriticalSection.Create;
try
CS.Enter;
try
// exclusive access to shared data
finally
CS.Leave;
end;
finally
CS.Free;
end;
end;
// TEvent - signaling between threads
var
Event: TEvent;
begin
Event := TEvent.Create(nil, True, False, ''); // manual reset
try
// thread 1: wait
if Event.WaitFor(5000) = wrSignaled then
ShowMessage('Signaled');
// thread 2: signal
Event.SetEvent; // wake waiting threads
Event.ResetEvent; // clear signal
finally
Event.Free;
end;
end;
// TMonitor - lock any object
var
List: TList<Integer>;
begin
TMonitor.Enter(List);
try
List.Add(42);
finally
TMonitor.Exit(List);
end;
// TMonitor.Wait / Pulse (like Java wait/notify)
TMonitor.Enter(List);
try
while List.Count = 0 do
TMonitor.Wait(List, 1000); // release lock, wait
TMonitor.PulseAll(List); // wake waiting threads
finally
TMonitor.Exit(List);
end;
end;
// TInterlocked - atomic operations
TInterlocked.Increment(Counter);
TInterlocked.Exchange(Value, 42);线程池与 Async/Await 模式
TThreadPool 管理工作线程池 —— 重用线程避免创建开销。根据工作负载设置最小/最大线程(CPU 密集型:~核心数,I/O 密集型:更多)。TTask.Run 是 Create+Start 的简写。ContinueWith 链式任务 —— 在前驱完成后运行,启用管道。Task.Status(Created、WaitingToRun、Running、Completed、Canceled、Faulted)跟踪生命周期。ICancellation 启用协作式取消 —— 在长任务中定期检查 IsCancelled。对于真正的 async/await,Delphi 没有语言级 await,但 TTask.Future + .Value 提供等价语义。OmniThreadLibrary(OTL)在 PPL 之上提供更高级抽象(管道、消息传递)。
uses System.Threading, System.SyncObjs;
// configure thread pool
var
Pool: TThreadPool;
begin
Pool := TThreadPool.Create;
try
Pool.SetMaxWorkerThreads(8);
Pool.SetMinWorkerThreads(2);
// use pool with TTask
TTask.Run(
procedure
begin
// runs on the pool
end, Pool);
finally
Pool.Free;
end;
end;
// async/await pattern using futures
function FetchDataAsync: IFuture<TStrings>;
begin
Result := TTask.Future<TStrings>(
function: TStrings
begin
Result := TStringList.Create;
// simulate slow fetch
TThread.Sleep(2000);
Result.LoadFromFile('data.txt');
end);
end;
// chain tasks
var
Task1, Task2: ITask;
begin
Task1 := TTask.Run(
procedure
begin
DownloadFile('part1.zip');
end);
// Task2 runs after Task1 completes
Task2 := Task1.ContinueWith(
procedure(const ATask: ITask)
begin
if ATask.Status = TTaskStatus.Completed then
ProcessFile('part1.zip')
else
ShowMessage('Download failed');
end);
end;
// cancellation
var
Cancel: ICancellation;
begin
Cancel := TTask.CurrentTask.Cancellation;
while not Cancel.IsCancelled do
begin
DoChunk;
Sleep(100);
end;
end;使用 Indy 进行网络编程
TCP 客户端与服务器(Indy)
Indy(Internet Direct)是 Delphi 捆绑的网络库。TIdTCPClient 连接到服务器 —— WriteLn/ReadLn 用于基于行的协议,Write/Read 用于二进制。ConnectTimeout 防止挂起。TIdTCPServer 监听连接 —— OnExecute 在每个客户端的线程中运行(AContext 代表每个连接)。Indy 使用阻塞套接字(更简单的模型 —— 无回调),因此服务器处理器在工作线程中运行。始终优雅处理断开连接。对于高性能服务器,考虑 ICS(重叠 I/O)或 Synapse。Indy 组件是非可视的 —— 放在窗体上或在代码中创建。设置 Active := True 开始监听。DefaultPort 设置监听端口。
uses IdTCPClient, IdTCPServer, IdContext;
// TCP Client
var
Client: TIdTCPClient;
Response: string;
begin
Client := TIdTCPClient.Create(nil);
try
Client.Host := 'example.com';
Client.Port := 8080;
Client.ConnectTimeout := 5000;
Client.Connect;
try
Client.IOHandler.WriteLn('Hello Server');
Response := Client.IOHandler.ReadLn;
ShowMessage('Server: ' + Response);
finally
Client.Disconnect;
end;
finally
Client.Free;
end;
end;
// TCP Server
type
TForm1 = class(TForm)
IdTCPServer1: TIdTCPServer;
procedure FormCreate(Sender: TObject);
procedure ServerExecute(AContext: TIdContext);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
IdTCPServer1.DefaultPort := 8080;
IdTCPServer1.OnExecute := ServerExecute;
IdTCPServer1.Active := True;
end;
procedure TForm1.ServerExecute(AContext: TIdContext);
var
Msg: string;
begin
Msg := AContext.Connection.IOHandler.ReadLn;
AContext.Connection.IOHandler.WriteLn('Echo: ' + Msg);
if Msg = 'quit' then
AContext.Connection.Disconnect;
end;HTTP 客户端(TIdHTTP)
TIdHTTP 是 Indy 的 HTTP 客户端 —— 支持 GET、POST、PUT、DELETE、头、cookie 和 SSL/TLS。对于 HTTPS,附加 TIdSSLIOHandlerSocketOpenSSL(需要 OpenSSL DLL:libeay32/ssleay32 或 libcrypto/libssl)。Request.ContentType 和 CustomHeaders 设置请求元数据。POST 接受字符串体(用于 JSON/API)或 TStrings(用于表单数据)。EIdHTTPProtocolException 捕获 HTTP 错误(404、500 等),带 ErrorCode 和 ErrorMessage。对于现代 REST 客户端,考虑 TRESTClient(内置,无 OpenSSL 依赖)或 TNetHTTPClient(更轻量)。始终在 finally 块中释放 HTTP 和 SSL 处理器。设置 Http.HandleRedirects := True 自动跟随 301/302 重定向。
uses IdHTTP, IdSSLOpenSSL, System.JSON;
var
Http: TIdHTTP;
SSL: TIdSSLIOHandlerSocketOpenSSL;
Response: string;
JSON: TJSONObject;
Params: TStringList;
begin
Http := TIdHTTP.Create(nil);
SSL := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
try
Http.IOHandler := SSL;
SSL.SSLOptions.Method := sslvTLSv1_2;
Http.Request.ContentType := 'application/json';
Http.Request.CustomHeaders.AddValue('Authorization', 'Bearer token123');
// GET request
Response := Http.Get('https://api.example.com/users');
ShowMessage(Response);
// POST with JSON body
JSON := TJSONObject.Create;
try
JSON.AddPair('name', 'Alice');
JSON.AddPair('age', 30);
Response := Http.Post('https://api.example.com/users', JSON.ToJSON);
finally
JSON.Free;
end;
// POST form data
Params := TStringList.Create;
try
Params.Add('username=alice');
Params.Add('password=secret');
Response := Http.Post('https://api.example.com/login', Params);
finally
Params.Free;
end;
// handle errors
try
Http.Get('https://api.example.com/missing');
except
on E: EIdHTTPProtocolException do
ShowMessage('HTTP ' + IntToStr(E.ErrorCode) + ': ' + E.ErrorMessage);
end;
finally
SSL.Free;
Http.Free;
end;
end;SMTP 电子邮件(TIdSMTP)
TIdSMTP 通过 SMTP 服务器发送电子邮件。TIdMessage 代表电子邮件(From、Recipients、Subject、Body)。对于 Gmail/Office365,使用 TLS(端口 587,utUseExplicitTLS)或 SSL(端口 465,utUseImplicitTLS)。Gmail 需要启用 2FA 的"应用密码"(非常规密码)。TIdAttachmentFile 添加文件附件。对于 HTML 电子邮件,设置 ContentType := 'text/html'。对于多部分(HTML + 纯文本 + 附件),使用 TIdMessageBuilderHTML。常见端口:25(未加密/中继)、465(SSL)、587(STARTTLS)。始终将 Connect/Send 包装在 try/finally 中以确保 Disconnect。对于接收电子邮件,使用 TIdPOP3 或 TIdIMAP4。
uses IdSMTP, IdMessage, IdSSLOpenSSL, IdExplicitTLSClientServerBase;
var
SMTP: TIdSMTP;
Msg: TIdMessage;
SSL: TIdSSLIOHandlerSocketOpenSSL;
begin
SMTP := TIdSMTP.Create(nil);
Msg := TIdMessage.Create(nil);
SSL := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
try
// SMTP config (Gmail example)
SMTP.Host := 'smtp.gmail.com';
SMTP.Port := 587;
SMTP.UseTLS := utUseExplicitTLS;
SMTP.IOHandler := SSL;
SSL.SSLOptions.Method := sslvTLSv1_2;
SMTP.Username := '[email protected]';
SMTP.Password := 'app-password';
// message
Msg.From.Address := '[email protected]';
Msg.From.Name := 'My App';
Msg.Recipients.Add.Address := '[email protected]';
Msg.Subject := 'Test from Delphi';
Msg.Body.Text := 'Hello,' + sLineBreak + 'This is a test email.';
// attachment
var Attachment := TIdAttachmentFile.Create(Msg.MessageParts,
'C:\report.pdf');
// HTML body
Msg.ContentType := 'text/html';
Msg.Body.Text := '<h1>Hello</h1><p>HTML email from Delphi</p>';
// connect and send
SMTP.Connect;
try
SMTP.Send(Msg);
ShowMessage('Email sent!');
finally
SMTP.Disconnect;
end;
finally
SSL.Free;
Msg.Free;
SMTP.Free;
end;
end;UDP 与原始套接字
UDP 是无连接的 —— 无握手,无保证交付,但比 TCP 快。TIdUDPClient.Send 发送数据报;ReceiveString 等待带超时的响应。BroadcastEnabled 发送到 255.255.255.255(LAN 上的所有设备)—— 适用于服务发现。TIdUDPServer.OnUDPRead 接收数据报;ABinding.PeerIP/PeerPort 标识发送者。UDP 非常适合:DNS、SNMP、游戏状态更新、流媒体和发现协议。对于 UDP 上的可靠性,在应用层实现 ACK/重试。TIdBytes 是 Indy 的字节数组类型 —— 使用 BytesToString/ToBytes 进行转换。对于原始套接字控制(原始 IP 数据包、自定义协议),使用 WinSock2 单元或 Synapse 库。
uses IdUDPClient, IdUDPServer, IdSocketHandle;
// UDP Client (connectionless, fire-and-forget)
var
UDP: TIdUDPClient;
begin
UDP := TIdUDPClient.Create(nil);
try
UDP.Host := '255.255.255.255'; // broadcast
UDP.Port := 9999;
UDP.BroadcastEnabled := True;
UDP.Send('DISCOVER');
// receive response
var Response: string;
UDP.ReceiveString(Response, 1000); // timeout 1s
ShowMessage(Response);
finally
UDP.Free;
end;
end;
// UDP Server
type
TForm1 = class(TForm)
IdUDPServer1: TIdUDPServer;
procedure FormCreate(Sender: TObject);
procedure UDPRead(AThread: TIdUDPListenerThread;
const AData: TIdBytes; ABinding: TIdSocketHandle);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
IdUDPServer1.DefaultPort := 9999;
IdUDPServer1.OnUDPRead := UDPRead;
IdUDPServer1.Active := True;
end;
procedure TForm1.UDPRead(AThread: TIdUDPListenerThread;
const AData: TIdBytes; ABinding: TIdSocketHandle);
var
Msg: string;
begin
Msg := BytesToString(AData);
// reply to sender
ABinding.SendTo(ABinding.PeerIP, ABinding.PeerPort,
ToBytes('ACK: ' + Msg));
end;
// raw socket with TIdIOHandlerSocket
// for low-level protocols, use WinSock2 unit directlyFTP 与 REST 客户端
TIdFTP 提供 FTP 客户端功能 —— Connect、List、Put(上传)、Get(下载)、MakeDir、ChangeDir。被动模式(Passive := True)通过 NAT/防火墙工作。UseTLS 保护 FTP(FTPS)。对于 SFTP(基于 SSH),使用第三方库(libssh2、SecureBlackbox)。TRESTClient/TRESTRequest/TRESTResponse 是内置 REST 组件(无 OpenSSL 依赖)—— 非常适合现代 API 消费。Resource 使用由 AddUrlSegment 填充的 {param} 占位符。Execute 发送请求;RESTResponse.Content 持有正文;JSONValue 自动解析 JSON。REST 组件支持 OAuth2、基本认证和自定义认证器。对于高性能 REST,考虑 TNetHTTPClient(更轻)或 Indy 的 TIdHTTP 获得最大控制。
uses IdFTP, IdFTPCommon, IdExplicitTLSClientServerBase;
// FTP client
var
FTP: TIdFTP;
begin
FTP := TIdFTP.Create(nil);
try
FTP.Host := 'ftp.example.com';
FTP.Username := 'user';
FTP.Password := 'pass';
FTP.Passive := True; // NAT-friendly mode
FTP.UseTLS := utUseExplicitTLS;
FTP.Connect;
try
// list directory
var Listing: TStringList := TStringList.Create;
try
FTP.List(Listing);
for var S in Listing do
ShowMessage(S);
finally
Listing.Free;
end;
// change directory
FTP.ChangeDir('/uploads');
// upload/download
FTP.Put('C:\local.txt', 'remote.txt');
FTP.Get('remote.txt', 'C:\downloaded.txt');
// create/remove directory
FTP.MakeDir('newfolder');
FTP.RemoveDir('oldfolder');
finally
FTP.Disconnect;
end;
finally
FTP.Free;
end;
end;
// REST Client (built-in, no Indy needed)
uses REST.Client, REST.Types;
var
RESTClient: TRESTClient;
RESTRequest: TRESTRequest;
RESTResponse: TRESTResponse;
begin
RESTClient := TRESTClient.Create('https://api.example.com');
RESTRequest := TRESTRequest.Create(RESTClient);
RESTResponse := TRESTResponse.Create(nil);
try
RESTRequest.Resource := 'users/{id}';
RESTRequest.Method := TRESTRequestMethod.rmGET;
RESTRequest.Params.AddUrlSegment('id', '42');
RESTRequest.Params.AddItem('fields', 'name,email', pkGETorPOST);
RESTRequest.Execute;
ShowMessage(RESTResponse.Content); // JSON response
// access JSON fields directly
ShowMessage(RESTResponse.JSONValue.GetValue<string>('name'));
finally
RESTResponse.Free;
RESTRequest.Free;
RESTClient.Free;
end;
end;DLL 与 BPL 包
创建与使用 DLL
DLL(动态链接库)跨应用共享代码。使用 'library' 关键字(而非 'program')构建 DLL。'exports' 列出对外部调用者可用的函数。stdcall 是标准 Windows 调用约定(C/C++、VB、C# 兼容)。静态导入(external)在编译时链接 —— DLL 必须在运行时存在。动态加载(LoadLibrary/GetProcAddress)在运行时加载 —— 启用插件和可选功能。FreeLibrary 卸载 DLL。PChar(PWideChar)是 DLL 导出的标准字符串类型(共享内存,无 Delphi 特定类型)。切勿直接导出 Delphi 字符串、对象或接口 —— 它们是 Delphi 内部的。使用 ShareMem 单元进行 Delphi 到 Delphi 的字符串共享(需要 BorlndMM.dll)。
// --- MyLib.dpr (DLL project) ---
library MyLib;
uses
System.SysUtils, System.Classes;
// exported function (stdcall for compatibility)
function Add(A, B: Integer): Integer; stdcall;
begin
Result := A + B;
end;
// exported procedure
procedure ShowMessage(const Msg: PChar); stdcall;
begin
WriteLn(Msg);
end;
// export table
exports
Add name 'Add',
ShowMessage name 'ShowMessage';
begin
end.
// --- MainApp.dpr (consumer) ---
// static import
function Add(A, B: Integer): Integer; stdcall; external 'MyLib.dll';
procedure ShowMsg(const Msg: PChar); stdcall; external 'MyLib.dll';
begin
ShowMessage(IntToStr(Add(3, 4))); // 7
end;
// dynamic loading (load at runtime)
var
LibHandle: THandle;
AddFunc: function(A, B: Integer): Integer; stdcall;
begin
LibHandle := LoadLibrary('MyLib.dll');
if LibHandle <> 0 then
try
@AddFunc := GetProcAddress(LibHandle, 'Add');
if Assigned(@AddFunc) then
ShowMessage(IntToStr(AddFunc(10, 20)));
finally
FreeLibrary(LibHandle);
end;
end;通过接口共享对象
跨 DLL 边界共享对象很棘手 —— Delphi 类不能直接导出(不同的内存管理器,不同的 RTTI)。解决方案:使用带 GUID 的接口。DLL 导出返回 IPlugin 的工厂函数(CreatePlugin)。宿主应用定义相同的接口(相同的 GUID!)并调用工厂。接口引用计数自动处理清理。对字符串使用 PChar(而非 Delphi string)以避免内存管理器冲突。这是插件架构模式 —— 动态加载 DLL,通过工厂创建插件,通过接口通信。对于完整插件系统,考虑 Delphi 中的插件框架或使用包(BPL),它们共享 RTL 并允许直接类共享。
// --- PluginDLL.dpr ---
library PluginDLL;
type
IPlugin = interface
['{12345678-1234-1234-1234-123456789012}']
function GetName: PChar; stdcall;
function Execute(const Input: PChar): PChar; stdcall;
procedure Free; stdcall;
end;
TMyPlugin = class(TInterfacedObject, IPlugin)
public
function GetName: PChar; stdcall;
function Execute(const Input: PChar): PChar; stdcall;
procedure Free; stdcall;
end;
function TMyPlugin.GetName: PChar;
begin
Result := 'My Plugin v1.0';
end;
function TMyPlugin.Execute(const Input: PChar): PChar;
begin
Result := PChar('Processed: ' + Input);
end;
// factory function - creates and returns the plugin
function CreatePlugin: IPlugin; stdcall;
begin
Result := TMyPlugin.Create;
end;
exports CreatePlugin;
// --- HostApp.dpr ---
type
IPlugin = interface
['{12345678-1234-1234-1234-123456789012}']
function GetName: PChar; stdcall;
function Execute(const Input: PChar): PChar; stdcall;
procedure Free; stdcall;
end;
var
CreatePlugin: function: IPlugin; stdcall;
Plugin: IPlugin;
Handle: THandle;
begin
Handle := LoadLibrary('PluginDLL.dll');
if Handle <> 0 then
try
@CreatePlugin := GetProcAddress(Handle, 'CreatePlugin');
if Assigned(@CreatePlugin) then
begin
Plugin := CreatePlugin;
ShowMessage(Plugin.GetName);
ShowMessage(Plugin.Execute('test'));
end;
finally
FreeLibrary(Handle);
end;
end;BPL 包(Borland 包库)
BPL(Borland 包库)是 Delphi 特定的共享库 —— 它们共享 Delphi RTL,允许直接类/对象共享(不同于 DLL)。用 'package' 关键字构建。运行时包减少 EXE 大小(共享代码在 .bpl 文件中)并启用热插拔模块。LoadPackage/UnloadPackage 动态加载 BPL —— GetClass 按名称查找注册的类。RegisterClass/UnRegisterClass 使类可发现。BPL 需要部署 Delphi RTL BPL(rtl.bpl、vcl.bpl)。使用 BPL 用于:插件架构(直接共享 Delphi 类型)、模块化应用(按需加载功能)和减少内存(共享代码加载一次)。对于跨语言共享,使用 DLL;对于仅 Delphi,BPL 更强大。
// --- MyPackage.dpk (runtime package) ---
package MyPackage;
requires
rtl,
vcl;
contains
MyUnit in 'MyUnit.pas',
MyForm in 'MyForm.pas' {Form1};
// compile: dcc32 -B MyPackage.dpk
// produces MyPackage.bpl (shared runtime package)
// --- Using the package ---
// Option 1: link statically (compile-time reference)
// Project → Options → Packages → Runtime packages → add MyPackage.bpl
// Option 2: load dynamically with LoadPackage
var
PackageModule: THandle;
FormClass: TPersistentClass;
begin
PackageModule := LoadPackage('MyPackage.bpl');
try
// register and use forms/classes from the package
FormClass := GetClass('TForm1');
if FormClass <> nil then
with TFormClass(FormClass).Create(Application) do
try
ShowModal;
finally
Free;
end;
finally
UnloadPackage(PackageModule);
end;
end;
// RegisterClass in the package's unit:
unit MyForm;
interface
uses Vcl.Forms;
type
TForm1 = class(TForm)
end;
implementation
initialization
RegisterClass(TForm1); // make class discoverable
finalization
UnRegisterClass(TForm1);
end.跨边界内存管理
头号 DLL 陷阱:在一个模块中释放在另一个模块中分配的内存。每个模块有自己的内存管理器 —— 混合它们导致堆损坏和崩溃。解决方案:(1) ShareMem —— 共享 BorlndMM.dll,但需要部署该 DLL。(2) 调用者分配模式 —— 调用者提供缓冲区,DLL 填充它(最安全,语言无关)。(3) SimpleShareMem/FastMM —— 现代共享内存管理器(FastMM 自 Delphi 2006 起为默认)。(4) 基于回调的释放 —— DLL 提供释放函数。对于 PChar 返回,使用 StrNew/StrDispose(Windows API,共享)。对于生产 Delphi 到 Delphi,使用 BPL(共享 RTL)或 SimpleShareMem。对于跨语言,始终使用调用者分配模式。切勿在没有共享内存管理器的情况下跨 DLL 边界传递 Delphi string/object/interface 类型。
// PROBLEM: different memory managers in EXE and DLL
// → crashes when freeing memory allocated in another module
// Solution 1: ShareMem (Delphi-to-Delphi only)
// First unit in both EXE and DLL .dpr file:
uses
ShareMem; // uses BorlndMM.dll as shared memory manager
// Solution 2: Caller allocates, caller frees (safest)
// DLL fills a buffer provided by the caller
procedure GetData(Buffer: PChar; var BufSize: Integer); stdcall;
var
Data: string;
begin
Data := 'Hello from DLL';
BufSize := Length(Data) + 1;
if Buffer <> nil then
StrLCopy(Buffer, PChar(Data), BufSize);
end;
// caller:
var
Size: Integer;
Buffer: PChar;
begin
GetData(nil, Size); // query size
GetMem(Buffer, Size); // allocate
try
GetData(Buffer, Size); // fill
ShowMessage(Buffer);
finally
FreeMem(Buffer); // caller frees
end;
end;
// Solution 3: Use SafeCall / COM-style allocation
// Solution 4: Use FastMM as shared manager (modern approach)
// Add SimpleShareMem unit (uses FastMM) to both projects
// Solution 5: Return only simple types / PChar with callback
type
TFreeCallback = procedure(Ptr: Pointer); stdcall;
function CreateString(out S: PChar; FreeProc: TFreeCallback): Boolean; stdcall;
begin
S := StrNew('Allocated in DLL');
Result := True;
// caller calls FreeProc(S) which calls StrDispose in the DLL
end;资源文件与嵌入
资源文件将二进制数据(图像、图标、声音、字符串、版本信息)嵌入 EXE/DLL —— 无需外部文件。创建 .rc 脚本,用 brcc32 编译(或让 IDE 自动编译)。{$R file.res} 链接它。TResourceStream 将 RCDATA 资源作为流读取。LoadIcon/LoadString 使用 Windows API 处理特定资源类型。资源在运行时是只读的,但将所有内容保存在一个文件中(非常适合部署)。常见用途:应用图标、启动画面图像、默认配置、WAV 声音、版本信息(文件属性对话框)、本地化字符串。对于大数据,考虑嵌入前压缩。资源 ID 可以是名称(字符串)或数字。RT_RCDATA 是通用二进制资源类型。
// --- Resource script (.rc file) ---
// MyResources.rc:
// LOGO RCDATA "logo.png"
// ICON1 ICON "app.ico"
// VERSION VERSIONINFO ...
// WAVE1 WAVE "sound.wav"
// STR1 STRINGTABLE { "Hello" }
// compile: brcc32 MyResources.rc → MyResources.res
// or add .rc to project (auto-compiled)
// --- In .dpr ---
{$R MyResources.res} // link resource
// --- Loading resources ---
uses System.Classes, Vcl.Graphics, Winapi.Windows;
// load RCDATA (binary data)
var
Stream: TResourceStream;
begin
Stream := TResourceStream.Create(HInstance, 'LOGO', RT_RCDATA);
try
Image1.Picture.LoadFromStream(Stream);
finally
Stream.Free;
end;
end;
// load icon
var
Icon: TIcon;
begin
Icon := TIcon.Create;
try
Icon.Handle := LoadIcon(HInstance, 'ICON1');
Image1.Picture.Icon.Assign(Icon);
finally
Icon.Free;
end;
end;
// load string resource
var
S: string;
Buffer: array[0..255] of Char;
begin
LoadString(HInstance, 1, Buffer, SizeOf(Buffer));
S := Buffer;
end;
// embed a file as resource at compile time
// {$R 'data.bin' 'data.bin'} // or use .rc调试与性能调优
调试器与断点
Delphi 的 IDE 调试器很强大:通过点击装订线设置断点。条件断点仅在表达式为真时中断(例如,i > 100)。日志/跟踪断点记录消息而不停止 —— 非常适合监视循环。asm int 3 end 在代码中创建硬断点(CPU 陷阱)。OutputDebugString 记录到事件日志窗口(和 DebugView 工具)。Assert 在调试构建中检查条件(用 {$C-} 或在发布中关闭断言禁用)。DebugHook 在 IDE 中运行时非零。调用堆栈窗口跟踪调用链;线程窗口检查所有线程;局部变量显示当前作用域。启用"使用调试 DCU"以单步执行 RTL/VCL 源代码。
// Conditional breakpoints (set in IDE):
// Break when expression is true
// e.g., (i > 100) and (List.Count > 0)
// Log breakpoints (no break, just log):
// Log message: "Iteration {i}, Count={List.Count}"
// Trace points / Action breakpoints:
// Run macro or evaluate expression on hit
// Code-based breakpoints:
var
I: Integer;
begin
for I := 1 to 1000 do
begin
// break only when condition met
if (I mod 100 = 0) and DebugHook <> 0 then
asm int 3 end; // hard breakpoint (CPU trap)
// or use OutputDebugString for logging
OutputDebugString(PChar('Processing ' + IntToStr(I)));
end;
end;
// Assert (only in debug builds)
Assert(List.Count > 0, 'List must not be empty');
// DebugHook: 0 = release, 1 = IDE, 2 = IDE step-over
if DebugHook <> 0 then
ShowMessage('Running in debugger');
// Watch and Evaluate expressions in IDE:
// List.Count
// List[0].Name
// TMyObject(Obj).PrivateField (with "Use Debug DCUs")
// Call Stack window shows the call chain
// Threads window shows all active threads
// Local Variables shows current scope variables异常处理与堆栈跟踪
Delphi 异常:try/except 捕获错误,try/finally 保证清理。异常类形成层次结构:Exception → EDivByZero、EAccessViolation、EListError、EAbort(静默)等。'on E: ExceptionType do' 捕获特定类型;基础 'on E: Exception do' 捕获所有。'raise;' 重新引发当前异常(保留堆栈跟踪)。EAbort(或 Abort 过程)引发静默异常(无对话框)。TApplicationEvents.OnException 是全局处理器 —— 捕获未处理的异常。对于堆栈跟踪,使用 JCL(JclDebug)或 MadExcept/ExceptionHunter —— 它们捕获调用堆栈、寄存器转储,甚至电子邮件崩溃报告。始终记录异常以便事后调试。切勿在生产中静默吞掉异常。
uses
System.SysUtils, System.Diagnostics;
// structured exception handling
try
RiskyOperation;
except
on E: EDivByZero do
ShowMessage('Division error: ' + E.Message);
on E: EAccessViolation do
ShowMessage('Access violation at ' + E.Message);
on E: Exception do
begin
ShowMessage('Unexpected: ' + E.ClassName + ': ' + E.Message);
raise; // re-raise
end;
end;
// finally (always executes)
try
AcquireResource;
UseResource;
finally
ReleaseResource; // always runs
end;
// nested try/except/finally
try
try
RiskyCode;
except
on E: Exception do
begin
LogError(E);
raise EAbort.Create(''); // suppress display
end;
end;
finally
Cleanup;
end;
// global exception handler
procedure TForm1.ApplicationEvents1Exception(Sender: TObject; E: Exception);
begin
LogError(Format('%s: %s', [E.ClassName, E.Message]));
if not (E is EAbort) then
ShowMessage('Error: ' + E.Message);
end;
// get stack trace (with JCL or MadExcept)
// JclDebug: JclCreateStackInfo, JclLastExceptStackList性能分析与性能
TStopwatch 是高精度计时器(使用 QueryPerformanceCounter)。优化前始终进行基准测试 —— 不要猜测。ReportMemoryLeaksOnShutdown := True 在程序退出时捕获泄漏(调试构建)。常见 Delphi 性能陷阱:(1) 循环中的字符串连接创建副本 —— 使用 TStringBuilder 或预分配。(2) 循环中的 SetLength 重新分配 —— 一次设置大小。(3) 按值传递字符串/数组复制它们 —— 对只读参数使用 'const'。(4) TStringList.Sorted + Find 是 O(log n);未排序的 IndexOf 是 O(n)。(5) TList<T>.Add 是摊销 O(1) 但前面 Insert 是 O(n)。对于深度分析,使用 Sampling Profiler(免费)、AQTime 或 GpProfile —— 它们无需代码更改即可识别热点。优化占用 80% 时间的 20% 代码。
uses System.Diagnostics;
// TStopwatch - precise timing
var
SW: TStopwatch;
Elapsed: Int64;
begin
SW := TStopwatch.StartNew;
try
ExpensiveOperation;
finally
SW.Stop;
ShowMessage(Format('Elapsed: %d ms', [SW.ElapsedMilliseconds]));
end;
end;
// benchmark comparison
function Benchmark(const Name: string; const Action: TProc): Int64;
var
SW: TStopwatch;
I: Integer;
begin
SW := TStopwatch.StartNew;
for I := 1 to 1000 do
Action;
SW.Stop;
WriteLn(Format('%s: %d ms', [Name, SW.ElapsedMilliseconds]));
Result := SW.ElapsedMilliseconds;
end;
// memory usage
var
Mem: TMemoryManagerState;
begin
GetMemoryManagerState(Mem);
ShowMessage(Format('Allocated: %d bytes', [Mem.TotalAllocated]));
// report memory leaks on shutdown
ReportMemoryLeaksOnShutdown := True; // shows leak dialog on exit
end;
// common optimizations:
// 1. Use TStringBuilder for heavy string concatenation
var SB := TStringBuilder.Create;
try
for var I := 1 to 10000 do
SB.Append('Line ').Append(I).AppendLine;
Result := SB.ToString;
finally
SB.Free;
end;
// 2. SetLength once, not in a loop
SetLength(Result, Count); // pre-allocate
for I := 0 to Count - 1 do
Result[I] := Compute(I);
// 3. Use const for strings/arrays (avoids copy)
procedure Process(const Data: string); // const = no copy内存管理与泄漏
内存管理是 Delphi 最大的 bug 来源。规则 #1:每个 Create 必须有匹配的 Free。虔诚地使用 try/finally。对于自动管理,使用接口(TInterfacedObject + 引用计数)—— 无需 Free。带 OwnsObjects := True 的 TObjectList<T> 自动释放包含的对象。ReportMemoryLeaksOnShutdown := True 在退出时显示列出泄漏对象的对话框(仅调试)。FullDebugMode 中的 FastMM(默认内存管理器)将泄漏记录到文件,带分配堆栈跟踪 —— 对跟踪泄漏至关重要。常见泄漏模式:缺少 try/finally、未移除的事件处理器、循环引用(用 [Weak] 修复)、未释放的线程、finalization 中未释放的全局对象。单元的 finalization 部分在关闭时运行 —— 用于全局清理。
// Rule: every Create needs a Free (or use interfaces)
// Pattern 1: try/finally
var
Obj: TMyObject;
begin
Obj := TMyObject.Create;
try
Obj.DoWork;
finally
Obj.Free; // always freed
end;
end;
// Pattern 2: interface reference counting (automatic)
var
Obj: IMyInterface;
begin
Obj := TMyObject.Create; // TInterfacedObject
Obj.DoWork;
// freed automatically when Obj goes out of scope
end;
// Pattern 3: TObjectList (owns children)
var
List: TObjectList<TPerson>;
begin
List := TObjectList<TPerson>.Create(True); // OwnsObjects
try
List.Add(TPerson.Create('Alice'));
List.Add(TPerson.Create('Bob'));
// freeing List frees all TPerson objects
finally
List.Free;
end;
end;
// Detecting leaks
// 1. ReportMemoryLeaksOnShutdown := True;
// 2. FastMM (default since D2006) with FullDebugMode
// → logs leaks with stack traces to file
// 3. Set breakpoint on System._DebugIntfMemLeak (FastMM)
// Common leak causes:
// - Create without Free (missing try/finally)
// - Event handler assigned but never removed
// - Circular references (use [Weak])
// - TThread not freed (FreeOnTerminate := True)
// - Global objects not freed in finalization
// finalization section for globals
var
GlobalCache: TDictionary<string, TObject>;
initialization
GlobalCache := TDictionary<string, TObject>.Create;
finalization
GlobalCache.Free; // cleanup on shutdown代码质量与测试
DUnitX 是现代单元测试框架(替代 DUnit)。[TestFixture] 标记测试类,[Test] 标记测试方法,[Setup]/[TearDown] 在每个测试前后运行。[TestCase] 用内联数据参数化测试。Assert.AreEqual/IsTrue/WillRaise 验证结果。测试驱动开发(TDD):先写测试,再写代码。测试捕获回归并记录预期行为。Delphi Mocks(或 Spring4D 模拟)从接口创建模拟对象 —— Setup.Expect 定义预期,VerifyAll 检查它们是否被满足。模拟对隔离单元(模拟数据库、网络、文件系统)至关重要。目标是业务逻辑的高覆盖率。在 CI(持续集成)中运行测试以尽早捕获回归。集成测试验证组件协同工作;单元测试隔离验证单个单元。
// DUnitX - unit testing framework
uses DUnitX.TestFramework;
type
[TestFixture]
TCalculatorTests = class
public
[Setup]
procedure Setup;
[TearDown]
procedure TearDown;
[Test]
procedure TestAdd;
[Test]
[TestCase('A', '1,2,3')]
[TestCase('B', '10,20,30')]
procedure TestAddParam(A, B, Expected: Integer);
[Test]
procedure TestDivideByZero;
end;
procedure TCalculatorTests.TestAdd;
var
Calc: TCalculator;
begin
Calc := TCalculator.Create;
try
Assert.AreEqual(5, Calc.Add(2, 3));
Assert.AreNotEqual(6, Calc.Add(2, 3));
Assert.IsTrue(Calc.Add(0, 0) = 0);
finally
Calc.Free;
end;
end;
procedure TCalculatorTests.TestDivideByZero;
var
Calc: TCalculator;
begin
Calc := TCalculator.Create;
try
Assert.WillRaise(
procedure
begin
Calc.Divide(10, 0);
end, EDivByZero);
finally
Calc.Free;
end;
end;
// mock with interfaces
type
[Mock]
ILogger = interface
['{...}']
procedure Log(const Msg: string);
end;
// Delphi Mocks framework
var
MockLogger: TMock<ILogger>;
begin
MockLogger := TMock<ILogger>.Create;
MockLogger.Setup.Expect.Once.When.Log('test');
// ... use MockLogger.Object ...
MockLogger.VerifyAll; // asserts Log was called
end;泛型与集合
泛型类声明
泛型让你编写类型安全的容器而无需转换。在类型名后用 <T> 声明。编译器为使用的每个类型生成专用版本。在泛型类型中使用 TArray<T> 代替 array of 作为动态数组。
type
TStack<T> = class
private
FItems: TArray<T>;
FCount: Integer;
public
procedure Push(const AValue: T);
function Pop: T;
function Peek: T;
function Count: Integer;
end;
procedure TStack<T>.Push(const AValue: T);
begin
if FCount = Length(FItems) then
SetLength(FItems, FCount * 2 + 4);
FItems[FCount] := AValue;
Inc(FCount);
end;TDictionary 用法
TDictionary<K,V> 是泛型哈希映射。Add 在键重复时引发异常;OrAdd 执行 upsert。TryGetValue 在键不存在时返回 false(而非异常)。始终释放字典 —— 默认它们不拥有对象。
uses
System.Generics.Collections;
var
Dict: TDictionary<string, Integer>;
begin
Dict := TDictionary<string, Integer>.Create;
try
Dict.Add('apple', 5);
Dict.Add('banana', 3);
Dict.OrAdd('apple', 10); // add or replace
if Dict.TryGetValue('apple', Value) then
Writeln(Value);
for var Pair in Dict do
Writeln(Pair.Key, ': ', Pair.Value);
finally
Dict.Free;
end;
end;带比较器的 TList
TList<T>.Sort 使用 IComparer<T>。TComparer<T>.Construct 将匿名函数包装为比较器。BinarySearch 要求列表已用相同比较器排序。AddRange 接受开放数组或另一个列表。
var
List: TList<Integer>;
begin
List := TList<Integer>.Create;
try
List.AddRange([5, 2, 8, 1, 9]);
List.Sort; // ascending
// custom comparer (descending)
List.Sort(TComparer<Integer>.Construct(
function(const L, R: Integer): Integer
begin
Result := R - L;
end));
List.BinarySearch(8, Index); // requires sorted list
finally
List.Free;
end;
end;泛型约束
约束限制可替换的类型:'class'(引用类型)、'record'(值类型)、'constructor'(无参构造函数)或特定祖先类。多个约束用逗号分隔。没有 'constructor' 你无法调用 T.Create。
type
TRepository<T: class, constructor> = class
public
function CreateInstance: T;
procedure Save(const AEntity: T);
end;
function TRepository<T>.CreateInstance: T;
begin
Result := T.Create; // requires 'constructor' constraint
end;
// multiple constraints: class, constructor, specific base
type
TControlFactory<T: TControl, constructor> = class ... end;用 TObjectDictionary 实现对象所有权
TObjectDictionary<K,V> 扩展 TDictionary 增加所有权。传递 [doOwnsValues]、[doOwnsKeys] 或两者。在 Remove/Clear/Free 时,拥有的对象自动释放 —— 防止对象集合中的内存泄漏。
var
Dict: TObjectDictionary<string, TButton>;
begin
// owns values — frees them automatically
Dict := TObjectDictionary<string, TButton>.Create([doOwnsValues]);
try
Dict.Add('btn1', TButton.Create(nil));
Dict.Add('btn2', TButton.Create(nil));
Dict.Remove('btn1'); // frees the TButton
finally
Dict.Free; // frees remaining buttons
end;
end;匿名方法与闭包
基本匿名方法
匿名方法是内联函数引用。TFunc<...> 用于函数,TProc<...> 用于过程。它们从封闭作用域捕获变量(闭包)。可赋值给变量,可作为参数传递。
var
Adder: TFunc<Integer, Integer, Integer>;
begin
Adder := function(A, B: Integer): Integer
begin
Result := A + B;
end;
Writeln(Adder(3, 4)); // 7
end;闭包捕获变量
捕获的变量在堆上分配,生命周期与匿名方法一样长。每次调用 MakeMultiplier 捕获自己的 Factor —— 闭包是独立的。这就是工厂和部分应用的工作方式。
function MakeMultiplier(Factor: Integer): TFunc<Integer, Integer>;
begin
Result := function(X: Integer): Integer
begin
Result := X * Factor; // captures Factor
end;
end;
var
Double: TFunc<Integer, Integer>;
Triple: TFunc<Integer, Integer>;
begin
Double := MakeMultiplier(2);
Triple := MakeMultiplier(3);
Writeln(Double(10)); // 20
Writeln(Triple(10)); // 30
end;高阶函数
'reference to' 声明与匿名方法兼容的过程类型。Apply 是高阶函数 —— 接受函数作为参数。这实现了 map/filter/reduce 模式。使用 TArray<Integer> 作为动态数组。
type
TIntFunc = reference to function(X: Integer): Integer;
function Apply(const F: TIntFunc; Values: array of Integer): TArray<Integer>;
var
I: Integer;
begin
SetLength(Result, Length(Values));
for I := 0 to High(Values) do
Result[I] := F(Values[I]);
end;
var
Squared: TArray<Integer>;
begin
Squared := Apply(function(X: Integer): Integer
begin
Result := X * X;
end, [1, 2, 3, 4, 5]);
end;带闭包的事件处理器
匿名方法可以替代传统的基于方法的事件处理器,捕获上下文而无需字段。适用于一次性处理器和减少样板代码。捕获的 Caption 与 OnClick 持有的闭包引用一起存活。
procedure SetupButton(Button: TButton; const Caption: string);
begin
Button.Caption := Caption;
Button.OnClick := procedure(Sender: TObject)
begin
ShowMessage(Caption + ' clicked!'); // captures Caption
end;
end;
// instead of:
// procedure TForm1.Button1Click(Sender: TObject);
// begin
// ShowMessage('Button1 clicked!');
// end;带匿名的 TThread
CreateAnonymousThread 将闭包包装在线程中 —— 即发即忘的后台工作。使用 TThread.Queue(或 Synchronize)将 UI 更新封送回主线程。切勿从工作线程直接操作 UI 控件。
TThread.CreateAnonymousThread(
procedure
var
I: Integer;
begin
for I := 1 to 10 do
begin
TThread.Queue(nil,
procedure
begin
Memo1.Lines.Add('Progress: ' + I.ToString);
end);
Sleep(100);
end;
end).Start;特性与 RTTI
自定义特性声明
特性是继承 TCustomAttribute 的类。用 [AttrName(...)] 应用于类型、字段、方法、属性。编译器将它们嵌入 RTTI。构造函数参数成为特性参数。
type
DisplayNameAttribute = class(TCustomAttribute)
private
FName: string;
public
constructor Create(const AName: string);
property Name: string read FName;
end;
constructor DisplayNameAttribute.Create(const AName: string);
begin
FName := AName;
end;
[DisplayName('User Account')]
TUser = class
[DisplayName('Full Name')]
FName: string;
end;通过 RTTI 读取特性
TRttiContext 是 RTTI 的入口点。GetType 返回类的 TRttiType。GetAttributes 返回所有应用的特性。转换到你的特性类型以读取属性。RTTI 要求类在用 {$M+} 编译的单元中或派生自 TPersistent。
uses
System.Rtti;
var
Ctx: TRttiContext;
RttiType: TRttiType;
Attr: TCustomAttribute;
begin
Ctx := TRttiContext.Create;
try
RttiType := Ctx.GetType(TUser);
for Attr in RttiType.GetAttributes do
if Attr is DisplayNameAttribute then
Writeln(DisplayNameAttribute(Attr).Name);
finally
Ctx.Free;
end;
end;字段和方法 RTTI
GetFields 返回所有 public/published 字段。SetValue/GetValue 提供按名称的动态字段访问 —— 对序列化器和 ORM 很有用。GetMethods 返回所有方法(包括继承的)。RTTI 比直接调用慢。
var
Ctx: TRttiContext;
FieldType: TRttiField;
Method: TRttiMethod;
User: TUser;
begin
Ctx := TRttiContext.Create;
try
User := TUser.Create;
try
for FieldType in Ctx.GetType(TUser).GetFields do
begin
Writeln(FieldType.Name, ': ', FieldType.FieldType.Name);
FieldType.SetValue(User, 'Alice'); // set by RTTI
end;
Writeln(FieldType.GetValue(User).AsString);
finally
User.Free;
end;
finally
Ctx.Free;
end;
end;属性 RTTI 和调用
GetProperties 返回 published 属性。IsReadable/IsWritable 检查访问器。GetValue/SetValue 也适用于属性。TypeKind(tkInteger、tkString、tkClass 等)让你适当处理每种类型。这是大多数 Delphi 序列化器的工作方式。
var
Ctx: TRttiContext;
Prop: TRttiProperty;
Instance: TMyClass;
begin
Instance := TMyClass.Create;
try
for Prop in Ctx.GetType(TMyClass).GetProperties do
begin
if Prop.IsReadable then
Writeln(Prop.Name, ' = ', Prop.GetValue(Instance).ToString);
if Prop.IsWritable and (Prop.PropertyType.TypeKind = tkInteger) then
Prop.SetValue(Instance, 42);
end;
finally
Instance.Free;
end;
end;按名称调用方法
GetMethod 按名称查找方法(区分大小写)。Invoke 用 TValue 数组参数动态调用它。TValue 是任何类型的类似变体的包装器。适用于插件系统、脚本和后期绑定。返回 TValue —— 用 AsInteger、AsString 等转换。
var
Ctx: TRttiContext;
Method: TRttiMethod;
Args: array of TValue;
Result: TValue;
begin
Method := Ctx.GetType(TMyClass).GetMethod('CalculateTotal');
if Assigned(Method) then
begin
SetLength(Args, 2);
Args[0] := 10;
Args[1] := 20;
Result := Method.Invoke(MyInstance, Args);
Writeln(Result.AsInteger);
end;
end;接口深入
接口声明与实现
接口定义无实现的契约。GUID(可选但推荐)启用 'as' 转换和 Supports()。TInterfacedObject 提供引用计数。所有接口方法必须实现(没有 'abstract' 逃避)。接口中的属性需要访问器方法。
type
IShape = interface
['{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}']
function GetArea: Double;
function GetPerimeter: Double;
procedure Draw;
property Color: TColor read FColor write SetColor;
end;
TCircle = class(TInterfacedObject, IShape)
private
FRadius: Double;
FColor: TColor;
procedure SetColor(Value: TColor);
public
constructor Create(ARadius: Double);
function GetArea: Double;
function GetPerimeter: Double;
procedure Draw;
end;引用计数与内存
接口引用是引用计数的。当最后一个接口引用离开作用域时,对象被释放。切勿混合对同一实例的对象和接口引用 —— 接口引用计数会在对象指针仍指向它时释放它。选择一种所有权模型。
var
Shape: IShape;
begin
Shape := TCircle.Create(5.0); // refcount = 1
// ... use Shape ...
end; // refcount drops to 0, object freed automatically
// Mixing interface and object references — DANGER:
var
Obj: TCircle;
begin
Obj := TCircle.Create(5.0);
Shape := Obj; // refcount = 1
Shape := nil; // refcount = 0, Obj freed!
Obj.GetArea; // AV — dangling pointer
end;接口继承与多接口
接口可以从多个父接口继承。一个类可以实现多个接口。方法解析子句(method = interface.method)在多个接口声明相同方法时解决冲突。使用 'as' 或 Supports() 在运行时查询接口。
type
IReadable = interface
function Read: string;
end;
IWritable = interface
procedure Write(const S: string);
end;
IStream = interface(IReadable, IWritable)
procedure Flush;
end;
TFileStream = class(TInterfacedObject, IStream, IReadable, IWritable)
// must implement all methods from all interfaces
end;Supports 和 as 转换
Supports() 检查对象是否实现接口 —— 返回布尔值,可选返回接口。'as' 转换做同样的事但在失败时引发 EInvalidCast。Supports() 适用于对象和接口引用。要求接口有 GUID。
uses
System.SysUtils, System.TypInfo;
var
Obj: TObject;
Shape: IShape;
begin
Obj := TCircle.Create(5.0);
try
if Supports(Obj, IShape, Shape) then
Writeln(Shape.GetArea:0:2);
// 'as' cast — raises if not supported
Shape := Obj as IShape;
// type info
if Supports(Obj, IShape) then
Writeln('Obj supports IShape');
finally
Obj.Free; // object reference — must free manually
end;
end;依赖注入模式
将依赖项作为接口传递 —— 启用模拟、替换实现和可测试性。类依赖抽象(ILogger),而非具体类型。这是 Spring4D 等 DI 容器的基础。接口所有权意味着记录器存活时间与服务持有引用一样长。
type
ILogger = interface
procedure Log(const Msg: string);
end;
TOrderService = class
private
FLogger: ILogger;
public
constructor Create(ALogger: ILogger);
procedure ProcessOrder(OrderId: Integer);
end;
constructor TOrderService.Create(ALogger: ILogger);
begin
FLogger := ALogger; // injected dependency
end;
procedure TOrderService.ProcessOrder(OrderId: Integer);
begin
FLogger.Log('Processing order ' + OrderId.ToString);
end;内存管理进阶
try-finally 模式
始终在 try-finally 中将分配与 Free 配对。为多个资源嵌套 finally 块。FreeAndNil(代替 Free)还清除变量 —— 对检测 use-after-free 很有用。Free 对 nil 安全 —— 无需先检查 Assigned。
var
List: TObjectList;
Stream: TFileStream;
begin
List := TObjectList.Create;
try
Stream := TFileStream.Create('data.bin', fmOpenRead);
try
// ... use Stream ...
finally
Stream.Free;
end;
finally
List.Free;
end;
end;基于接口的所有权
TInterfacedObject + 接口引用 = 自动清理。当接口离开作用域时,析构函数运行。这是 Delphi 中的 RAII —— 将资源包装在接口对象中实现保证清理而无需 try-finally 样板代码。
type
TTempFile = class(TInterfacedObject)
private
FName: string;
public
constructor Create(const AName: string);
destructor Destroy; override;
end;
destructor TTempFile.Destroy;
begin
if FileExists(FName) then
DeleteFile(FName);
inherited;
end;
// usage:
var
Temp: TTempFile;
begin
Temp := TTempFile.Create('tmp.txt');
// ... use file ...
end; // Temp freed automatically (refcount)弱引用
弱引用打破引用循环。没有 [Weak],两个互相持有接口引用的对象将永远不会被释放(循环)。TComponent 有内置的 FreeNotification 机制用于弱引用。[Weak] 特性需要 RTTI 并适用于接口和类字段。
type
[Weak]
FParent: TComponent; // weak reference — no refcount increment
// or for interfaces:
[Weak]
FLogger: ILogger;
// TComponent uses notification-based weak refs:
type
TChild = class(TComponent)
private
FParent: TComponent;
public
property Parent: TComponent read FParent write FParent;
end;记录与对象
记录是值类型(栈,赋值时复制)—— 无需内存管理。类是引用类型(堆,必须释放)。对小的不可变数据(点、日期、金额)使用记录。对多态或大对象使用类。现代 Delphi 中记录可以有方法和运算符。
type
TPoint = record // value type — stack allocated
X, Y: Double;
function Distance: Double;
end;
TPointObj = class // reference type — heap allocated
X, Y: Double;
function Distance: Double;
end;
var
P1, P2: TPoint;
O1, O2: TPointObj;
begin
P1 := P2; // copies values
O1 := O2; // copies reference (both point to same object)
end;内存泄漏检测
ReportMemoryLeaksOnShutdown 在退出时显示列出泄漏对象的对话框。FastMM(默认内存管理器)检测泄漏、双重释放和 use-after-free 。对于生产,将泄漏记录到文件。在开发期间定期运行泄漏检查 —— 在引入时修复泄漏更容易。
uses
System.ReportMemoryLeaksOnShutdown;
begin
ReportMemoryLeaksOnShutdown := True;
// ... your code ...
// on app exit, leak report shown if any unfreed objects
end;
// FastMM4 (built into modern Delphi):
// - Detects leaks with call stack
// - Reports type and count of leaked objects
// - Use FullDebugMode for detailed diagnostics
// Manual check:
var
StartMem: Integer;
begin
StartMem := AllocMemSize;
// ... code under test ...
if AllocMemSize > StartMem then
Writeln('Memory leak detected');
end;FireMonkey (FMX)
跨平台窗体基础
FMX 窗体是跨平台的(Windows、macOS、iOS、Android、Linux)。相同代码,不同的原生渲染器。使用 FMX.* 单元代替 Vcl.*。控件是基于矢量的(完美缩放)。样式取代主题 —— 视觉外观是数据驱动的。
unit MainForm;
interface
uses
System.SysUtils, System.Types, FMX.Forms, FMX.Controls,
FMX.Controls.Presentation, FMX.Edit, FMX.Buttons;
type
TFormMain = class(TForm)
EditName: TEdit;
ButtonSubmit: TSpeedButton;
procedure ButtonSubmitClick(Sender: TObject);
private
FName: string;
public
property Name: string read FName;
end;
var
FormMain: TFormMain;
implementation
procedure TFormMain.ButtonSubmitClick(Sender: TObject);
begin
FName := EditName.Text;
Close;
end;
end.布局与对齐
FMX 使用 Align(Client、Top、Bottom、Left、Right、None)和 Margins/Padding 进行布局。TFlowLayout 像 CSS flexbox 一样排列子控件。TGridLayout 创建网格。使用 TScaleBox 实现分辨率无关的缩放。布局本身是控件 —— 可嵌套。
// Layout types: TLayout, TFlowLayout, TGridLayout, TScrollBox
var
Layout: TFlowLayout;
Btn: TButton;
begin
Layout := TFlowLayout.Create(Self);
Layout.Parent := Self;
Layout.Align := TAlignLayout.Client;
Layout.FlowDirection := TFlowDirection.LeftToRight;
Layout.Justify := TJustifyMode.SpaceBetween;
for var I := 1 to 5 do
begin
Btn := TButton.Create(Self);
Btn.Parent := Layout;
Btn.Text := 'Button ' + I.ToString;
Btn.Margins.Rect := RectF(5, 5, 5, 5);
end;
end;样式与样式化
样式是存储在 .fsf 或 .style 文件中的视觉资源(画刷、字体、效果)集合。StyleLookup 为控件选择命名样式。TStyleManager 在运行时切换全局样式。FMX 样式是矢量的 —— 缩放到任何 DPI。样式设计器可视化编辑样式。
// Load a custom style
begin
TStyleManager.LoadFromFile('Dark.fsf');
TStyleManager.TrySetStyleFromResource('DarkStyle');
end;
// Apply style to a single control:
Button1.StyleLookup := 'cornerbutton';
// Read style in code:
var
StyleObj: TFmxObject;
begin
StyleObj := TStyleManager.ActiveStyle(Self).FindStyleResource('buttonstyle');
end;
// LiveBindings designer for visual data binding
// Tools > LiveBindings Designer效果与动画
效果(Glow、Shadow、Blur、Reflection)是父级为控件的非可视组件。动画(TFloatAnimation、TColorAnimation、TPathAnimation)随时间动画化属性。将 Parent 设置为目标控件。Trigger/Start 开始。全部 GPU 加速 —— 在所有平台上平滑。
uses
FMX.Effects, FMX.Ani;
var
Glow: TGlowEffect;
Ani: TFloatAnimation;
begin
// Glow effect on a button
Glow := TGlowEffect.Create(Button1);
Glow.Parent := Button1;
Glow.GlowColor := TAlphaColors.Blue;
Glow.Enabled := True;
// Animate opacity
Ani := TFloatAnimation.Create(Button1);
Ani.Parent := Button1;
Ani.PropertyName := 'Opacity';
Ani.StartValue := 0;
Ani.EndValue := 1;
Ani.Duration := 0.5;
Ani.Start;
end;平台服务
平台服务抽象 OS 特定功能。用 SupportsPlatformService 查询 —— 在不支持的平台上返回 false。使用前始终检查。常见服务:剪贴板、对话框、虚拟键盘、设备信息、屏幕。此模式使你的代码跨平台而无需 {$IFDEF} 块。
uses
FMX.Platform;
var
ScreenSvc: IFMXScreenService;
Size: TPoint;
begin
if TPlatformServices.Current.SupportsPlatformService(
IFMXScreenService, IInterface(ScreenSvc)) then
begin
Size := ScreenSvc.GetScreenSize;
Writeln(Size.X.ToString, 'x', Size.Y.ToString);
end;
end;
// Other services:
// IFMXClipboardService
// IFMXDialogService (async message boxes)
// IFMXVirtualKeyboardService
// IFMXDeviceService数据库(FireDAC)
连接设置
TFDConnection 是中央 FireDAC 对象。设置 DriverName(SQLite、MSSQL、MySQL、PostgreSQL、Oracle 等)和 Params。连接定义可存储在 .ini 文件中以便重用。释放前始终设置 Connected := False。使用 TFDManager 进行连 接池。
uses
FireDAC.Comp.Client, FireDAC.Stan.Def;
var
FDConn: TFDConnection;
begin
FDConn := TFDConnection.Create(nil);
try
FDConn.DriverName := 'SQLite';
FDConn.Params.Database := 'app.db';
FDConn.Params.Add('Encrypt=AES-256');
FDConn.Params.Password := 'secret';
FDConn.Connected := True;
// or use a connection definition file:
// FDConn.ConnectionDefName := 'MySQLite';
finally
FDConn.Free;
end;
end;查询执行
对 SELECT 使用 Open(返回游标),对 INSERT/UPDATE/DELETE 使用 ExecSQL(返回受影响行数)。始终使用参数 —— 切勿将值连接到 SQL 中(注入风险)。ParamByName 不区分大小写。FieldByName 按名称访问列。Eof/Next 迭代行。
var
Query: TFDQuery;
begin
Query := TFDQuery.Create(nil);
try
Query.Connection := FDConn;
// parameterized query (prevents SQL injection)
Query.SQL.Text := 'SELECT * FROM users WHERE age > :min_age';
Query.ParamByName('min_age').AsInteger := 18;
Query.Open;
while not Query.Eof do
begin
Writeln(Query.FieldByName('name').AsString);
Query.Next;
end;
// execute non-query (INSERT/UPDATE/DELETE)
Query.SQL.Text := 'INSERT INTO users (name, age) VALUES (:n, :a)';
Query.ParamByName('n').AsString := 'Alice';
Query.ParamByName('a').AsInteger := 30;
Query.ExecSQL;
finally
Query.Free;
end;
end;事务
StartTransaction/Commit/Rollback 包装原子操作。如果任何语句失败,Rollback 撤销所有更改。嵌套事务使用保存点(部分回滚)。始终包装在 try-except-raise 中以在回滚后传播错误。没有事务时,每个语句自动提交。
FDConn.StartTransaction;
try
Query.SQL.Text := 'UPDATE accounts SET balance = balance - 100 WHERE id = 1';
Query.ExecSQL;
Query.SQL.Text := 'UPDATE accounts SET balance = balance + 100 WHERE id = 2';
Query.ExecSQL;
FDConn.Commit;
except
FDConn.Rollback;
raise;
end;
// nested transactions via savepoints:
FDConn.StartTransaction;
try
// ... work ...
FDConn.StartTransaction; // savepoint
try
// ... more work ...
FDConn.Commit;
except
FDConn.Rollback; // rolls back to savepoint
end;
finally
FDConn.Commit;
end;TFDTable 与实时数据
TFDTable 是表上的实时、可编辑游标。Edit/Post 修改当前行。Append/Post 插入。Delete 移除当前行。更改直接进入数据库。使用 IndexFieldNames 进行排序。对于复杂查询,改用 TFDQuery。
var
Table: TFDTable;
begin
Table := TFDTable.Create(nil);
try
Table.Connection := FDConn;
Table.TableName := 'users';
Table.IndexFieldNames := 'name'; // ORDER BY
Table.Open; // SELECT * FROM users
// edit current row
Table.Edit;
Table.FieldByName('age').AsInteger := 31;
Table.Post;
// insert new row
Table.Append;
Table.FieldByName('name').AsString := 'Bob';
Table.FieldByName('age').AsInteger := 25;
Table.Post;
// delete current row
Table.Delete;
finally
Table.Free;
end;
end;批量更新与缓存模式
CachedUpdates 模式在内存中缓冲更改 —— 用 ApplyUpdates 一次性应用。比逐行更新批量操作更快。CancelUpdates 丢弃缓冲。Status 显示每行的更改类型。适用于断开连接的场景和减少往返。
Query.CachedUpdates := True;
Query.Open;
// make many changes locally
while not Query.Eof do
begin
Query.Edit;
Query.FieldByName('status').AsString := 'processed';
Query.Post;
Query.Next;
end;
// apply all changes in one transaction
FDConn.StartTransaction;
try
Query.ApplyUpdates;
FDConn.Commit;
except
FDConn.Rollback;
Query.CancelUpdates;
raise;
end;
// inspect change log:
Query.Status; // TUpdateStatus (usModified, usInserted, usDeleted)REST 与 HTTP
TRESTClient 基础
TRESTClient 持有基础 URL。TRESTRequest 构建请求(方法、资源、参数)。TRESTResponse 持有结果。URL 段({id})由 AddUrlSegment 替换。StatusCode/Content 提供 HTTP 响应。按创建的相反顺序释放。
uses
REST.Client, REST.Types;
var
Client: TRESTClient;
Request: TRESTRequest;
Response: TRESTResponse;
begin
Client := TRESTClient.Create('https://api.example.com');
Request := TRESTRequest.Create(Client);
Response := TRESTResponse.Create(Client);
try
Request.Resource := '/users/{id}';
Request.Method := TRESTRequestMethod.rmGET;
Request.Params.AddUrlSegment('id', '42');
Request.Params.AddItem('fields', 'name,email', TRESTRequestParameterKind.pkGETorPOST);
Request.Execute;
if Response.StatusCode = 200 then
Writeln(Response.Content);
finally
Response.Free;
Request.Free;
Client.Free;
end;
end;JSON 解析
System.JSON 提供 TJSONObject、TJSONArray、TJSONValue。ParseJSONValue 解析字符串(返回 TJSONValue —— 按需转换)。GetValue<T> 读取类型化值。AddPair/AddElement 构建 JSON。所有 JSON 对象必须释放 —— 仅当由父级拥有时才引用计数。
uses
System.JSON;
var
JSON: TJSONObject;
Arr: TJSONArray;
Item: TJSONObject;
I: Integer;
begin
// parse
JSON := TJSONObject.ParseJSONValue('{"name":"Alice","age":30}') as TJSONObject;
try
Writeln(JSON.GetValue<string>('name'));
Writeln(JSON.GetValue<Integer>('age'));
finally
JSON.Free;
end;
// build
JSON := TJSONObject.Create;
try
JSON.AddPair('name', 'Bob');
JSON.AddPair('scores', TJSONArray.Create(90, 85, 92));
finally
JSON.Free;
end;
end;带 DataSnap 的 REST 服务器
DataSnap 自动将 Delphi 方法公开为 REST 端点。方法名成为 URL 段。参数映射到 URL 段或 POST 正文。TJSONObject/TJSONArray 是标准返回类型。应用如 [httppost] 的特性以指定 HTTP 动词。使用 TDSServerModule 作为基类。
// ServerContainerUnit1.pas
type
TServerMethods1 = class(TDSServerModule)
function GetUsers: TJSONArray;
function GetUser(id: Integer): TJSONObject;
[httppost] function CreateUser(Data: TJSONObject): TJSONObject;
end;
function TServerMethods1.GetUsers: TJSONArray;
begin
Result := TJSONArray.Create;
// ... populate from DB ...
end;
// Access via URL:
// GET http://localhost:8080/datasnap/rest/TServerMethods1/GetUsers
// GET http://localhost:8080/datasnap/rest/TServerMethods1/GetUser/42
// POST http://localhost:8080/datasnap/rest/TServerMethods1/CreateUser用于低级控制的 Indy HTTP
TIdHTTP(Indy)提供对 HTTP 的完全控制 —— 头、cookie、重定向、超时。比 TRESTClient 更冗长但更灵活。对于 HTTPS,分配 SSL IOHandler(TIdSSLIOHandlerSocketOpenSSL)。为生产设置 ReadTimeout/ConnectTimeout。Indy 是同步的 —— 包装在 TThread 中以实现异步。
uses
IdHTTP, IdGlobal;
var
Http: TIdHTTP;
Response: string;
PostData: TStringStream;
begin
Http := TIdHTTP.Create(nil);
try
Http.Request.ContentType := 'application/json';
Http.Request.CustomHeaders.AddValue('Authorization', 'Bearer token123');
// GET
Response := Http.Get('https://api.example.com/users');
// POST
PostData := TStringStream.Create('{"name":"Alice"}', TEncoding.UTF8);
try
Response := Http.Post('https://api.example.com/users', PostData);
finally
PostData.Free;
end;
Writeln(Http.ResponseCode); // 200, 404, etc.
finally
Http.Free;
end;
end;带任务的异步 HTTP
将 REST 调用包装在 TTask.Run 中以避免阻塞 UI 线程。用 TThread.Queue(异步)或 TThread.Synchronize(同步)封送 UI 更新回主线程。注意对象生命周期 —— 请求必须比任务存活更长。考虑 TRESTRequest.ExecuteAsync 获得内置异步支持。
uses
System.Threading, REST.Client;
var
Request: TRESTRequest;
begin
Request := TRESTRequest.Create(nil);
try
Request.Client := TRESTClient.Create('https://api.example.com/users');
Request.Client.Owner := Request;
TTask.Run(
procedure
begin
Request.Execute; // background thread
TThread.Queue(nil,
procedure
begin
// update UI on main thread
Memo1.Lines.Text := Request.Response.Content;
end);
end);
finally
// don't free Request here — task may still be running
end;
end;多线程(并行)
TThread 基础
子类化 TThread 并重写 Execute。Create(False) 立即启动;Create(True) 需要 .Start。FreeOnTerminate := True 自动释放 —— 切勿对此类线程调用 Free。定期检查 Terminated 以优雅关闭。切勿从 Execute 操作 UI —— 使用 Synchronize/Queue。
type
TWorker = class(TThread)
protected
procedure Execute; override;
public
constructor Create;
end;
procedure TWorker.Execute;
var
I: Integer;
begin
for I := 1 to 100 do
begin
if Terminated then Exit;
// ... work ...
Sleep(50);
end;
end;
constructor TWorker.Create;
begin
inherited Create(False); // False = start immediately
FreeOnTerminate := True; // auto-free on completion
end;TTask 与 future
来自 System.Threading 的 ITask/IFuture<T> 比 TThread 更高级。Future 返回类型化值 —— .Value 阻塞直到结果就绪。任务是引用计数的(无需手动 Free )。使用 TTask.WaitForAll / WaitForAny 协调多个任务。比原始 TThread 更易用。
uses
System.Threading;
var
Task: ITask;
Future: IFuture<string>;
begin
// fire-and-forget task
Task := TTask.Create(
procedure
begin
// ... background work ...
end);
Task.Start;
// future — returns a value
Future := TTask.Future<string>(
function: string
begin
Sleep(1000);
Result := 'computed value';
end);
// ... do other work ...
Writeln(Future.Value); // blocks until ready
end;并行 for 循环
TParallel.For 跨 CPU 核心并行运行循环迭代。必须同步共享状态(使用 TCriticalSection 或 TInterlocked)。迭代顺序是非确定性的。使用 TLoopState 实现 break/continue。对 CPU 密集型工作更快;对琐碎迭代因开销而更慢。
uses
System.Threading, System.SyncObjs;
var
Total: Integer;
Lock: TCriticalSection;
I: Integer;
begin
Lock := TCriticalSection.Create;
try
TParallel.For(1, 1000,
procedure(I: Integer)
begin
Lock.Enter;
try
Total := Total + ComputeExpensive(I);
finally
Lock.Leave;
end;
end);
finally
Lock.Free;
end;
end;同步原语
TCriticalSection:互斥(一次只有一个线程)。TEvent:线程间信号(SetEvent/WaitFor)。带手动复位的 TEvent 保持信号直到 Reset。TInterlocked.Increment 是原子的,比关键区对简单计数器更快。TMonitor(内置于 TObject)是另一个选项。
uses
System.SyncObjs;
var
Lock: TCriticalSection;
Event: TEvent;
Count: Integer;
begin
Lock := TCriticalSection.Create;
Event := TEvent.Create(nil, True, False, '');
try
TThread.CreateAnonymousThread(
procedure
begin
Lock.Enter;
try
Inc(Count);
finally
Lock.Leave;
end;
Event.SetEvent; // signal completion
end).Start;
Event.WaitFor(INFINITE); // wait for signal
finally
Lock.Free;
Event.Free;
end;
end;TThread.Queue 与 Synchronize
UI 控件只能从主线程操作。Synchronize 阻塞工作线程直到主线程执行匿名方法 —— 谨慎使用(导致串行化)。Queue 投递后继续 —— 适用于即发即忘的 UI 更新。传递 nil 作为线程参数以使用当前线程。
// From a worker thread, update UI safely:
// Synchronous — blocks worker until main thread runs the code
TThread.Synchronize(nil,
procedure
begin
Label1.Caption := 'Done';
end);
// Asynchronous — posts to main thread queue, doesn't block
TThread.Queue(nil,
procedure
begin
Label1.Caption := 'Progress: 50%';
end);
// TThread.Queue is preferred for non-critical updates
// Synchronize for cases where you need the result before continuing包与组件
包项目基础
包(.bpl)是带 Delphi 元数据的 DLL —— 在应用间共享代码。'requires' 列出依赖。'contains' 列出此包中的单元。设计时包将组件安装到 IDE 中;运行时包随应用发布。拆分设计/运行时以减少 IDE 膨胀。
// MyPackage.dpk
package MyPackage;
{$R *.res}
{$ALIGN 8}
{$ASSERTIONS ON}
{$DESIGNONLY MyDesignUnits} // design-time only
{$RUNONLY MyRuntimeUnits} // runtime only
requires
rtl,
vcl,
System.Generics.Collections;
contains
MyUnit1 in 'MyUnit1.pas',
MyUnit2 in 'MyUnit2.pas',
MyComponent in 'MyComponent.pas';
end.
// Build configurations:
// - Build (debug)
// - Release
// - Design-time (installs into IDE)
// - Runtime (deployed with app)自定义组件骨架
从最接近的现有类派生(TCustomLabel 提供没有 published 属性的标签)。仅重新发布你想要暴露的属性。Register 过程将组件添加到 IDE 面板。'default' 设置初始值(必须与构造函数匹配)。将 Register 放在设计时包中。
unit MyLabel;
interface
uses
Vcl.Controls, Vcl.Graphics, Vcl.StdCtrls;
type
TMyLabel = class(TCustomLabel)
private
FHighlightColor: TColor;
procedure SetHighlightColor(Value: TColor);
protected
procedure Paint; override;
public
constructor Create(AOwner: TComponent); override;
published
property HighlightColor: TColor
read FHighlightColor write SetHighlightColor default clYellow;
property Caption;
property Font;
property OnClick;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('MyPalette', [TMyLabel]);
end;组件属性与编辑器
TComponent 是非可视组件的基类。拥有子对象(FItems)—— 在构造函数中创建,在析构函数中释放。TStrings 属性获得内置字符串编辑器。RegisterPropertyEditor 为特定属性自定义对象检查器。对需要流式传输的嵌套对象使用 TPersistent。
type
TMyComponent = class(TComponent)
private
FItems: TStringList;
function GetItems: TStrings;
procedure SetItems(Value: TStrings);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Items: TStrings read GetItems write SetItems;
end;
constructor TMyComponent.Create(AOwner: TComponent);
begin
inherited;
FItems := TStringList.Create;
end;
destructor TMyComponent.Destroy;
begin
FItems.Free;
inherited;
end;
// Property editor for rich editing in Object Inspector:
// RegisterPropertyEditor(TypeInfo(TStrings), TMyComponent, 'Items',
// TStringListProperty);事件与方法指针
事件类型是带 'of object' 的过程类型 —— 它们同时持有对象引用和方法指针。调用前始终检查 Assigned() —— nil 事件引发 AV。Do* 方法(DoChange、DoClick)是触发事件的受保护分派器。子类可以重写 Do* 以拦截事件。
type
TMyEvent = procedure(Sender: TObject; Value: Integer) of object;
TMyComponent = class(TComponent)
private
FOnChange: TMyEvent;
protected
procedure DoChange(Value: Integer);
published
property OnChange: TMyEvent read FOnChange write FOnChange;
end;
procedure TMyComponent.DoChange(Value: Integer);
begin
if Assigned(FOnChange) then
FOnChange(Self, Value);
end;
// 'of object' makes it a method pointer — must be assigned to a method
// (e.g., Form1.Button1Click). Always check Assigned before calling.流式传输与持久化
TPersistent 启用流式传输和 Assign。Published 属性自动保存到 DFM 文件。重写 Assign 以支持对象间复制。DefineProperties 将非 published 数据添加到流。WriteComponent/ReadComponent 序列化到任何 TStream。这是窗体持久化其状态的方式。
type
TMySettings = class(TPersistent)
private
FTimeout: Integer;
FTitle: string;
published
property Timeout: Integer read FTimeout write FTimeout default 30;
property Title: string read FTitle write FTitle;
end;
// TPersistent enables streaming (DFM, RTTI):
// - Inherits from TPersistent (gives Assign)
// - Published properties are streamed
// - Override AssignTo/Assign for custom copy
// Save to DFM automatically:
// - Component owned by a form is streamed
// - Sub-properties (TPersistent) are nested in DFM
// - Use DefineProperties for non-standard data
// Manual streaming:
var
Stream: TFileStream;
begin
Stream := TFileStream.Create('settings.bin', fmCreate);
try
Stream.WriteComponent(MyComponent);
finally
Stream.Free;
end;
end;相关 Delphi 代码片段
Copy-paste ready code for common tasks.
这篇内容对您有帮助吗?