파스칼 프로그래밍/문법

위키책, 위키책
둘러보기로 가기 검색하러 가기

이 문서는 파스칼의 가능한 모든 문법을 담으려 하고 있습니다.

따라서 이 문서가 다루는 내용 중 많은 부분은 일반적인 언어 사용에서 불필요한 것일 수 있습니다.

차례

구성[+/-]

주 프로그램[+/-]

program 프로그램이름;

label
  지표이름, ... , 지표이름;
 
uses
  유닛이름, ... , 유닛이름;
 
type
  형이름 = 자료형;
 
var
  변수이름: 자료형;
 
const
  상수이름=상수값;
 
  procedure 프로시저이름;
  type
    형이름 = 자료형;
  var
    변수이름: 자료형; 
  const
    상수이름=상수값;

    procedure 프로시저이름;
    begin
      //본문
    end;

  begin
    // 본문
  end;

  function 함수이름: 자료형;
  begin
    // 본문
  end;
 
begin
  // 본문
end.

위와 같이 type, var, const, procedure, function 등은 어디든지 프로그램 블럭 구조를 가진 곳이라면 쓰일 수 있다.

목적 부분에는 주 프로그램 소스코드라면 program이, 유닛이라면 unit이 올 것이다. 유닛이라면 아래의 내용은 선언부, 구현부, 초기화 등 여러 부분으로 나뉘게 된다.

한편 고전 파스칼에서는 주 프로그램 소스코드에서 입출력파일을 지정해 주어야 한다.

Program Name(Input, Output);
begin
  //본문
end.

하지만 대부분의 현대 컴파일러에서, 입출력 파일을 생략할 경우 자동으로 Input과 Output을 입력한 것으로 본다.

기본 문법 구성[+/-]

문장[+/-]

각종 선언부에서는 선언부의 문장 규칙을 따른다.

본문 내에서는 실질적인 실행 단위이며, 문법적으로는 이전 문장의 종료자 이후부터 다음 종료자 까지이다. 자유도가 높은 몇몇 언어와 달리 빈 문장이나 상수, 변수 표현식만으로 문장을 구성할 수는 없다.

문장 종료자[+/-]

파스칼에서는 문장 종료자로 세미콜론(;)을 쓴다. 매 세미콜론까지가 그 문장의 끝이 된다. 문장 종료자가 나타나지 않으면 그사이에 어떤 종류의 공백이나 매우 긴 선언이 나타나더라도 한 문장이 종료되는 것은 아니다.

블럭[+/-]

블럭은 여러 문장을 묶어 하나의 수준단위로 만들어준다.

일반적으로 본문 내에서는 begin~end에 의해 묶인 것을 보고 블럭이라 부르지만, 여러가지 문법요소를 설명하며 다양한 블럭과 그 우선순위를 다루어야 한다.

특별히 본문에서 여러 문장 사이에 블럭이 있다면, 이 블럭은 그 문장과 블럭이 속한 블럭의 수준에서 마치 하나의 문장인 것처럼 처리될 것이다.

주석[+/-]

양괄형 주석으로

(* 주석 *)

를 쓴다.

한편 현대의 컴파일러는

{ 주석 }

문법도 지원한다.

둘은 완전히 동일하게 동작하지만 서로 섞어 쓸수는 없다.

그리고 { }로 묶인 주석은 컴파일러 지시자와 동등한 표현을 하고 있을 경우 컴파일러 지시자로 취급된다.

문장; // 주석

과 같이 쓰면 // 오른쪽 부분을 주석으로 처리한다. 사용된 줄에서만 유효하다.

한편 파일을 종료하는 마침표를 포함하는 마지막 end 뒤는 무시된다.

Program Name;
begin
  // 본문
end.

 아래에 쓰는 글은 모두 의미가 없다.

컴파일러가 아래의 글을 무시하는 속성을 이용해 주석처럼 활용할 수도 있다.

하지만 많은 컴파일러는 파일 끝에 내용이 있다는 경고를 출력한다.

식별자[+/-]

식별자는 어떤 대상을 구별할 수 있는 이름이다.

사용자가 정의해 쓰는 모든 변수, 서브루틴, 상수, 자료형 등의 이름을 포함하며 영문자, 숫자, 밑줄만 사용해야 한다.

첫문자는 반드시 알파벳으로 시작하고 63자를 초과할 수 없다.

단 유닛 이름은 파일 이름이기 때문에 운영체제에 따라 대소문자를 구분할 수도 있다.

예약어는 식별자로 쓸수 없으며 지시어는 권장되는 기법은 아니지만 쓰일 수는 있다.

예약어[+/-]

  • 파스칼 프로그램의 기초적인 부분을 작성하기 위한 단어의 조합이 예약어이다.
  • 코드 작성에 최적화된 편집기에서는 예약어가 강조표시되므로 쉽게 알아볼 수 있다.
  • 예약어는 정의된 기능 외에 다른 용도로는 쓸 수 없다.
  • 파스칼에서 제공하는 예약어 외에 사용자 임의로 다른 예약어를 정의하여 사용할 수 없다.

아래는 모든 예약어의 알파벳 순 표이다.

  • and
  • array
  • as
  • asm
  • begin
  • case
  • class
  • const
  • constructor
  • destructor
  • dispinterface
  • div
  • do
  • downto
  • else
  • end
  • except
  • export
  • file
  • finalization
  • finally
  • for
  • function
  • goto
  • if
  • implementation
  • in
  • inherited
  • initialization
  • inline
  • interface
  • is
  • label
  • library
  • mod
  • nil
  • not
  • object
  • of
  • or
  • out
  • packed
  • procedure
  • program
  • property
  • raise
  • record
  • repeat
  • resourcestring
  • set
  • shl
  • shr
  • string
  • then
  • threadvar
  • to
  • try
  • type
  • unit
  • until
  • uses
  • var
  • while
  • with
  • xor

한편 객체 가시성에 관한 지시어는 객체 선언 내에서는 예약어로 사용된다.

지시어[+/-]

  • absolute
  • abstract
  • assembler
  • automated
  • cdecl
  • contains
  • default
  • deprecated
  • dispid
  • dynamic
  • export
  • external
  • far
  • forward
  • implements
  • index
  • library
  • local
  • message
  • name
  • near
  • nodefault
  • overload
  • override
  • package
  • pascal
  • platform
  • private
  • protected
  • public
  • published
  • read
  • readonly
  • register
  • reintroduce
  • requires
  • resident
  • safecall
  • stdcall
  • stored
  • varargs
  • virtual
  • write
  • writeonly

연산자[+/-]

편집 프로그래밍/파스칼/문법 (문단) - 위키책
분류 연산자 기능 피연산자 결과 예시 설명
대입 := 대입 호환평가값 없음 변수 := 평가값 오른쪽의 평가값을 왼쪽에 대입한다. 두 자료형은 같거나 호환성이 있어야 한다.
부호 + 정부호 피연산항 중 큰범주 +수 오른쪽 값의 같은 부호를 가진 값을 돌려준다.
- 반부호 -수 오른쪽 값의 반대 부호를 가진 값을 돌려준다.
산술연산 + 덧셈 수 + 수 양쪽 값을 더한 값을 돌려준다.
- 뺄셈 수 - 수 왼쪽 값에서 오른쪽을 뺀 값을 돌려준다.
* 곱셈 수 * 수 양쪽 값을 곱한 값을 돌려준다.
div 정수 정수 정수 div 정수 왼쪽 값에서 오른쪽 값을 나눈 값을 돌려준다. 정수만 연산한다.
mod 나머지 정수 mod 정수 왼쪽 값에서 오른쪽 값을 나눈 나머지를 돌려준다. 정수만 연산한다.
/ 나눗셈 실수 수 / 수 왼쪽 값에서 오른쪽 값을 실수꼴로 나눠 돌려준다. 항상 실수를 돌려준다.
논리연산 not 논리부정 논리 논리 not 논리 오른쪽 논리값의 반대값을 돌려준다.
and 논리곱 논리 and 논리 양쪽 값이 모두 참이면 참값을 돌려준다.
or 논리합 논리 or 논리 양쪽 값이 모두 거짓이면 거짓 값을 돌려준다.
xor 배타논리합 논리 xor 논리 양쪽 논리값이 다르면 참을 돌려준다.
비트연산 not 비트부정 정수 정수 not 정수 오른쪽 정수의 비트단위의 반수를 돌려준다
and 비트단위논리곱 정수 and 정수 양쪽 정수의 비트단위 논리곱을 돌려준다.
or 비트단위논리합 정수 or 정수 양쪽 정수의 비트단위 논리합을 돌려준다.
xor 비트단위배타논리합 정수 xor 정수 양쪽 정수의 비트단위 배타논리합을 돌려준다.
shl 왼쪽 비트밀기 정수 shl 자연수 왼쪽 정수를 오른쪽 자연수 비트만큼 왼쪽으로 밀어낸다.
shr 오른쪽 비트밀기 정수 shr 자연수 왼쪽 정수를 오른쪽 자연수 비트만큼 오른쪽으로 밀어낸다.
포인터연산 @ 포인터반환 임의자료형 포인터 @변수 변수의 포인터를 돌려준다.
^ 자료반환 포인터 포인터^ 포인터가 가리키는 자료를 돌려준다.
^ 포인터한정 자료형 자료형이 정해진 포인터 ^자료형 자료형이 정해진 포인터 자료형을 돌려준다.
+ 포인터 덧셈 포인터, 정수 포인터 포인터 + 정수 포인터를 정수 만큼 증가시켜 돌려준다.
- 포인터 뺄셈 포인터, 정수 포인터 포인터 - 정수 포인터를 정수 만큼 감소시켜 돌려준다.
포인터 정수 포인터 - 포인터 포인터간의 주소값 차를 돌려준다.
집합연산 + 합집합 집합 집합 집합 + 집합 두 집합의 합집합을 돌려준다.
- 차집합 집합 - 집합 왼쪽 집합의 오른쪽 집합에 대한 차집합을 돌려준다.
* 교집합 집합 - 집합 두 집합의 교집합을 돌려준다.
in 원소검사 서수형, 집합 논리 서수형 in 집합 해당 서수형 자료형이 집합의 원소이면 참을 돌려준다.
문자열연산 + 문자열 붙이기 문자열, 문자 문자열 문자(열) + 문자(열) 두 문자(열)을 이어붙인 문자열을 돌려준다.
비교연산 = 등가 모든 자료형 논리 같으면 참값을 돌려준다.
< 비교 서수형, 실수 값 < 값 오른쪽 항이 왼쪽 항보다 크면 참값을 돌려준다.
> 값 > 값 오른쪽 항이 왼쪽 항보다 작으면 참값을 돌려준다.
<=, >= 값 >= 값 위의 비교에 대해 같은 경우도 참값을 돌려준다.
<, >, <=, >= 집합 집합 > 집합 집합에 대해 진부분집합, 부분집합을 검사한다.

우선순위[+/-]

위쪽이 우선순위가 높다.

분류 종류
단항 연산자 @ not ^ 등
곱셈 나눗셈 비트 논리곱 * / div mod shl shr and
덧셈 뺄셈 논리합 + - or xor
비교 = < > <= >= <> in

다른 언어와 다른 파스칼의 연산자 우선순위는 종종 당황스럽게 보이기도 한다.

파스칼의 조건식에서 다음 코드는 괄호를 지우면 다른 표현식이 되어버린다.

if ( Var1 = Var2 ) and ( ValX > ValY ) then
begin
  ...
end

자료형[+/-]

정의하기[+/-]

새 자료형을 정의할 때는 type을 이용한다.

type
  NewInteger = Integer;

위와 같이 정의하면 NewInteger라는 자료형이 새로 생기게 되며, 이것은 Integer와 완전히 동일하다. 따라서 Integer자료형과 호환성을 갖는다.

type
  NewInteger = type Integer;

위와 같이 정의하면 Integer의 속성만을 갖는 NewInteger라는 자료형이 새로 생긴다. 이것은 Integer와 호환성을 갖지 않는다.

자세한 정의법은 각각의 자료형에 따라 아래를 참고하라.

서수형[+/-]

  • 정수형
크기(바이트) 이름 범위 이름 범위
1 ShortInt -128..127 Byte 0..255
2 SmallInt -32768..32767 Word 0..65535
4 LongInt -2147483648..2147483647 LongWord 0..4294967295
8 Int64 -2^63..2^63-1

32비트 아키텍처에서 부호 있는 형은 Integer, 부호 없는 형은 Cardinal이 기본형이며 각각 LongInt와 LongWord로 연결된다. 32비트 아키텍처에서 Int64는 기본 연산 자료형을 벗어나는 범위이기 때문에 사용할때는 특별히 오류에 주의해야 하며, 어떤 경우 강제로 형 변환을 해줘야 할수도 있다.

  • 논리형 : 참/거짓 자료형이다. 참과 거짓은 True와 False라는 상수로 정의되어 있다. 1바이트이다.
    • Boolean : 기본 자료형이며 1바이트를 차지한다. Ord함수의 반환값으로 0과 1만 가지며 참을 1로 표현한다.
    • ByteBool : 1바이트를 차지하며 Ord의 반환값이 0이 아니면 참이다.
    • WordBool : 2바이트를 차지하며 위와 같다.
    • LongBool : 4바이트를 차지하며 위와 같다.
  • 문자형 : Char는 AnsiChar로 연결된다.
    • AnsiChar : 1바이트 문자 자료형이다. 아스키 문자를 표현하기 위해 개발되었으나 지금은 지역별 코드를 표현하는데 사용된다. 파스칼의 기본 문자 자료형이다.
    • WideChar : 2바이트 문자 자료형이다. 유니코드 문자를 표현한다.
  • 열거형 : 어떤 모임에 순서를 부여하기 위해 쓴다.
type
  Wiki = (wikipedia, wikibooks, wiktionary, wiknews);

var
  Work: Wiki;

begin
  Work := wikibooks;
  if Work in [wikipedia, wikibooks, wiktionary] then // 열거형은 주로 집합형(Set)과 함께 쓰인다.
    writeLn('이 작업은 한국어 위키미디어 프로젝트가 존재한다.');
end

기본적으로 ord함수의 반환값이 0부터 채워져 나가지만, 원하는 위치로 지정할 수도 있다.

type
  Wiki = (wikipedia = 3, wikibooks = 5, wiktionary = 3, wiknews = 2*wikibooks); // 3, 5, 5, 10

wikipedia와 wiktionary는 모두 3으로 선언되었다. 위의 열거형에서 정의된 값은 4개지만 실제로 나타내는 의미있는 값은 3개라고 할 수 있다. wikipedia와 wiktionary는 완전히 같게 취급된다.

또 이렇게 선언하면 값은 3개만 선언되어 있지만 실제로 이 열거형이 갖는 제한형적 의미는 3부터 10까지의 8개값에 해당한다. 위의 예제에서

var
  Project: Wiki;
begin
  Project := wiki(6);
  Ord(Project); // = 6
  Inc(Project);
  Ord(Project); // = 7
end

위와 같이 가운데의 빈 값을 얼마든지 가리키게 할 수 있다. 이 경우에서 Project는 7을 갖고 있으며 7에 해당하는 값은 정의되어 있지 않다. 그렇다면 열거형적 의미로는 이전값 wikibooks를 가리키게 된다.

  • 범위형 : 범위형은 독립적인 자료형이라기 보다는 다른 자료형의 범위를 필요한 만큼으로 제한하는 것이다. 제한형이라고도 부른다.
type
  Alphabet = 'A'..'z';
  NumberKey = 0..9;

var
  Chr: Alphaber; // 영문자의 대소문자만 가질 수 있다.
  IntKey: NumberKey; // 0부터 9까지의 한자리 숫자만 가질 수 있다.

begin
  IntKey := 5; // 일반적
  IntKey := 10; // 오류, 범위초과
  IntKey := 9;
  Inc(IntKey); // IntKey=10
end

범위형은 위의 예제와 같은 오류를 감지하지 못하기도 한다.

실수형[+/-]

이름 최소값 최대값 유효숫자 크기(바이트)
Real48 2.9 * 10^-39 1.7 * 10^38 11 ~ 12 6
Single 1.5 * 10^-45 3.4 * 10^38 7 ~ 8 4
Double 5.0 * 10^-324 1.7 * 10^308 15 ~ 16 8
Extended 1.9 * 10^-4951 1.1 * 10^4932 19 ~ 20 10
Comp -2^63 + 1 2^63 - 1 19 ~ 20 8
Currency -922337203685477.5808 922337203685477.5807 10-20 8

32비트 아키텍처에서 Real을 선언하면 Double로 쓰인다. 옛 Real인 Real48로 연결하려면 16비트 호환성을 갖게 하는 컴파일러 지시자를 쓰면 된다. Currency는 통화 계산에 최적화된 자료형으로서 내부적으로는 소수점 이하 넷째자리까지 Int64형으로 처리되며 다른 자료형과 혼용될때는 같이 쓰이는 자료형에 따라자동으로 10000을 곱하거나 나누어준다.

문자열형[+/-]

문자열형 문자열형은 Array of Char 와 어느정도 호환이 된다. Null종료형이 아닌 문자열의 처음에 크기가 지정되어있는 파스칼의 독특한 문자열형이다.

이름 최대문자수 최소크기(바이트) 최대크기(바이트) 유형 기반 문자
ShortString 255 1 256 길이지정 AnsiChar
AnsiString 2^31 4 2기가 길이지정 AnsiChar
WideString 2^30 4 2기가 길이지정 WideChar
PAnsiChar - 포인터크기 - Null종료 AnsiChar
PWideChar - 포인터크기 - Null종료 WideChar

기본자료형인 String은 AnsiString으로 자동으로 연결된다.

var
  str: String;
  chr: Char;

begin
  str := 'Hello World';
  chr := str[1]; //chr = 'H' 
end

예제와 같이 str은 Char의 배열형처럼 취급할 수도 있다.

var
  str: String[10];

위와 같이 선언하면 10자만 가질 수 있도록 제한된다.

단 이 제한형의 경우 자동으로 ShortString으로 취급되며 따라서 최대 255자 까지 제한할 수 있다.

Null 종료형 문자열도 사용할 수 있다.

  • PAnsiChar: AnsiChar를 사용하는 Null 종료형.
  • PWideChar: WideChar를 사용하는 Null 종료형.

AnsiChar를 기본으로 사용하는 환경에서 PChar는 PAnsiChar로 연결된다.


구조형[+/-]

배열형[+/-]

type
  이름: Array[시작값..끝값] of 자료형;

크기가 정해진 자료형만 배열형으로 만들 수 있다. 따라서 문자열이나 파일, 집합 등은 만들수 없다.

type
  IntArray: Array[1..10] of Integer;

var
  intMass: IntArray; // intMass[1], intMass[2], ... intMass[10] 까지의 변수를 모두 선언한 것과 같다.
  count: Integer;

begin
   For count := 1 to 10 do // 반복문을 이용해 배열 전체에 접근한다.
     IntMass[count] := 0;
end

다차원 배열 만드는법이다.

type
  Dim1Array  = Array[1..10] of Integer;
  Dim2Array1 = Array[1..10][1..10] of Integer;
  Dim2Array2 = Array[1..10] of Array [1..10] of Integer;
  Dim2Array3 = Array[1..10] of Dim1Array;
  Dim2Array4 = Array[1..10, 1..10] of Integer;

Dim2Array4는 Dim2Array2의 다른 표현이다. Dim2Array1과 Dim2Array2는 같은 배열이다.

그렇다고 각각으로 선언한 자료형이 직접 대입 가능하다는 뜻은 아니다. 자료형의 이름이 다르다면 구조적으로 같은 자료형이라도 다르게 인식된다. Dim2Array2는 Array [1..10] of Integer 로 선언된 자료형을 읽고 쓸 수없다.

하지만 Dim2Array3은 Dim1Array를 이용해 배열 전체를 하나씩 처리할 수 있는 자료형이 되었다. Dim2Array1이나 Dim2Array2는 Dim1Array형을 처리할 수 없지만, Dim2Array3형은 가능한다.

배열의 크기를 미리 지정해 두지 않고 필요한 만큼 정의해 쓰는 동적 배열을 만들 수도 있다.

type
  IntArray = Array of Integer;

var
  IntArray1 : IntArray;

begin
  SetLength(IntArray1, 10);
end

이때 IntArray는 배열을 직접 가리키는 것이 아니라, 배열을 가리킬 수 있는 포인터이다. SetLength를 하게 되면 적절한 지점에 그 크기만큼의 배열공간을 확보하게 된다. 위의 예제에서 IntArray1은 Array[0..9] of Integer와 같게 되었다.

위의 예제에서 주의할 점은 길이를 10으로 설정했더라도, 그 범위 밖까지 접근할 수 있다는데 있다. 하지만 IntArray[9] 이후의 값은 정의한 범위를 벗어나므로 다른 자료를 파괴하거나 자료를 잃을 수 있다. 배열의 범위는 Low와 High 함수를 써서 접근하는것이 안전하다.

한편 동적 배열은 포인터이기 때문에 배열을 직접 대입할 수 없다.

type
  IntArray = Array of Integer;

var
  IntArray1, IntArray2 : IntArray;

begin
  SetLength(IntArray1, 10);
  IntArray1[0] := 100;
  IntArray2 := IntArray1;
  IntArray2[0] := 200;
  write(IntArray1[0]); // 200
end

가변형 배열은 포인터이기 때문에 IntArray2에 IntArray1를 대입하게 되면 값이 들어가는것이 아니라 IntArray1의 포인터가 들어간다. 따라서 IntArray2와 IntArray1은 같은 배열을 가리키게 되는 것이다. 자세한 것은 아래의 포인터 부분을 참고하라.

SizeOf 함수를 써 할당된 크기를 비교해보면 정적 배열은 배열의 전체 크기를 반환한다. 하지만 동적 배열은 포인터 자료형의 크기를 돌려준다.

Packed Array[+/-]

Packed Array 라는 배열형이 있다. 이것은 속도를 느리게 하지만 배열이 메모리를 빈틈없이 사용하도록 한다.

예를들어 60비트 플랫폼에서

type
  pIntArray = Packed Array[1..10] of LongInt;
  IntArray = Array[1..10] of LongInt;

var
  pArr: pIntArray; //320비트 할당
  Arr: IntArray;   //600비트 할당

와 같이 작동한다.

몇몇 플랫폼과 컴파일러에서 Array는 기본적으로 Packed Array이다.

플랫폼 독립적인 코드를 작성하고 있다면 모든 배열과 구조체는 반드시 packed로 선언하는 것이 안전하다.

구조체[+/-]

  • Record : 여러개의 자료형을 묶는 틀과 같은 자료형이다.
type
이름 = Record
  필드1 : 자료형;
  필드2 : 자료형;
  필드3 : 자료형;
end;
type
  Subject = Record
    Pascal, PHP, Assembly : Integer;
  end;

  Student = Record  
    Score: Subject;
    Name: Array[0..8] of Char;
    Grade: Char;
  end;

var
  Student1, Student2: Student;

begin
  Student1.Name := 'student';
  with Student1.Score do
    begin
      readln(Pascal, PHP, Assembly);
      if Pascal + PHP + Assembly > 200 then
        Student1.Grade := 'A'
      else
        Student1.Grade := 'F';
    end;

  Student2.Score := Student1.Score; // Student2의 Score를 한꺼번에 대입했다.
end

기본적인 사용법에 관해서는 예제를 참고하라.

한편 구조체는 가변형으로 만들어 크기를 절약할 수도 있다.

type
  Project = Record
    isWikibook=Boolean;
    Case isWikibook of
      True : (NameOfBook : Array[1..10] of Char);
      False: (Contributions : Integer);
    end;
  end;

위와 같은 가변형 구조체를 만들면 isWikibook의 값에 따라 NameOfBook과 Contributions중 하나의 값만 가질 수 있다. 만약 잘못된 이름을 호출하게 되면 내부에 저장된 값을 필요한 바이트만큼 끊어오기 때문에 원하지 않는 결과를 받게 된다.

위 구조체에서 Contirubutions의 Integer보다 NameOfBook의 Array가 더 크기 때문에 10바이트가 두 영역을 위해 할당된다. 이는 두개를 따로 선언했을때의 합 14바이트보다 4바이트가 적은 값이다.

어떤 방식으로 선언해야 하는지 잘 이해가 안된다면, 조건문의 Case 부분을 참고하라.

한편 간소화 하여 아래처럼 만들더라도같은 구조체이다.

type
  Project = Record
    Case isWikibook: Boolean of
      True : (NameOfBook : Array[1..10] of Char);
      False: (Contributions : Integer);
    end;
  end;

초기화할때는 ()를 이용해 내용을 둘러싸면 된다. 첫 예제의 Student 구조체를 예로 들면

var
  Student1 : Student = (Score : (Pascal  :100;
                                 PHP     : 90;
                                 Assembly: 50);
                        Name  : '파스칼';
                        Grade : 'A');

)가 있을때 ;가 밖으로 나가는 것은 end 앞에는 원칙적으로 ;을 쓰지 않는 것과 같은 원리이다.

집합형[+/-]

Set 으로 다루는 집합형이다. 서수형 자료형을 원소로 가질 수 있다. 수학의 집합을 생각하면 쉽다.

type
  이름 = Set of 자료형; //단 자료형의 선택 가능한 수에 제약이 있다.

집합형은 연산의 독특한 연산성질을 이해해야 쓰기 쉽다.

단 집합형을 만들 때는 원소집단의 크기가 256개 이하여야 한다. 따라서 Set of SmallInt 와 같은 자료형은 존재할 수 없다.

파일[+/-]

  • File : File of (자료형) 꼴로 선언해 쓴다. File형을 다루는 고유의 함수가 많다.
    • Textfile : 텍스트 파일을 제어할 때는 File of Char를 쓰지 않고 Textfile자료형을 쓰는 경우가 많다. Textfile 자료형에서는 WriteLn, ReadLn을 쓸 수 있으며 표준입출력장치를 쓰는것과 같은 효과를 낸다. 자료형을 쓰는 경우가 많다. Textfile 자료형에서는 WriteLn, ReadLn을 쓸 수 있으며 표준입출력장치를 쓰는것과 같은 효과를 낸다.

포인터[+/-]

포인터는 연산의 측면에서 서수형, 특히 부호없는 정수형과 매우 닮아있으나 산술 연산에서 고유의 성질을 가진다.

오브젝트 파스칼에서 포인터는 겉으로 드러나기보다는 동적 배열, 파일, 문자열, 클래스변수를 통해 간접적으로 이용된다.

var
  PData = Pointer;
  Data = Integer;

begin
  Data := 5;
  PData := @Data;
  Write(Integer(PData^)); // 5 출력
end

한정된 포인터[+/-]

특정한 자료형의 포인터를 생성할 수 있다.

var
  PData = ^Integer;
  Data = Integer;

begin
  Data := 5;
  PData := @Data;
  PData^ := 10; // Data에 10을 대입
end

함수[+/-]

함수와 프로시저를 변수에 저장하고 사용할 수 있다.

type
  ProcVar: Procedure(Parameter: String);

procedure MyProc(Parameter: String);
begin
  // 본문
end;

begin
  ProcVar := MyProc;

  ProcVar('Test');
  MyProc('Test');
end.

아래의 두 실행은 같은 것이다.

포인터 등을 이용해 정밀하고 복잡한 제어가 가능하다.

이때 실질적으로 ProcVar가 MyProc인지 검사하기 위해

  ProcVar = MyProc

와 같은 표현을 하면, 컴파일러는 이를 프로시저의 호출로 해석하고 매개변수를 요구한다.

  @ProcVar = @MyProc

와 같은 표현으로 이를 피해갈 수 있다.

이때 @는 포인터 연산자가 아니다.

가변형[+/-]

만능 자료형 변수를 사용하면 포인터를 제외한 자료를 다룰 수 있다.

정수, 실수는 입출력에 따라 자동으로 바뀌고, 숫자 문자나 문자열은 해당하는 숫자 데이터와 호환된다.

논리형은 값이 0이거나 문자열 'False'면 거짓, -1이거나 'True'면 참으로 해석된다.

가변형 배열[+/-]

한편 가변형에는 배열을 할당할 수도 있다.

클래스[+/-]

클래스의 선언에 관해서는 아래의 클래스 부분을 참고하라.

컴파일러 지시자[+/-]

문법[+/-]

선언부[+/-]

지표[+/-]

Goto문을 참고하라

유닛 참조[+/-]

유닛을 참고하라.

상수[+/-]

상수 선언은 const로 시작한다.

상수는 컴파일 시에 결정될 수 있는 연산으로만 구성된 상수식으로 표현되어야 하며, 대부분의 함수 사용은 상수식을 표현하지 않는다.

단 System에 정의된 기본 함수 몇개는 함수가 아닌 단순한 변환으로 취급되어 사용할 수 있다.

const
  Const1 = 1; // 단순 상수
  Const2 = Const1 + 50; // 상수 산술 연산
  Const3 = Ord('a'); // 표준 함수

사용할 수 있는 표준 함수의 목록은 아래와 같다.

자료형[+/-]

변수[+/-]

함수[+/-]

조건문[+/-]

if[+/-]

if문은 Boolean형 자료의 참거짓 값에 따라 프로그램 진행을 분기하고자 할때 쓴다.

if Bool then
  명령
else
  명령;

Bool이 참값이라면 then 뒤의 명령을, 거짓이라면 else뒤의 명령을 실행할 것이다.

else 이후는 생략해도 된다.

단 if문 전체가 한 문장으로 인식되므로, else를 사용할 경우 else 앞에는 문장 종료자를 사용하면 안된다.

case[+/-]

case 서수형 of
  경우1 :
    명령1;
  경우2 :
    명령2;
  경우3 :
    명령3;
  else
    명령;
end;

case문은 서수형 자료형의 값에 따라 여러 분기문을 만들때 쓴다. 서수형의 자료값이 각 경우에 해당되면 그 경우의 명령을 실행한다. 만일 조건 중 아무것도 충족하지 못한다면 else의 문장을 실행할 것이다.

이때 case문에 들어가는 각 경우는 컴파일할 때 계산 가능한 값이어야 한다.

반복문[+/-]

for[+/-]

begin
  for 정수형지시자 := 시작점 to 끝점 do
    문장;
end

정수형지시자에 시작점부터 끝점까지 1씩 증가시키며 대입한다. for문 내에서는 참조변수를 제어할 수 없으며 시작점과 끝점은 반복문을 실행할 때 이미 결정된다. 따라서 시작점과 끝점이 함수를 포함한 평가값으로 나타날 때 이 값이 변하더라도 끝값이 재계산되지 않는다.

만일 for의 끝점이 시작점의 이전이어서 문장을 실행할 필요가 없을 경우, 지시자에는 시작점도 대입되지 않는다.

to 대신 downto를 사용하면 1씩 감소한다.

1 이외의 차로 for문을 설정할 수는 없다.

일부 컴파일러에서는 for의 정수형 지시자는 반드시 단일한 지역변수일 것을 강제/권고한다.

for-in[+/-]

최신 버전의 델파이는 확장 문법으로 for-in 구문을 지원한다.

begin
  for 반복자 in 컨테이너 do
    문장;
end

while[+/-]

while문은 조건이 맞으면 반복하는 반복문이다.

while bool do
  문장;

bool이 True라면 문장을 실행하고 다시 bool을 검사한다. bool이 False라면 while문 밖으로 빠져나간다.

repeat ~ until[+/-]

repeat ~ until문은 조건이 맞을때까지 반복하는 반복문이다.

repeat
  문장1;
  문장2;
until bool;

repeat문은 일단 문장1, 문장2를 실행하게 된다. 그리고 until의 bool을 검사해 True면 순환을 빠져나가고, False면 repeat부터 새로 반복하게 된다.

repeat~until 사이에는 여러 문장이 들어갈 수 있다.

반복문의 프로시저[+/-]

  • Break는 반복문을 빠져나가게 한다.
  • Continue는 반복문의 맨 끝으로 이동하게 한다. 반복문의 끝에 도달했으므로 다시 처음값으로 돌아가 반복문을 계속 실행하게 된다. while에서는 조건검사부터 시작하게 되고 for에서는 지시자 재설정부터 하게 된다.

한정구문[+/-]

With를 이용해 한정구문을 작성할 수있다.

유닛, 구조체, 클래스 등 하부구조를 가지는 자료를 다룰 때 불필요한 반복을 막기 위해 사용하거나, 스코프의 우선순위를 정하기 위해 사용된다.

type
  RecSample1 = Record
    Data: Char;
  end;

  RecSample2 = Record
    Value: Char;
  end;

var
  Rec1: RecSample1;
  Rec2: RecSample2;

begin
  Rec1.Data := 'a'; // #1
  with Rec1 do
    Data := 'b';    //#1

  with Rec1, Rec2 do
    Value := Data;         // #2
  Rec2.Value := Rec1.Data; // #2
end

주석에 #1과 #2가 붙은 쌍은 같은 번호끼리 같은 문장이다.

with 구문에 여러 개의 값이 들어갈 경우 각각의 값에 대해 with를 연달아 실행한 것과 같다.

  with Rec1, Rec2 do
    Value := Data;
  with Rec1 do with Rec2 do
    Value := Data;
end

위와 아래는 같은 문장이다.

나중에 실행된 with구문일수록 스코프의 우선순위가 높다.

Goto[+/-]

goto는 프로그램의 어떤 지점으로 실행 위치를 옮기는 역할을 한다.

label
  1;

begin
  문장1;
  Goto 1;
  문장2;
1:문장3;
end

위의 가상 프로그램에서, 문장 1을 실행한 후 Goto에 의해 지표 1을 따라 문장 3으로 이동하게 된다.

지표[+/-]

Goto문을 사용하기 위해서는 goto의 대상이 되는 지점에 지표를 미리 선언해 둘 필요가 있다. 고전 파스칼에서 지표로 선언할 수 있는 것은 9999 이하의 자연수이지만 널리 쓰이는 컴파일러는 문자 사용을 지원한다.

사용[+/-]

아래는 for문을 goto와 if로 구현한 것이다.

label
  head;

var
  count: integer;

procedure virtualfor(var count: integer; from, to: integer;);
begin
  count := from-1;
head:
  inc(count);
  if count <= then begin
    write(count:4, 'for 안에 있다.');
    goto head;
  end;
end;

begin
  virtualfor(count, 1, 20);
end;

위의 실행은 아래와 정확히 일치한다.

var
  count: integer;

begin
  for count := 1 to 20 do
   write(count:4, 'for 안에 있다.');
end;

사용 규칙[+/-]

Goto문은 외부 블럭의 내부로 분기할 수 없다. 내부로 분기하게 되면, 어떤 조건에서 그 블럭으로 들어가는지 알 수 없기 때문이다.

부프로그램[+/-]

부프로그램은 함수와 프로시저를 통칭한다.

함수[+/-]

함수는 함수명에 지정된 부프로그램을 실행한 후 자신의 이름에 연산의 결과를 반환한다.

function 이름(인자: 인자자료형): 자료형;
begin
  // 본문;
  Result := 함수값;
end;

Result는 함수 결과 값에 대한 만능 변수이다. 함수 이름에 직접 결과값을 대입할 수도 있다.

프로시저[+/-]

프로시저는 자료형이 지정되지 않고, 반환값을 지정하지 않는 함수와 같다. 단 반환하는 자료형이 없는 함수를 만들 수는 없다.

procedure 이름(인자: 인자자료형);
begin
  연산;
  연산;
end

프로시저는 프로시저 이름만으로 호출하면 된다. 어떤 값도 반환하지 않으므로 그 자체로 하나의 문장을 이루어야만 한다.

참조방법[+/-]

함수에 인자를 넘겨줄 때, 넘겨주는 방식에 따라 약간씩 특징이 바뀌게 된다.

값 참조[+/-]

값 참조 방식은 일반적 방식이다. 아무련 표식도 하지 않았을 때 함수는 인자를 자동으로 값참조 방식으로 넘긴다. 서브루틴 내에 인자 이름에 해당하는 지역변수가 있는것과 같다.

주소 참조[+/-]

주소 참조 방식은 변수의 인자의 포인터를 인자이름의 포인터에 대입시키는 것과 같다.

function 이름(var 인자: 자료형): 자료형;
begin
  Result := 반환값;
end;

상수 참조[+/-]

상수 참조 방식은 변수의 값만 인자로 넘겨주는 것과 같다.

function 이름(const 인자: 자료형): 자료형;
begin
  Result := 반환값;
end;

인자 이름은 하나의 상수처럼 취급된다.

출력 참조[+/-]

출력 참조는 변수에 값을 입력받기 위한 장치이다.

procedure 이름(out 인자: 자료형);
begin
  인자 := 출력값;
end

out으로 넘겨받은 인자에서는 자료를 가져올 수 없다. 값을 돌려주기에 최적화 되어있다.

순환호출[+/-]

간혹 재귀호출이 필요할때와 마찬가지로, 두개 이상의 부프로그램을 순환호출해야 할 때도 있다.

procedure Proc2; forward;

procedure Proc1;
begin
  Proc2;
end;

procedure Proc2;
begin
  Proc1;
end;

위와 같이 만들면 Proc1에서도 Proc2를 참조할 수 있고, 실제로 Proc2를 호출했을때는 위의 Proc2를 부르게 되지만, 위의 Proc2는 실 선언부인 아래로 이동하게 되므로 문제가 발생하지 않는다.

forward는 구현이 없다는 것을 의미한다.

자료형이 정해지지 않은 매개변수[+/-]

var, const, out으로 자료형을 정하지 않고 변수를 넘겨줄 수 있다.

function Ord(const Data): Cardinal;
begin
  result := Cardinal(Data);
end

내장 함수 Ord는 위와 같은 기능을 한다고 볼 수도 있다.

자료형이 정해지지 않은 인자는 어떤 자료형과도 호환되지 않으며 반드시 형 변환을 해줘야 한다.

만능 배열 매개변수[+/-]

크기가 정해지지 않은 배열을 매개변수로 받을 수도 있다. 배열의 크기를 지정하지 않고 받아들이면 된다.

변수 선언에서 동적 배열을 선언한 것과 유사해 보이며, 실제로도 비슷하다.

0부터 시작하지 않는 배열을 매개변수로 넘겨주더라도, 받아들인 함수에서는 0부터 시작한 것으로 간주한다.

자료형 대신 const 를 넣으면 만능 자료형 배열을 받아들일 수 있다.

제어[+/-]

  • exit : 서브루틴에서 빠져나갈 때 쓴다. exit를 실행하면 한번에 서브루틴의 end로 이동하게 된다.
  • exit(반환값) : 최신 버전의 델파이에서는 문법의 확장으로 함수의 결과 값을 exit로 바로 넘겨줄 수 있다.

기본 매개변수[+/-]

델파이는 매개변수 기본인자를 지원한다. 이 기능 없이도 오버로딩으로 동일한 구현이 가능하다.

매개변수에서 자주 사용되는 인자가 있다면, 기본값을 지정해 직접 입력하지 않아도 자동으로 값을 채워넣을 수 있다.

procedure Progress(Name:String; Level:Integer=0);

위와 같은 원형으로 함수를 선언하면

Progress(Name);
Progress(Name, 0);

두 문장은 정확히 같은 문장이 된다.

기본값은 항상 오른쪽부터 차있어야 하며, 오른쪽 값에 기본값이 없이 왼쪽에만 기본값을 지정할 수는 없다.

보다 자유롭고 다양한 중복선언을 위해서는 기본 매개변수와 함께 아래의 중복선언을 이용해야 한다.

한편 forward를 이용해 미리 선언한 경우, 실제 구현 부분에서만 기본값을 명시해도 충분하다. 하지만 미리 기본값까지 선언한 경우, 구현부에서도 기본값을 일치시켜야 한다.

메소드 오버라이드 등으로 기본값을 다르게 정의한 경우 정의부의 기본값을 따르게 된다.

중복선언[+/-]

같은 이름의 부프로그램을 다른 인자를 받도록 중복 선언해야 할 필요가 있을 수 있다.

아래는 받은 자료형을 정수로 바꿔주는 프로시저이다.

function ToInteger(Number: String): int64; overload;
begin
  try
    Result := StrToInt64(Number);
  except
    Result := Round(StrToFloat(Number));
  end;
end;

function ToInteger(Number: Extension): int64; overload;
begin
  Result := Round(Number);
end;

function ToInteger(Number: TDateTime): int64; overload;
begin
  Result := DateTimeToUnix(Number);
end;

필요한 만큼 정의하여 쓸 수 있다.

같은 갯수의 인자를 받아들이는 함수가 여럿 정의되어 있을 경우 넘겨준 인자와 가장 유사한 인자를 요구하는 함수를 호출한다.

만든 함수가 어디있는지 못찾거나, 함수의 용도가 헷갈리는 것을 방지하기 위해서는 같은 이름의 함수는 한곳에 모아두는 것이 좋다.

오브젝트 파일의 함수[+/-]

{$L 파일이름}
procedure 선언; external;

링크한 오브젝트 파일에서 함수를 가져와 쓸 수 있게 된다.

외부 라이브러리의 함수[+/-]

외부 라이브러리의 함수는 다음과 같이 가져온다.

procedure Name; external 파일;

파일에는 라이브러리 파일 이름이 들어가야 한다.

procedure Name; external 파일 name 이름;

위와 같이 쓰면 이름에 해당하는 것을 Name이라는 새로운 이름으로 가져올 수 있다.

지시어[+/-]

부프로그램을 선언할 때는 지시어를 줄 수 있다. 대부분 위에서 한번씩 언급한 것이다.

procedure Name; 지시어;
begin

end;
  • overload
  • forward
  • external
  • varargs

아래 표를 포함한 5개의 지시어는 호출과 작동 방식을 결정한다.

  • register: CPU 레지스터에 3개까지 변수 전달.
  • pascal
  • cdecl: 함수의 호출자가 스택을 비움.
  • stdcall
  • safecall

기본값은 register이다.


아래는 16비트 윈도 프로그래밍에서만 쓰이는 지시어이다.

  • far
  • near
  • export

형변환[+/-]

자유로운 자료 제어를 위해서는 적절한 형변환이 필요하다.

어떤 자료형이든 그 자료형의 이름을 함수처럼 사용하여 형을 바꿀 수 있다.

이때 변수를 형변환 할 경우 변환된 형의 변수처럼 자유롭게 쓸 수 있다.

파일 입출력[+/-]

괄호 안은 델파이의 경우이다.

종류 이름 원형 용도
열고닫기 Assign(File) procedure(F: File; Filename: String) 파일 변수 F에 실제 파일 Filename을 대응시킨다.
Rewrite procedure(F:File) 파일내 내용을 쓰기 위해서 연다. 내용이 모두 지워진다.
Reset procedure(F:File) 파일을 읽기 위해 연다.
Append procedure(F:File) 파일에 내용을 덧붙일 수 있게 연다.
Close(File) procedure(F:File) 열었던 파일을 닫는다.
읽고쓰기 Read procedure(F:File; [N1, N2, ... ]) F에서 내용을 읽는다.
Write F에 내용을 쓴다.
ReadLn procedure(F:Textfile; [N1, N2, ... ]) 텍스트파일에 대해 줄을 읽는다.
WriteLn 텍스트파일에 대해 줄을 쓴다.
BlockRead
BlockWrite

유닛[+/-]

파스칼의 유닛은 일부 언어와 달리 소스코드를 보호할 수 있다. 유닛을 컴파일하면 interface 부분은 접근 가능한 자료로 남고, implementation 부분은 컴파일된다. 컴파일러는 버전마다 독특한 컴파일된 유닛을 생성하기 때문에 유닛을 컴파일하는 컴파일러와 유닛을 참조하는 컴파일러는 같은 버전이어야 한다.

  • interface : 유닛의 외부에서 접근할 수 있는 파일의 머리부분이다.
  • implementation : interface에서 선언한 유닛의 내용을 서술한다.
unit Unitname;

interface

var
  int1: Integer; // 이 유닛을 참조하면 interface에 선언한 내용은 접근할 수 있다.

procedure Proc1; // interface에서는 이름만 선언해 준다.

implementation

var
  int2: Integer; // implementation에 선언한 내용은 유닛 내에서만 접근할 수 있다. 이 기능으로 소스코드를 보호할 수 있다.
  procedure Proc1; // interface에서 선언한 부분을 implementation에서 구현한다.
  begin
    
  end;

initialization
 //본문

finalization
 //본문
end.

선언부에서 이름만 선언한 것은 앞에서 다루었듯 뒤의 내용에 대해 미리 선언하는 것이다. 유닛은 컴파일하면 구현부에 접근할 수 없기 때문에 구현부와 연결되는 선언부를 만들어 두는 것이다.

initialization은 초기화 부분이다. 유닛의 참조가 순서대로 일어나며 최초 참조 때 이 부분이 실행된다. 필요하지 않다면 사용하지 않아도 되는 부분이며, 이 안에서는 변수나 자료형 등을 위한 선언부를 만들 수 없다.

finalization은 완료 부분이다. 초기화 부분이 있는 유닛에서만 사용할 수 있다.

상호 참조[+/-]

유닛은 서로 uses로 참조하게 될 수도 있다. 이때 유닛간의 참조가 가시적으로 꼬리를 물게 되면 안된다.

즉 유닛끼리 interface의 uses에서 서로 참조하는 고리를 이루게 하면 안된다.

이런 문제를 피하기 위해 다른 유닛의 구성물을 선언부에서 굳이 사용할 필요가 없다면, 유닛의 참조는 구현부에서 하는 것이 좋다.

객체[+/-]

오브젝트 파스칼 언어에서 객체를 다루는 것은 크게 두 가지 문법으로 나눌 수 있다. 하나는 널리 쓰이는 class 예약어를 사용하는 델파이 언어의 클래스이며, 다른 하나는 object 예약어를 쓰는 전통적인 오브젝트 파스칼의 객체이다.

전통적인 오브젝트 파스칼의 객체는 객체 타입을 하나의 자료형으로 간주하는 고전적인 문법을 사용해 객체를 다룬다. 델파이는 클래스 전체를 하나의 모듈로 간주하여 클래스에 접근한다.

가시성[+/-]

type
  TClassName = class
    int1: Integer; // 일반적 변수 선언. private로 본다.
  private
    int2: Integer;
  protected
    int3: Integer;
  public
    int4: Integer;
    constructor create;
    destructor destroy;
  end;

상속성[+/-]

type
  TClassName = class
  private
    procedure Proc1; virtual; // private이므로 virtual로 선언할 필요가 없다.
  protected
    procedure Proc2; virtual;
  public
    procedure Proc3; virtual;
  end;

  TClassName2 = class(TClassName)
  public
    procedure Proc1: override; // private로 선언한것은 override할 수 없다.
    procedure Proc2: override;
    procedure Proc3: override;
  end;

var
  ClassExam: TClassName;

procedure Proc4;
begin
  ClassExam := TClassName.Create;
  Proc1; // Private로 선언한 것을 호출할 수 없다.
  Proc2; // Protected로 선언한 것을 호출할 수 없다.
  Proc3; // Public으로 선언했으므로 호출할 수 있다.
  ClassExam.Destroy; // 일반적으로 TObject를 제거할때는 Free를 호출한다.
end;

클래스는 항상 Constructor로 만들고 Destructor로 제거한다. Destructor로 제거해주지 않으면 클래스의 찌꺼기가 램에 남아 메모리 누수를 일으킬 수 있다.

위키백과 한국어 위키백과에 수록된
메모리 누수 문서 참고.

첫번째 경우에서

var
  ClassExam: TclassName;

을 선언했다면,

ClassExam.Create;

라고 쓸 수는 없다. ClassExam이 생성자인 Create를 갖고 있기는 하지만, 변수로 선언한 ClassExam에는 단순히 TclassName 속성의 포인터라는 표시를 해 둔 것이기 때문이다. 변수를 선언한 시점에서 ClassExam은 실체가 없는 빈 포인터일 뿐이다. 따라서 저 포인터에 어떤 TClassName의 값을 대응시켜주어야 사용할 수 있고, 그것이 TClassName.Create로 새 클래스를 생성해 대입해 주는 과정이다.

  • 클래스는 서로 상속할 수 있다.
type
  TClassParent = Class
  public
    int1: Integer;
    procedure Proc1; virtual;
  end;

  TClassName = Class(TClassParent)
  public
    procedure Proc2: override;
  end;

procedure TclassParent.Proc1;
begin
  
end;

procedure TclassName.Proc1;
begin
  inherited; // inherited 자리에서 이 클래스가 상속한 TClassParent.Proc1이 실행된다.
  
end;

클래스를 선언할 때 ( ) 안에 클래스의 이름을 적으면 그 클래스의 모든 속성을 상속하게 된다. 위의 예에서 TClassName에는 int1을 선언하지 않았지만 int1을 쓸 수 있다.

같은 이름의 서브루틴을 내용을 상속하며 새로 쓸 때는 override를 이용한다. TClassName에서 TclassParent의 Proc1을 상속했다. inherited로 상속한 함수를 원하는 시점에 실행해주면 이를 이용해 추가처리를 할 수 있다.

정적 바인드[+/-]

위의 예제에서는 virtual과 override를 썼다. 그런데 이것을 쓰지 않아도 상속은 가능하다. 하지만 아래에서 보이는 이유 때문에 상속한 클래스의 객체의 메소드를 조상 클래스를 이용해 호출하고자 할때는 동적 바인드를 해주게 된다.

type
  TClassParent = Class
  public
    constructor Create;
    procedure Proc1;
  end;

  TClassSon = Class(TClassParent)
  public
    destructor Destroy;
    procedure Proc2:
  end;

var
  Class1: TClassParent;
  Class2: TClassSon;

begin
  Class1 := TClassParent.Create; // TClassParent을  할당
  Class1.Proc1; // TClassParent.Proc1 을 실행 #1
  Class1.Destroy;
  
  Class1 := TClassSon.Create; // TClassSon 을 할당. TClassSon이 TClassParent의 속성을 갖고 있어서 가능하다.
  Class1.Proc1; // TClassParent.Proc1 을 실행 #2
  TClassSon(Class1).Proc1; // TClassSon.Proc1 을 실행. TClassSon으로서 실행해주었다. #3
  Class1.Destroy;

  Class2 := TClassParent.Create; // 오류. #4
  Class2.Destroy;

  Class2 := TClassSon.Create;
  Class2.Proc1; // TClassSon.Proc1을 실행. #5

  Class2.Destroy;
end;

위에서 #1과 #5는 일반적인 클래스의 사용이다.

  1. 3에서는 다른 형의 변수에 클래스가 대입되어 있지만, 맞는 형으로 변환해 실행했으므로 정상적으로 작동했다.
  1. 4는 TClassParent는 TClassSon에 대한 정보가 없기 때문에 TClassSon인 Class2에 맞지 않는 형이 되어 대입할 수 없다.
  1. 2가 가장 큰 문제인데, Class1은 TClassSon이므로 TClassSon.Proc1을 실행해야할 것 같지만, 실제로는 선언된 형을 때라 TClassParent.Proc1이 실행된다.

파스칼은 객체지향적인데, 위와 같이 대입한것과 다른 형의 함수를 호출하게 되면 객체지향성에 위배된다. 이 문제를 해결하기 위해 virtual, dynamic, override를 이용해 동적으로 바인드하는 것이다.

동적 바인드[+/-]

클래스의 함수를 동적으로 바인드하게 되면, 그 객체의 클래스는 메소드를 직접 가리키는 대신 객체의 메소드에 대한 포인터를 가리키게 된다. 이에 따라 그 클래스에서 메소드를 호출하게 되면 객체는 자신의 메소드를 가리키는 포인터를 참조하여 적절한 메소드를 실행하게 된다.

구현 없는 선언[+/-]

선조는 해당 함수가 필요없지만, 후손 클래스가 이를 상속해 사용할 필요가 있을 수 있다. 이럴때는

  procedure ProcName; abstract;

와 같이 이름만 선언하고 abstract를 붙이면 된다.

주로 선조에서 후손이 정의할 함수를 사용하는데 이용되기 때문에

  procedure ProcName; virtual; abstract;

와 같이 동적으로 생성하는 경우가 많다.

최적화[+/-]

동적 바인드를 위한 최고 조상을 만드는 방법은 virtual 외에도 dynamic이 있다.

type
  TParent = Class
    public
      proc1; virtual;
      proc2; dynamic;
  end;
  TSon = Class(TParent)
    public
      proc1; override;
      proc2; override;
  end;

보기와 같이 어떻게 선언하더라도 자손 클래스에서는 구분하지않고 override할 수 있다. 하지만 두가지는 서로 다른 기능을 한다.

  • virtual: 속도에 최적화.
  • dynamic: 코드크기에 최적화.

용도에 맞게 쓰는것이 바람직하지만, 일반적인 경우에는 하드웨어의 성능때문에 프로그램을 실행할 수 없는 경우는 드물기 때문에 속도에 최적화하는것이 좋다.

중복선언[+/-]

부프로그램과 유사하다.

type
  TParentClass = Class
    public
      Proc1(Int: integer); virtual; overload;
  end;

  TSonClass = Class(TParentClass);
    public
      Proc1(Str: String); reintroduce; overload;
  end;

부프로그램의 예제와 달리 reintroduce가 사용되었다. 이는 상속하지 않음을 나타낸다.

일반적인 동적 상속과 달리 override도 사용되지 않았다. 이것은 선조의 Proc1을 상속한 것이 아니라 새로운 함수를 만들었기 때문이다.

클래스간의 순환참조[+/-]

부프로그램간의 순환참조와 매우 유사하므로 예제만 참고하면 충분할 것이다.

type
  TClass1 = class; // 이름만 선언한다.
  TClass2 = class
    Class1: TClass1; // TClass1을 참조
  end;
  TClass1 = class
    Class2: TClass2; //TClass2를 참조
  end; // 본 내용을 선언한다.

이 경우 서로의 생성자나 소멸자가 순환호출하지 않도록 유의해야 한다.