LazarusCompleteGuide:1.2

From 흡혈양파의 번역工房
Jump to: navigation, search

언어의 기본원리(basics)

프리 파스칼 컴파일러는 파스칼에서 쓰이는 오브젝트 파스칼을 위한 완전한 객체 지향의 컴파일러이다. 라자루스는 FPC 성능을 광범위하게 사용한다. 오브젝트 파스칼은 C 또는 C++에 비해 빠르고 효율적인 코드를 쓰는 데 적합한 동시 학습하기가 쉽고 소스 코드를 읽기가 수월하다. 특히 초보 프로그래머들에게 유익한데, 파스칼은 본래부터 프로그래밍 언어 교육용으로 설계되었기 때문이다.


물론 언어의 확장이 가능하지만 확장되더라도 기본 원칙은 여전히 적용된다. 오리지널 파스칼은 인라인 어셈블리 언어의 탑재를 허용하도록 확장되었고 (예: 어셈블리 언어 소스 코드가 파스칼 코드 내 직접 위치), 새 오브젝트와 클래스가 추가되었다.


프리 파스칼로 컴파일된 프로그램들은 C 라이브러리에 덜 의존적이어서 그 결과 그에 해당하는 C 프로그램보다는 리눅스나 BSD 배포판에서 더 많이 실행되는 것이 보통이다. 예를 들어, 리눅스 시스템용으로 컴파일된 프로그램은 SUSE, Red Hat, Ubuntu를 비롯해 여러 많은 배포판에서 재컴파일 없이 실행이 가능하다. 실행 범위는 사용되는 라이브러리에 따라 좌우된다. 이는 파일 조작과 같은 기본 기능을 제공하는 런타임 라이브러리(RTL)의 독립성으로부터 발생한다. 런타임 라이브러리는 가능하면 커널 함수(kernel function)만 활용하도록 프로그래밍 되며 이러한 커널 함수들은 거의 수정되지 않는다.


C 혹은 C++에서와 마찬가지로 프리 파스칼 컴파일러의 최근 출시버전으로 변경 시 재컴파일이 필수다.


프리 파스칼 컴파일러의 표준 버전에는 내부 어셈블러가 함께 따라오지만 원한다면 다른 어셈블러를 사용할 수도 있다. 다양한 어셈블러를 통합 가능하며, 그 중 가장 중요한 것은 GNU as 어셈블러이다.

컴파일러는 소스 코드에서 검색한 모든 어셈블리어 명령어를 원하는 출력 포맷으로 해석한다: GNU as, NASM 또는 Watcom 어셈블러. 또한 어셈블러로 전송하기 전에 구분된 파일에 어셈블리어 코드를 쓰고 편집하는 것이 가능하다. 2.2.0 버전 이상은 윈도우에서 FPC를 실행 시 고유의 링커가 있으며, 훨씬 빠르게 작업하고 스마트한 결합(smart linking)으로 파스칼 코드를 더 잘 처리한다.


리눅스나 BSD에서 기본 값은 GNU나 C++ 컴파일러에서와 마찬가지로 ld 이다. FPC 내장 링커도 리눅스나 BSD에서 사용 가능한지의 여부 혹은 언제 사용 가능한지가 알려져 있지 않다.


MacOS X는 애플사 자체 링커를 사용한다. 애플사에서 MacOS X 10.6 이상 버전에 제공하는 새 링커는 dwarf 디버깅 양식을 더 잘 처리한다. 이에 따라 MacOS X 10.5 버전에서는 FPC가 이 포맷을 이용하도록 명령하기 위해 -gw 옵션을 호출해야 한다.


컴파일러는 GNU gdb 디버거용 디버깅 데이터를 생성한다. 이는 라자루스 IDE가 배경에서 사용하는 명령 행 도구이다. gcc는 해당 데이터를 공급하므로 복합 언어 프로그램의 디버깅도 어느 정도 가능하다. 하지만 라자루스 디버거는 C 소스 코드 파일을 처리할 수 없다.


GNU gprof 프로파일러는 모든 플랫폼에서 사용할 수는 있지만 멀티스레드 프로그램으로는 작업할 수 없고 윈도우에서 설치가 까다롭다. valgrind 프로파일러 또한 리눅스, BSD, MacOS X에서 사용이 가능하다.


프리 파스칼 컴파일러는 사용자 친화적 컴파일러의 전통을 이어오며, 컴파일러에 충분한 내재적 지능이 있으므로 개발자의 도움 없이 서로 다른 코드 세그먼트들을 연결할 수 있기 때문에 make 파일 또는 그와 관련된 도구가 불필요하다. 이는 모두 재사용이 가능한 소스 코드인 유닛의 개념으로 인해 가능해졌다. 컴파일된 다양한 유닛을 어디서 검색할 것인지 개발자가 컴파일러로 알려주기만 하면 나머지는 컴파일러가 전부 알아서 처리한다.

그림 1.8: 일자와 시간을 출력하는 작은 프로그램이 있는 IDE


유닛 개념은 명칭 공간 또한 지원하는데, 여러 유닛에 동일한 식별자를 (함수, 변수 또는 타입 이름) 재사용할 수 있음을 나타낸다. 애매한 경우에는 식별자나 유닛이 동일한 이름을 가지는 경우를 (물론 정상적인 실습에서는 절대적으로 피해야 하는) 제외하곤 어떠한 식별자가 유효한지 컴파일러 스스로 인지한다. 컴파일러와 RTL은 오브젝트 파스칼에서 쓰이므로 대개 C 라이브러리에서 독립적이다. 생성된 코드는 시스템 코어 함수(core function)에 직접 호출하기 때문에 사용자는 외부 라이브러리로 연결을 피하는 코드를 쉽게 쓸 수 있다. 따라서 FPC로 생성된 프로그램을 많은 다른 버전의 운영체제나 특정 운영체제의 배포판에서 실행시킬 수 있다. 이를 위해선 라이브러리 인터페이스의 오브젝트 파스칼 해석을 제공하는 유닛을 씀으로써 컴파일러가 라이브러리를 사용할 때 필요로 하는 모든 데이터로 접근할 수 있도록 만드는 작업만 요한다. 프리 파스칼에는 자동으로 이러한 해석을 실행하는 h2pas 명령행 도구가 제공된다.


C 라이브러리를 비롯해 기존의 많은 C 도구들을 사용할 수 있다. 예를 들어 생성된 코드는 GNU 도구 등 즉시 이용 가능한 도구들을 이용해 디버깅할 수 있다. GNU 어셈블러와 링커로 실행 가능한 파일을 생성 가능하며, 디버깅에는 (라자루스에서와 마찬가지로) gdb를 이용할 수 있다. 프로파일링(Profiling)은 gprof 또는 valgrind로 실행된다. 컴파일러는 20개 이상의 플랫폼에서 실행된다. 플랫폼 독립성에 특히 주의를 기울이므로 FPC 프로그램에 자주 사용되는 ./configure 호출을 꼭 필요로 하진 않는다. 런타임 라이브러리는 커널 함수만 기반으로 하기 때문에 FPC 프로그램은 재컴파일 할 필요 없이 서로 다른 배포판에서 실행된다.


기본 타입

C와 마찬가지로 오브젝트 파스칼에도 기본 타입 집합이 있다. 표 1.1는 파스칼 정수 타입과 C에서 그에 상응하는 데이터 타입을 열거한다.


표 1.1: 오브젝트 파스칼과 C의 정수타입

오브젝트 파스칼 타입 C언어에서 사용되는 타입
Integer int
Cardinal unsigned int
ShortInt char
Byt eunsigned char
Word 16비트 unsigned int
LongWord unsigned int
Int64 long int
Qword unsigned long int


표 1.2: 오브젝트 파스칼에서 이용 가능한 부동소수점 타입

부동소수점 타입 길이(바이트)
single 4
double 8
extended 10 (지원시에만10, 미지원시 Double과 동일)
comp 8
currency 8


오브젝트 파스칼에서는 문자열 타입 또한 다양하다.


표 1.3: 오브젝트 파스칼에서 이용 가능한 문자열 타입

문자열 타입 특징
char 1바이트 단일 문자
Widechar 2바이트 단일 문자
ShortString 1 바이트 문자의 문자열. 최대 문자열 길이는 255자이다. 문자열 길이는 0번째 문자에 저장된다. 메모리는 스택에 예약된다.
AnsiString 1 바이트 문자의 문자열. 짧은 문자열(Shortstring)과 반대로 길이 제한이 없다.

힙(heap)으로부터 메모리가 요청된다. Indexed 타입.

WideString 2 바이트 문자의 문자열 (DBCS). 길이 제한이 없다. 힙으로부터 메모리가 요청된다.
Resourcestring AnsiString과 비슷한 리소스 문자열으로 소스 코드에서 정의되지만 리소스 파일로부터 로딩되므로 다른 언어로 해석 가능함을 의미한다.


오브젝트 파스칼에서는 다른 형태의 데이터 타입도 이용 가능하다.


표 1.4: 기타 데이터 타입

타입 설명
Pchar 문자에 대한 포인터
Pointer 타입이 정의되지 않은 포인터
Boolean 2개의 값이 가능: True 또는 False
Longbool 내부 크기가 4 바이트인 부울값(Boolean value)
Shortbool 내부 크기가 1 바이트인 부울값(Boolean value)


프리 파스칼과 라자루스에서 제공하는 Ctypes 유닛은 가장 자주 사용되는 C 타입에 대한 타입 정의 집합을 포함한다.

program CDemo1;
uses  CTypes;
  var  i: cint;                   // C에서 "int i;" 에 해당
       j: cint16;                 // C에서 "int16 j;" 에 해당
       k: cunsigned;              // C에서 "unsigned k;" 또는 "unsigned int k;"
       l: cushort;                // C에서 "unsigned short l;"
       m: cfloat;                 // C에서 "float m;"
begin
  i := 3;
end.


오브젝트 파스칼에는 기본 타입에 더해 열거, 범위, 세트, 레코드, 클래스 타입도 제공된다. 이는 타입 선언 블록에서 타입으로 정의되어야 한다. 이러한 블록은 type 키워드 뒤에 세미콜론으로 구분된 하나 또는 이상의 타입 정의가 붙는다.


예를 들자면 다음과 같다.

type
  TWeekDay = (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday);


위는 열거 타입을 정의한다. 범위(range) 타입은 다음과 같이 정의된다:

TDice = 1..6;


레코드 타입의 정의는 다음 형태를 취한다:

TPoint = record
  X : Integer;
  Y : Integer;
end;


파스칼에서는 변수들이 있는 레코드를 가변 레코드(variant record)라고 부른다. 가변 레코드에는 두 가지 타입이 있다:

MyVariant = record
  A: Integer;
  case Integer of
    1 : (B   : ShortString);
    2 : (C, D: ShortString;);
end;


레코드의 가변 부분은 case 식별자 뒤에 타입을 붙인 형태이다. 그 뒤에는 각 가변부에 대한 값이 따라오는데, 주로 연속 번호와 괄호 내에 위치하는 정의가 해당된다. 필드명을 명시함으로써 각 번호에 대한 메모리를 예약하는 것도 가능하다:

MyVariant = record
  A: Integer;
  case E: Integer of
    1 : (B   : String);
    2 : (C, D: String;);
end;


여기서 주목해야 할 점은 숫자들은 전적으로 가독성을 위해 제공된다는 점이다; 컴파일러는 숫자를 무시한다.


아래 집합 타입의 정의를 예를 들어보겠다:

TDays = set of TWeekDay; // 위의 TWeekDay 선언을 참고하라

또는

Throws = set of TDice;  // 위의 TDice 선언을 참고하라


타입이 지정된 포인터는 아래 예문에서와 같이 특정 타입의 변수를 가리킨다:

PInteger = ^Integer;


이는 C에서 다음 정의에 해당한다.

PInteger = int *;


파스칼 언어는 대-소문자에 민감한 언어가 아니기 때문에 오브젝트 파스칼에서 대-소문자는 중요하지 않다. 다시 말해 아래 정의들은 모두 동일하다:

PInteger = ^Integer;
Pinteger = ^Integer;
pInteger = ^Integer;
pinteger = ^Integer;

이는 식별자를 대-소문자를 달리하여 두 번 정의하는 경우 오류가 발생할 수 있음을 의미한다. 아래 명령어는 'Duplicate identifier' 오류를 야기할 수 있다:

type
  MyInteger = ^Integer;
  MYINTEGER = ^Integer;
end;


파스칼에서는 모든 식별자로 접근하기 전에 선언이 먼저 되어야 한다. 이는 확실히 구조화된 코드를 생성시키고 파스칼 컴파일러가 한 번 만에 소스 코드를 컴파일할 수 있다는 장점이 있는데, 이것은 모든 파스칼 컴파일러가 그토록 빠른 한 가지 이유이다. C와 C++의 선언 규칙은 상당히 느리기 때문에 C 컴파일러들은 훨씬 속도가 느리다.


다음은 연결된 목록의 요소를 포함하는 레코드 정의의 예이다. 가장 단순한 정의는 다음과 같은 형태를 띨 것이다:

TData = record
  TheData: Integer;
  Next   : ^TData;
end;

Next 필드는 바로 다음의 TData 레코드를 가리키는 포인터이다. 그러나 TDataNext 필드가 선언되는 지점에서 완전한 상태로 정의되어 있지 않다.

그림 1.9: C 코드가 파스칼로 바로 변환되기도 한다.


본 예제는 소스 코드에서 추후 완성되는 partial 선언인 forward declaration 의 핵심을 보여준다. 전방 선언(forward declaration)은 포인터 타입, 클래스 타입, 함수에 사용 가능하다. 포인터 타입에 대한 전방 선언은 다음과 같은 형태를 취한다:

PData = ^TData;
TData = record
  TheData: Integer;
  Next   : PData;
end;

여기서 TData 레코드의 Next 필드는 PData 타입에 해당한다. 이는 TData라는 이름의 타입에 대한 포인터로 정의되어 있다. 이 시점에서 TData 는 아직 정의되지 않았으므로 해당 타입의 정의는 forward 정의가 된다. 이러한 유형의 forward 정의는 항시 참조를 포함하는 type 블록에서 완성되어야 한다.


클래스 타입의 경우에서도 비슷한 상황이 존재한다:

TFirstClass  = class;     // 타입선언(전방선언)
TSecondClass = class
  First: TFirstClass;
end;

TFirstClass = class       // 실제선언
  Second: TSecondClass
end;


TFirstClassTSecondClass 타입의 필드가 있기 때문에 후자가 TFirstClass 보다 먼저 선언되어는 것이 맞다. 하지만 TFirstClassTSecondClass 보다 먼저 정의해야 하는 이유는 TSecondClass 자체에 TFirstClass 타입의 필드가 있기 때문이다. 이러한 문제를 해결하기 위해 위의 예에서 설명하였듯 TFirstClass에 전방 선언을 이용할 수 있겠다. TFirstClass를 먼저 단순히 클래스로서 정의한 후 전체 정의는 이후에 이루어지는 방법이다. 이러한 기법은 함수에도 적용된다. 함수를 선으로(forward) 선언할 수 있는데, 실제 구현이 이후에 이루어지더라도 차후 소스 코드에서 함수를 사용할 수 있음을 의미한다.


아래는 전방 선언의 예이다:

function Second: Boolean; forward;

function First : Integer;
begin
  Result := Ord(Second);
end;


Second 함수의 정의가 아직 완성되지 않았다 하더라도 First 함수의 구현에서 Second 함수를 이용할 수 있다. 물론 Second 구현은 First 구현 이후에 소스 코드에서 발생해야 하는데, 그렇지 않을 경우 컴파일러는 모든 전방 선언을 해결할 수 없기 때문에 오류를 보고할 것이다.


타입, 변수, 상수는 선언 블록에서 정의된다. C 혹은 C++와 달리 이러한 블록들은 소스 코드 내에 마음대로 위치시킬 수 없으며 대신 유닛이나 프로그램의 헤더 내에, 또는 함수나 프로시저 헤더 내에 위치해야 한다.


타입 선언 블록은 type 키워드로 시작해 하나 이상의 타입 선언이 뒤에 붙는다.


변수 선언 블록은 var 또는 ThreadVar 키워드로 시작해 하나 이상의 변수에 대한 선언이 뒤따른다.


다음은 유효한 변수 선언 블록의 예이다:

var  I: integer;

var  I: Integer;
     S: String;


상수 선언 블록은 const 키워드 다음에 하나 이상의 상수 정의가 붙는다. Resourcestring 키워드는 하나 이상의 문자열 상수 정의를 표시한다. 다음 예제에서 볼 수 있듯이 Resourcestring 키워드 다음에는 문자열 상수 정의만 따라올 수 있다.

const  MyInteger = 10;
       MyString  = 'A String';

Resourcestring   SOK     = 'OK';
                 SCancel = 'Cancel';


유닛의 구조와 작업(operation)

C나 C++에서 사용되는 헤더 파일 및 본체(body) 파일의 시스템과는 달리 오브젝트 파스칼은 Unit의 개념을 사용한다. Unit이란 개별화된 자체 포함의 소스 코드 파일을 의미한다. Unit은 두 개의 섹션으로 나뉜다:

  • interface 섹션: 이는 C 언어에서 헤더 파일에 해당하며, 타입, 상수, 변수, 함수에 대한 정의만 포함한다. interface 섹션에서 정의되는 모든 내용은 외부에서 접근이 가능하므로 다른 유닛에서 접근이 가능함을 의미한다.
  • implementation 섹션: 이는 C 언어에서 body 코드 파일에 해당하며, interface 섹션에서 정의된 함수와 프로시저의 구현을 포함한다. 해당 유닛에 접근하는 다른 유닛들은 이용할 수 없는 private 정의와 선언 또한 포함하고 있다.


최소한의 유닛 구조의 예를 들자면 다음과 같다:

unit unita;

interface

  procedure Hello;

implementation

  procedure Hello;
  begin
    Writeln('Hello, World');
  end;
end.


앞서 언급하였듯 하나 이상의 유닛에 대해 동일한 식별자를 정의할 수 있으므로 유닛이란 개념은 동일한 공간을 지원한다. 동일한 식별자를 가진 여러 유닛에 접근하는 소스 코드 세그먼트가 유닛 이름을 식별자 앞에 붙임으로써 둘을 구별할 수 있다. 표 1.5는 거의 모든 프로그램에서 사용되는 기본 루틴을 제공하는 표준 유닛 몇 가지를 열거한다.


표 1.5: 프리 파스칼에서 제공하는 기본 유닛

유닛이름 내용
System 표준 I/O루틴과 기본 변환함수
SysUtils 확장된 I/O, 다수의 변환루틴, 날자와 시간핸들러, 파일 시스템 및 예외(Exception)
Math 수학적 루틴
StrUtils 문자열 처리를 위한 루틴
DateUtils 날자와 시간 처리를 위한 루틴
Classes 기본 클래스와 스트리밍 시스템


C 언어에서 LibC 라이브러리는 이러한 기본 컴포넌트를 구성한다.


유닛으로 접근이 가능하게 하려면 다음과 같이 프로그램이나 유닛의 uses 절에 그 이름이 표시되어야 한다:

uses  SysUtils, Classes;


위의 문은 SysUtilsClasses 유닛을 결합한다. 이는 C에서 다음의 preprocessor 지시어에 해당한다:

#include <sysutils.h>
#include "Classes.h"


파스칼에서는 C 와 마찬가지로 #include 문에서 <>와 " "를 식별하지 않는다. 검색 경로의 모든 유닛이 검색되고 최종 프로그램으로 한 번만 결합된다. 이것이 프리 파스칼의 기본 원리다.


이러한 원리를 보여주는 완전한 예제를 소개하겠다:

unit unit1;
interface
  uses Math; // 해당유닛은 Math유닛 내에서 함수를 사용할 수 있지만
             // MiniProgram에 의해 자동으로 상속되지는 않는다
  var  FirstNumber: Integer;     // public 정의
implementation
  var  SecondNumber: integer;    // Private 정의 (unit1 내에서만 보인다)
end.


유닛의 프로그램 파일은 다음과 같다:

program MiniProgram;
uses Classes, SysUtils, unit1;
begin
  FirstNumber := 0;       // 허용: unit1이 uses 절에 열거된다
  unit1.FirstNumber := 1; // 위와 동일하나 식별자가 속한 유닛에 대해
                          // 명시적으로 명명(naming)
  SecondNumber := 3 ;     // 금지: SecondNumber는 unit1에서 private으로 정의
  Math.Min (1, 3);        // 금지: Math 유닛이 uses 절에 열거되지 않음
end.


앞서 언급하였듯 오브젝트 파스칼에서는 식별자 이름에서 대·소문자를 구분하지 않는다. 하지만 몇 가지 예외가 존재한다. 이는 대·소문자가 파일명에 중요한 파일 시스템에 적용되는데, 일반적으로 유닉스 또는 일부 MacOS 시스템이 해당된다.


그러한 시스템에서는 컴파일러가 먼저 uses 절에 쓰인 유닛 이름을 검색하고 소문자로 된 이름을 검색한 후 마지막으로 대문자로 된 이름을 검색한다. 이후에는 소문자로 모든 파일명을 쓰는 것이 통례가 되었다. 모든 FPC와 라자루스 유닛의 파일명도 소문자로 쓰이며 uses 절에 원하는 방식으로 쓸 수 있다. 이러한 이유로 새로운 유닛이 저장되면 IDE는 이름을 소문자로 설정할 것인지 묻고, 여기에 "Yes"로 답해야 한다. 해당 쿼리(query)는 Environment ⇒ Options ⇒ Environment ⇒ Naming 에서 이용 가능한데, 소문자로 자동 재명명(renaming)을 활성화시킬 수도 있다. 이를 제외하면 몇 가지 규칙만 존재한다. 이름을 할당할 때는 다음 규칙을 따라야 한다:

  • 타입은 항시 "T"로 시작한다;
  • 클래스의 private 필드는 "F"로 시작한다;
  • 속성에 대한 setter 메소드는 "Set"로 시작한다;
  • 속성에 대한 getter 메소드는 "Get"로 시작한다.


예를 들면 다음과 같다:

TAClass = class
private
  FName: String;
  FValue: Integer;
  function GetName: String;
  procedure SetValue(const AValue: Integer);
public
  property Value: integer read FValue write SetValue;
  property Name: String read GetName write FName;
end;


이러한 규칙이 의무는 아니지만 권할 만하다. CamelCase/Incaps 기호의 사용도 추천할 만한데, 여기서는 식별자의 첫 문자와 식별자에 속한 단어의 첫 문자들이 다음 예제에서와 같이 대문자화된다:

MyOwnVariable: Integer;
TSearchRec = record ... end;

라자루스에서의 객체 지향

이름에서 알 수 있듯이 오브젝트 파스칼로는 객체 지향의 코드를 생성할 수 있다. 객체 지향은 언어의 필수부분이지만 선택적이다. 객체 타입에는 두 가지가 있다: 오래된 타입과 (터보 파스칼 시대) 새로운 타입이 있는데, 새로운 타입은 클래스로 구성된다. 라자루스와 LCL은 클래스 개념에 따라 쓰이므로 아래에는 클래스 개념만 설명하도록 하겠다.


클래스는 하나의 타입이기 때문에 항시 타입 선언 블록에서 정의되어야 한다. C++언어와 달리 클래스 선언에서는 메소드만 선언하고, 실제 코드는 구분된 블록에서 구현된다. 클래스 선언은 여러 개의 섹션으로 구성된다:

  • Private 섹션: 해당 섹션 내 모든 식별자는 현재 유닛에서만 이용 가능하다.
  • Protected 섹션: 해당 섹션 내 모든 식별자는 현재 유닛과 그로부터 파생된 클래스(derived class)에서만 이용 가능하다.
  • Public 섹션: 해당 섹션 내 식별자는 전역적으로 이용 가능하다.
  • Published 섹션: public 섹션과 유사하지만 이 섹션에서는 컴파일러가 선언을 위해 런타임 타입 정보(RTTI)를 생성한다. 각 섹션은 변수(필드), 메소드, 클래스 메소드, 속성을 지닌다. 유효한 클래스 선언의 예를 들자면 다음과 같다:
TMyClass = class(TComponent)
private
  FMyInteger : Integer;
public
  property MyInteger: Integer Read FMyInteger Write FMyInteger;
end;


MyInteger의 속성 선언에서 Read 키워드 다음에 필드명이 붙는 경우 FMyInteger 필드에 직접 접근할 수 있음을 명심한다.


속성은 다음과 같이 메소드를 이용해 설정 또는 검색(retrieve)이 가능하다:

unit unit1;

interface
uses Classes;
type
  TMyClass = class(TComponent)
  private
    FMyInteger: Integer;
    Function GetMyInteger: Integer;
    Procedure SetMyInteger(const AValue: Integer);
  public
    Property MyInteger: Integer Read GetMyInteger Write SetMyInteger;
  end;

implementation
function TMyClass.GetMyInteger: Integer;
begin
  Result := FMyInteger;
end;

procedure TMyClass.SetMyInteger(const AValue: Integer);
begin
  FMyInteger := AValue;
end;


직접 필드 접근과 Getter와 setter 메소드를 혼합하는 것도 가능하다. 메소드는 단순히 정적 메소드일 (위의 예제와 같이) 수도 있고 아니면 virtual, abstract, class 메소드 중 하나로 정의할 수도 있다.


메소드 선언 뒤에 virtual 키워드를 위치시키면 메소드가 가상으로 정의된다.

function GetMyInteger: Integer; virtual;


메소드가 파생 클래스에 의해 구현될 경우 정의가 반복되어야 하며 virtual 키워드 대신 override로 대입해야 한다:

function GetMyInteger: Integer; override;


Override 키워드가 누락되면 메소드를 오버라이드 할 수 없게 된다.

그림 1.10: 클래스는 다음 대화창에서 표시할 수 있다: Source Editor, Object Inspector, Identifier Completion, Code Explorer


가상 메소드를 abstract로 선언할 수도 있다. 이러한 경우 클래스 내 메소드의 구현이 존재하지 않고, 파생 클래스가 메소드를 오버라이드하고 구현해야만 한다.


클래스가 추상 메소드를 이용해 구성된 경우 컴파일러는 경고를 발생시킨다.


비구현된 추상 메소드를 호출하면 런타임 오류가 발생한다.


아래는 추상 선언의 예제이다:

function GetMyInteger: Integer; virtual; abstract;


Class 메소드는 클래스의 인스턴스가 아니라 클래스에만 한정되는 메소드이다. 클래스의 구현은 클래스의 속성이나 필드에 접근할 수 없으며, 클래스의 어떠한 정규(regular) 메소드도 호출할 수 없다. 클래스 메소드는 선언 시작에 class 키워드를 위치시켜 선언한다:

class function InstanceSize: Cardinal;


클래스가 조상 클래스(ancestor class)로부터 파생된 경우, 클래스 선언은 부모 클래스의 이름만 포함시키면 된다:

TMyChildClass = class(TMyParentClass)
  function GetMyInteger: Integer; override;
end;


이번 예에서는, TMyChildClassTMyParentClass의 자식으로 선언되고 GetMyInteger 메소드는 오버라이드된다. C++와 다르게 오브젝트 파스칼에서는 다중 상속을 지원하지 않는다. 파생 클래스가 생성되면 하나 이상의 부모 클래스를 명시하는 것이 불가능하다. 하지만 오브젝트 파스칼은 interfaces 형태에서 제한된 유형의 다중 상속을 가진다. 모든 클래스는 하나 이상의 인터페이스를 구현할 수 있다. 그리고 인터페이스는 메소드 집합을 정의한다. 어떠한 소스 코드도 이러한 메소드들과는 연관이 없으며, 어떠한 변수 또는 필드 정의도 인터페이스에 표시될 수 없다. 아래는 유효한 인터페이스 정의의 예제이다:

IStreamPersist = interface ['{B8CD12A3-267A-11D4-83DA-00004F60B2DD}']
  procedure LoadFromStream(Stream: TStream);
  procedure SaveTostream(Stream: TStream);
end;


클래스가 해당 인터페이스를 구현하고자 하는 경우, Implementation 부분에 인터페이스명을 명시하고 Interface 에 정의된 각 메소드에 대한 구현을 제공해야만 한다. 다음 예제에서는 TPersistent에서 파생된 클래스가 정의되고 IStreamPersist 인터페이스가 구현된다:

TStreamPersistent = class(TPersistent, IStreamPersist)
protected
  procedure LoadFromStream(Stream: TStream);
  procedure SaveToStream(Stream: TStream);
end;


IStreamPersist 의 두 가지 메소드 모두 클래스의 구현부에 나타난다.


프리 파스칼의 배열과 C 배열의 비교

C 에서는 포인트를 이용해 배열이 생성되는데, 프리 파스칼에서는 아래와 같이 생성할 수 있다:

type
  PExample = ^Example;
var
  ExampleArray: ^Example;


여기서 Example 타입은 Byte 또는 Longint와 같이 원하는 타입으로 정할 수 있다.


첫 번째 행은 PExample 이라는 이름의 타입이 지정된(typed) 포인터를 정의하는데, C에서는 다음과 같이 정의된다:

typedef Example* PExample;


두 번째 행은 배열을 정의하고 있지만 여전히 예약해야 하는 메모리 공간을 필요로 한다. 이는 다음 호를 통해 실행할 수 있다:

GetMem(ExampleArray, Sizeof(Example) * Number);


GetMem은 메모리를 할당하고 C 언어에서 malloc과 동일한 방식으로 작용한다. C 언어에서와 같이 sizeof 함수는 타입의 사이즈를 바이트로 리턴한다. GetMem은 힙(heap)에 명시된 크기의 메모리 영역을 할당하고 ExampleArray에 포인터를 저장한다. ExampleArray가 이미 메모리 영역을 가리키고 있는 경우는 해제(release)되지 않는다. 호출에 실패할 경우 Out of memory 예외를 임의로 발생시키고 (throw) 새로운 영역이 초기화되지 않는다. 영역은 주로 프로세서에 가장 적합한 처리크기로 조정된다.


배열은 FillByte 함수로 초기화되어야 한다:

FillByte(ExampleArray^, SizeOf(Example) * Number, 0);


FillByte는 변수를 바이트 패턴으로 채운다. ExampleArray 포인터 자체보다는 포인터에서 참조하는 메모리 영역이 선언되어야 하기 때문에 ExampleArray^(탈자 기호가 붙음)를 전달할 필요가 있다. 역참조(dereferencing) 연산자가 누락된 경우 변수는 덮어 쓰일 것이며 가까이 있는 변수 또한 덮어 쓰이기 쉬운데 이는 FillByte가 속도는 빠르지만 메모리 보호를 전혀 하지 않기 때문이다.


GetMem으로 요청된 메모리는 그 뒤에 FreeMem으로 해제된다:

FreeMem(ExampleArray);


좀 더 간편한 방법은 메모리 영역을 요청, 해제, 확대, 축소할 수 있는 ReAllocMem 프로시저이다. 그 작업은 C 언어의 realloc과 비슷하다:

ExampleArray := NIL;   // NIL은 정의되지 않은 포인터나 객체에 대한 상수이다; C의 NULL과 비슷하다.

ReAllocMem(ExampleArray, SizeOf(Example) * number);


number에 대해 0이 전달되면 어떠한 메모리도 예약되지 않고 ExampleArray는 nil으로 남는다. 그렇지 않으면 해당 함수는 GetMem과 일치한다.

ReAllocMem(ExampleArray, SizeOf(Example), number_size);


메모리 영역을 확대할 필요가 있는경우 함수는 이후 메모리 영역이 남아 있는지 검사하고 힙(heap)만 적용시키면 된다. 여기까지는 매우 빠른 속도로 진행된다. 이용 가능한 추가 메모리가 충분하지 않은 경우 새 영역이 할당되고, 오래된 영역의 해제 및 ExampleArray가 조정되고 나면 이전 내용이 복사된다. 추가 메모리는 초기화되지 않는다. 새 영역의 크기가 더 작은 경우 더 작은 영역만 복사된다.


0 바이트가 요청되고 ExampleArray가 영역을 가리키는 경우 해당 영역이 해제되고 ExampleArray는 NIL로 설정된다.

ReAllocMem(ExampleArray, 0);


배열의 요소는 C 언어에서와 동일한 포인터 산술 연산을 이용해 접근할 수 있다:

Value := ExampleArray[3];    // 네번째 요소값을 읽는다.
Value := ExampleArray^;      // ^는 역참조 연산자이다.
                             // 이는 =ExampleArray[0]와 동일한 효과를 가진다


포인터를 요소로 인출하기 위해서는:

var
  Runner: PExample;
...
  Runner := @ExampleArray[2];          // @는 포인터를 3번째 요소로 리턴한다
  Value  := Runner^;                   // 3번째 요소값을 읽는다
  Runner := Runner + SizeOf(Example);  // 다음요소
  Inc(Runner);                         // 다음요소
                                       // Inc는 모든 타입에 대해 동일하게 작용한다
  Inc(Runner, 2);                      // 다음차례의 요소(next plus one)
  Dec(Runner);                         // 이전 요소; Inc와 반대


메모리 영역은 System 유닛의 Move 함수로 복사할 수 있다:

Move(ExampleArray[0], ExampleArray[10], SizeOf(Example) * 3);


이는 첫 3개의 값을 11-13번째 요소 값으로 복사한다.


고정된 크기의 배열도 정의할 수 있다. C 언어와 반대로 원하는 값에서 번호 붙이기(numbering)를 시작할 수 있다:

var FixedArray: array[1..3] of Integer;


FixedArray 배열은 고정된 크기의 요소 3개를 가지며, 첫 번째 요소는 position 1에 위치한다. 열거(Enumerated) 타입을 이용해서 배열 한계를 명시할 수도 있다:

  type TColor = (Red, Green, Blue);
  var  ColorArray: array[TColor] of integer;
begin
  ColorArray[Red] := 3;
end;


배열의 각 요소를 거치려면 다음과 같다:

  var Color: TColor;
      i    : Integer;
begin
  for Color := Low(TColor) to High(TColor) do
    WriteLn(ColorArray[Color]);
  for i := Low(FixedArray) to High(FixedArray) do
    WriteLn(FixedArray[i]);
end;


동적 배열은 포인터 배열의 확장이다. 동적 배열은 고유의 길이를 저장하는데, Length를 이용해 저장된 길이를 찾을 수 있다. 크기는 SetLength로 변경이 가능하다. 동적 배열에는 인덱스(indice)가 있어 자동으로 해제되고 빠르게 복사할 수 있다. 인덱스는 각 요소가 아니라 배열만 개체로 참조한다. SetLength는 필요 시 자동으로 초기화된다.

  var DynArray: array of Integer;
begin
  SetLength(DynArray, 3);
  for i := 0 to Length(DynArray) - 1 do WriteLn(DynArray[i]);
 // The compiler handles the release
end;


동적 배열이 함수로 전달되도록 하려면 하나의 타입으로 정의해야만 한다:

  type ArrayOfInteger = array of Integer;
Procedure Sum(a: ArrayOfInteger);


C 언어는 정의되지 않은 함수 인수(function argument)의 수를 생략 부호(...)로 명시할 수 있도록 허용한다. 오브젝트 파스칼에서는 이를 varargs 키워드를 이용해 구현할 수 있다.


표준 C 라이브러리에서 printf 함수는 생략 부호를 이용한다:

int printf(char * fmt, ...);


오브젝트 파스칼에서 이는 다음으로 결합된다.

function printf(Fmt: PChar); CDecl; varargs;


해당 구조는 cdecl 호출 규칙으로만 가능하다. 외부 함수 선언, 요소에 대한 변수 번호를 수용하는 오브젝트 파스칼 함수, 개방형 배열, 상수의 배열에도 유용하다. 개방형 배열은 모든 인수(argument)가 하나의 동일한 타입으로 되어 있을 때 가능하다. 이런 경우 함수는 다음과 같이 선언할 수 있다:

function DoSomething(const Fmt:String; Args:array of String):Integer;


주의: 위를 동적 배열과 혼동하지 말라!


위 배열의 요소는 다음 예제에서 볼 수 있듯이 Low(Args)High(Args)의 제한 값을 사전에 명시한 일반 배열에서와 동일한 방식으로 접근이 가능하다:

function DoSomething(const Fmt: String; Args: array of String):Integer;
  var i: Integer;
begin
  for i := Low(Args) to High(Args) do Write(Args[i]);
end;


다른 타입의 인수에 해당하는 변수 번호를 전달해야 하는 경우 상수형 배열이 (const 배열) 좋은 선택이다. Format 함수가 좋은 예가 되겠다:

function Format(const Fmt: String; Args: array of const): String;


Args 배열은 해당 함수에서 TVarRec의 배열로 내부에서 구현되는데, 마치 다음 예제에서 선언되는 것과 같다:

function Format(const Fmt: String; Args: array of TVarRec): String;


컴파일러는 함수가 호출되면 필요한 TVarRec record를 자동으로 빌드한다. 따라서 Format 함수의 다음 호출방법은 유효하다:

Format('There are %d entries', [12]);
Format('There are %d entries and %d errors', [12, 4]);
Format('There were %d errors, with message : %s', [3,'Error occurred']);


인수(Argument) 타입은 TVarRec 레코드의 vType 필드를 이용해 살펴볼 수 있다:

function ArrayofConst(Args : array of const): Integer;
  var  A: TVarRec;
       I: Integer; 
begin
  for I := Low(Args) to High(Args) do
  begin
    A := Args[i];
    case A.vType of
      vtInteger: WriteLn('Argument ', I, ' is an integer with value: ',A.vInteger);
      vBoolean: WriteLn('Argument ', I, ' is a boolean with value: ', A.VBoolean);
    end; { case }
  end; { for }
end; { function }


위의 예제에서는 가능한 값을 모두 살펴보는 것은 아니다. 이는 TVarRec 구조에 가능한 모든 값을 열거하는 라자루스 IDE 코드툴을 이용해 해결할 수 있다.


컴파일러 지시어

C 에서 사용되는 일부 컴파일러 지시어와 프로그램들은 FPC에서도 찾을 수 있다. 'pragma'라는 용어는 FPC에서 사용되지 않는데, C 에서 pragma라 불리는 것이 FPC에서는 컴파일러 지시어로 알려져 있다. 오브젝트 파스칼에 가장 중요한 지시어를 간략하게 설명하고자 한다. 이용 가능한 모든 지시어에 대한 상세한 설명은 본 저서와 동일한 출판사에서 낸 Michael Van Canneyt 의 Free Pascal V2 User's Guide를 참고한다 (ISBN 978-3-936546-43-8).


Include 파일

Include 지시어는 여러 개의 파일을 하나의 파일로 결합하도록 허용한다. Include 지시어의 문법은 다음과 같다:

{$include file_name}


간략한 형태로 나타내자면 다음과 같다:

{$i file_name}


이는 C 에서 #include 에 해당한다. 컴파일러가 파일에서 {$i …} 지시어에 접하면 포함된 파일 읽기를 진행하고 원본 파일로 돌아가 읽기를 계속한다. 프리 파스칼 컴파일러는 include 파일 고유의 검색 경로를 가지며 –Fi 파라미터로 확장이 가능하다. include 파일의 확장자는 프로그래머가 자유롭게 선택할 수 있지만 파스칼 유닛으로부터 구별하기 위해 공통적으로 .inc를 사용한다.


include 파일은 고유의 검색 경로를 가지고 검색 경로는 매크로를 포함할 수 있기 때문에 각 플랫폼에는 서로 다른 include 파일을 결합할 수 있다.


예를 들어, 윈도우, 리눅스, OS X마다 구현이 다른 경우 세 가지 include 파일을 생성하여 모두 동일한 (예: example.inc) 이름으로 할당할 수 있지만, 저장은 적절하게 이름을 붙인 구분된 하위 디렉터리로 이루어진다. 이후에는 운영체제 타입에 따라 자동으로 호출할 수 있다.


조건부 컴파일

$IF, $IFDEF, $IFNDEF, $ELSE, $ENDIF, $DEFINE, $UNDEF 지시어는 개별 컴파일러 버전, 프로세서, 운영체제 특정적 예외를 강조한다. 위 지시어들의 작업은 C 에서의 작업과 거의 동일하며 다음을 예로 들어보겠다:

{$IFDEF MSWindows}
 // code for Windows
 {$ELSE}
 // code for non-Windows
 {$ENDIF}


MSWindows 나 그와 동일한 Windows 외에도 운영체제 (Linux, Darwin, FreeBSD); 계열 (MS Windows, Unix, BSD); 프로세서 (CPUi386, CPU 32, CPU 64); 엔디언 (ENDIAN_LITTLE, ENDIAN_BIG); 컴파일러 버전 (FPC, VER2, VER2_3, VER2_3_1); 모드, 테스트, 특성(feature)에 대한 기타 flag가 많다.


라자루스에서 실제 flag는 Environment ⇒ Code Tools Defines Editor ⇒ Free Pascal Compiler 에서 정의된다.


구체적인 의미는 프리 파스칼 문서에서 설명한다.


매크로

C 에서는 매크로가 가상적으로 없어서는 안 되지만 프리 파스칼에서는 불필요하다. 프리 파스칼 컴파일러 역시 호환성의 이유와 C 라이브러리의 구현을 단순화시키기 위해 매크로를 지원하긴 하지만 기본적으로는 비활성화되어 있으며 라자루스에서는 인식하지 않는다. 단 extdecl 가 유일한 예외다. 많은 플랫폼 독립적 라이브러리들이 다른 시스템마다 다른 호출 규칙을 사용한다. 이는 스택에 어떠한 order 함수 파라미터를 위치시키고 어떻게 위치시키는지도 포함한다. 호출 규칙은 각 함수마다 키워드 형태로 명시되어야 한다; extdecl 매크로는 프로그래머를 해당 업무로부터 해방시켜준다. Gl 유닛의 예제를 들자면 다음과 같다:

{$MACRO ON}
{$IFDEF Windows}
  {$DEFINE extdecl := stdcall}
{$ELSE}
  {$DEFINE extdecl := cdecl}
{$ENDIF}

  procedure glAccum ( op : GLenum ;  value :  GLfloat ) ;  extdecl ; // 기타함수들


매크로는 그 유닛에서만 유효하며 다른 유닛으로는 전달이 안 된다. FPC 2.3.1 버전 이상부터 호출 규칙을 전환하는 데 새로운 지시어가 사용된다: {$callingconvention stdcall}.

{$IFDEF Windows}   {$callingconvention stdcall}
{$ELSE}            {$callingconvention cdecl}
{$ENDIF}


지시어는 다음 {$callignconvention}가 나올 때까지 모든 프로시저에 적용된다.

그림 1.11: 코드 익스플로러는 지시어가 많은 긴 유닛에서 베어링(bearing)을 얻는 데 유용하다.