프로그램 구조 & 기본
프로그램 구조 & 유닛
Delphi 프로그램은 'program'으로 시작하여 'end.'(마침표)로 끝납니다. {$APPTYPE CONSOLE}은 콘솔 앱으로 표시하는 컴파일러 지시문입니다. 'uses'는 유닛(모듈)을 임포트합니다 — System.SysUtils에 Format, IntToStr 등이 있습니다. 유닛은 인터페이스 섹션(공개 선언)과 구현 섹션(코드)을 가집니다. 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(유니코드, 참조 카운트됨), 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 = 구분은 초보자 오류의 #1 원인입니다.
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(16진수), %m(통화), 너비/정밀도 수정자를 사용합니다. WriteLn(value:width:decimals)은 부동소수점을 직접 포맷합니다. ReadLn은 변수로 입력을 읽습니다. StrToInt/StrToFloat는 문자열을 숫자로 변환합니다(실패 시 EConvertError 발생); TryStrToInt는 부울을 반환하며 더 안전합니다. 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의 모듈입니다. 인터페이스 섹션은 공개된 것을 선언하고(사용자에게 보임); 구현은 코드를 포함하며 private 타입/변수를 가질 수 있습니다. initialization/finalization 섹션은 유닛 로드/언로드 시 실행됩니다(유닛의 생성자/소멸자처럼). 인터페이스에 선언된 변수는 전역이고; 구현에서는 유닛 private입니다. 인터페이스의 타입은 공개이고; 구현에서는 private입니다. 이 두 섹션 설계는 유닛 수준에서 캡슐화를 강제합니다. '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를 사용하세요. 이산 값에 대해 연결된 if-else보다 Case가 더 깔끔합니다. 문자열에 직접 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는 다음 반복으로 건너뜁니다. 내장된 step은 없습니다 — 조건문이나 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가 필요 없습니다(본질적으로 블록). '0회 이상'에는 while을, '1회 이상'에는 repeat을 사용하세요. Break는 종료하고; Continue는 테스트로 건너뜁니다. 복잡한 종료 조건이 있는 루프에는 Break와 함께 while True가 일반적인 관용구입니다.
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 프로그래머를 위한 일반적인 버그 원인. Length()는 문자 수를 반환합니다. Pos()는 부분 문자열을 찾습니다(없으면 0 반환, -1이 아님). Copy()는 부분 문자열을 추출합니다(시작, 개수). 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(16진수), %m(통화), 너비/정밀도 수정자. FloatToStrF는 정밀한 제어를 제공합니다(ffFixed, ffCurrency, ffNumber, ffExponent). FormatDateTime은 날짜를 포맷합니다: yyyy(4자리 연도), mm(월), dd(일), hh(시), nn(분), ss(초), dddd(전체 요일 이름), mmmm(전체 월 이름). StrToInt/StrToFloat는 잘못된 입력 시 EConvertError를 발생시키고; TryStrToInt는 부울을 반환합니다(더 안전). 사용자 입력에는 항상 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는 문자열(S[1])과 달리 0-인덱스입니다(SL[0]). 항상 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 연산 & 인코딩
Char는 2바이트 유니코드 문자입니다. 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는 괄호로 부분을 캡처합니다 — Groups[1], Groups[2]로 접근(1-인덱스). Replace는 일치를 대체합니다($1, $2는 그룹 참조). Split은 패턴에서 분할합니다. 일반적인 정규식: \d(숫자), \w(단어 문자), \s(공백), +(1개 이상), *(0개 이상), {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), 검증을 위한 Char 집합(['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는 부울과 찾은 인덱스를 반환합니다 — 배열이 먼저 정렬되어야 합니다. 복잡한 검색의 경우 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가 소멸자입니다(항상 override; Free에 의해 호출). 'inherited'는 기본 클래스의 메서드를 호출합니다. 필드는 관례상 F 접두사를 사용합니다. 속성(property X: Type read GetX write SetX)은 제어된 접근을 제공합니다 — 호출자는 P.Age를 사용하지만 setter가 검증합니다. 가시성: private(이전 Delphi에서는 유닛 전용; strict private가 진정한 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.Items[i] 대신 L[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)는 구현이 없습니다 — 하위 클래스가 반드시 override해야 함. 이것은 모든 도형이 Area/Perimeter를 제공하도록 강제. 클래스 메서드(class function/procedure)는 인스턴스가 필요 없음 — TShape.ShapeCount로 호출. 클래스 변수(class var)는 모든 인스턴스에서 공유. 템플릿 메서드 패턴: TShape.Describe는 추상 Area/Perimeter를 호출, 하위 클래스가 채움. 추상 메서드는 'what'을 정의; 하위 클래스는 'how'를 정의.
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 구현). 인터페이스는 QueryInterface/as 캐스트를 위한 GUID를 가질 수 있습니다. TInterfacedObject는 참조 카운팅을 제공 — 마지막 인터페이스 참조가 범위를 벗어나면 객체가 자동으로 해제됩니다(Free 호출 금지!). 디커플링에 인터페이스를 사용: 코드는 TButton이 아닌 IDrawable에 의존. '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나 생성자 매개변수를 통해 원본을 보존하세요. 사용자 정의 예외는 호출자가 특정 오류 타입을 잡을 수 있게 합니다: ERangeError와 별도로 ETransactionError를 잡으세요. 항상 의미 있는 메시지를 포함. 일반적인 내장: 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는 조건을 검사하고 거짓이면 EAssertionFailed를 발생 — 불변식(항상 참이어야 하는 조건)에 사용. 어설션은 {$C-}로 비활성화(또는 release 빌드에서 제거) — 입력 검증에 사용하지 마세요(예외 사용). 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) 리소스 보호 — Create/Free를 항상 try/finally로 감싸서 예외 발생 시 객체가 해제되도록(이것이 가장 중요한 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 enum으로 필터링(예: 프로덕션에서 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는 쓰기용 열기. 큰 파일의 경우 LoadFromFile(전체 파일 로드) 대신 StreamReader로 한 줄씩 읽으세요(낮은 메모리).
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는 크기 사용자 정의. goEditing(편집 가능 셀), goColSizing(열 크기 조정), goRowSelect 같은 옵션이 동작을 활성화. OnDrawCell은 Canvas로 사용자 정의 렌더링 허용. TDBGrid는 TDataSource를 통해 데이터셋(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 발생 — 메시지 기반이므로 차단 작업 중에 발생하지 않음. Application.ProcessMessages는 긴 작업 중 메시지 큐를 펌프('응답 없음' 방지)하지만 재진입 버그를 일으킬 수 있음. 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는 매개변수(:param 구문)로 SQL 실행 — 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 & 라이브 바인딩
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배 가속). 실패 시 Rollback하기 위해 항상 트랜잭션을 try/except로 감싸세요. 긴 트랜잭션의 경우 격리 수준(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로 행 추가. 인덱스, 필터, 모든 데이터셋 탐색 지원. Local SQL(TFDLocalSQL)은 모든 TDataSet(TFDMemTable, TClientDataSet, ODBC를 통한 Excel 포함)에 대해 SQL 쿼리 실행 — 메모리 테이블과 데이터베이스 테이블 간 조인 가능. 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를 인스턴스화할 수 있게 보장.