본문 바로가기
Delphi, RadStudio

[개발/delphi] 델파이 - Object Pascal 강좌

by SB리치퍼슨 2012. 1. 18.

두 번째 강좌 : 이번에는 델파이의 근간을 이루고 있는 Object Pascal에 대해 알아보겠습니다!

출처:  http://cafe.daum.net/delphi5Completed/5XK/3?docid=6zC|5XK|3|20000805083613&q=%B5%A8%C6%C4%C0%CC%20longword

 

2.1 기본 Data Type

일반적으로 어떤 언어든 기본적인 데이터 형은 비슷하다. 왜냐하면 같은 컴에서 돌아가니까...

델파이 4의 새로운 기능으로 세가지 Type이 나온다.

델파이4에서는 일반적인 Interger형(32bit Longint)으로 처리할 수 없는 너무 큰 정수값을 사용하고자 할 때 새롭게 Int64형을 사용할 수 있다.
그리고 StrToInt64와 StrToInt64, StrToInt64Def는 Int64를 지원하는 새롭게 추가된 함수이다. 그러나 몇몇 루틴들(Ord 등)은 Int64형 값을 지원하지 않는다.

또 델파이 4에서는 32비트 부호 없는 정수형(32bit Unsigned Integer Type)인 Unsigned Integer형으로 Longword형이 추가되었다. 기존에 지원되었던 Cardinal형이 이제 델파이 4에서 Longword와 동등한 범위를 지원한다.

마지막으로, 이전 버전에서 48비트 부동 소수점 수로 표현되었던 Real형은 이제 64비트 Double형으로 식별되도록 변경되었다. Double형이 인텔 계열 CPU와의 호환성을 제공하기 때문에, 이와 같은 변경이 성능을 향상시킨다. Real형의 속성들은 이제 Published로 사용할 수 있다.

▶ 정수형 (Integer형)

정수형은 일반적인 숫자를 표현하는데 사용되는 자료형이다. 정수형은 표현할 수 있는 값의 범위에 따라 다시 표와 같이 구분된다.

자료형

범 위

크 기

Shortint

-128 ~ 127

1바이트 / 부호있는 8비트

Smallint

-32768 ~ 32767

2바이트/ 부호있는 16비트

Longint

-2147483648 ~ 2147483647

4바이트/ 부호있는 32비트

Int64

-(263) ~ (263)-1

8바이트/ 부호있는 64비트

Byte

0 ~ 255

1바이트/ 부호없는 8비트

Word

0 ~ 65535

2바이트/ 부호없는 16비트

Longword

0 ~ 4294967295

4바이트/ 부호없는 32비트

Integer

-2147483648 ~ 2147483647

4바이트/ 부호있는 32비트

Cardinal

0 ~ 4294967295

4바이트/ 부호없는 32비트

▶ 실수형

실수형은 소수점 이하의 값을 다룰 수 있다는 점에서 정수형과 다르다.

자료형

범 위

유효 자릿수

크 기

Real

5.0 * 10-324 ~ 1.7 * 10308

15 ~ 16자리

8 바이트

Real48

2.9 * 10-39 ~ 1.7 * 1038

11 ~ 12자리

6 바이트

Single

1.5 * 10-45 ~ 3.4 * 1038

7 ~ 8자리

4 바이트

Double

5.0 * 10-324 ~ 1.7 * 10308

15 ~ 16자리

8 바이트

Extended

3.6 * 10-4951 ~ 1.1 * 104932

19 ~ 20자리

10 바이트

Comp

-263+1 ~ 263-1

19 ~ 20자리

8 바이트

Currency

-922337203685477.5808 ~

-922337203685477.5807

19 ~ 20자리

8 바이트

Comp형은 분류상으로는 실수형으로 두었지만, 실제로는 정수형 값이다. 이제는 Int64형을 쓰는 것이 더 효율적이다.(Int64형이 나왔으니깐…) 또한, Currency형도 정확한 돈 계산을 위하여 부동 소수점 연산을 하지 않고 소수점 이하 네 자리만 보존하는 고정 소수점 수이기 때문에 유효 자릿수 이후를 잘라버림으로써 발생하는 Round off오류가 발생하지 않는다. 연산할 때도 자동으로 10000을 곱하고 나누어 주는 기능을 한다. 미국과 같이 달러와 센트 등으로 표기하는 국가에서 사용할 만하지만 우리 입장에서는 Int64형을 쓰는 것이 더 나을 것 같이 보인다.

▶ 문자열형

변수를 String형으로 정의하면 Generic형이 되어 기본적으로 AnsiString형으로 정의된다.

자료형

최대 길이

필요한 메모리양

용 도

ShortString

255문자

2 ~ 256바이트

과거 버전과의 호환용

AnsiString

231문자

4 ~ 2기가바이트

8비트 ANSI 문자열 처리

WideString

230문자

4 ~ 2기가바이트

유니코드 문자열 처리

실제로 델파이에서는 주로 Boolean형이 많이 사용되고, 나머지 대수형은 주로 외부 언어나 모듈과 함께 사용된다.

▶ 열거형(Enum형)

열거형은 말 그대로 몇 개의 항목들을 나열하고 그 순서를 값으로 가질 수 있는 자료형이다.

Type
   Colors = ( Red, Blue, Yellow, Black, White)
   //Colors형의 변수는 열거한 5가지 값들 중에서 한가지를 가질 수 있다.
Var
   MyColor : Colors;
Begin
   MyColor := Red;
End;

▶ 부분 범위형 (Subrange형)

부분 범위형은 파스칼의 독특한 자료형이다. 부분 범위형은 다른 Ordinal형의 가능한 값 범위의 부분 집합을 의미한다. 예를 들어, 정수들 중에서 1부터 10까지의 정수만을 사용할 수 있는 범위형을 정의한다면 다음과 같이 할 수 있다.

Type
   MyNumber = 1..10;

▶ Ordinal형

Ordinal형이라는 것은 순서가 정해진 형을 말한다. 
오브젝트 파스칼에서는 Ordinal형으로는 Interger, Character, Boolean, Enumerated, Subrange형이 있다.
실수의 경우 순서가 있다고 할 수는 있지만, 한 값에 대해 바로 다음 순서가 되는 값을 정할 수가 없다. 그러나 정수형의 경우 항상 1씩 증가하므로 다음 값은 현재 값에 대해 +1을 하면 정할 수가 있다. 마찬가지로 앞의 값도 구할 수 있다.

Ordinal형의 기본연산

함수 이름

의 미

Ord(X)

X의 순서값(정수)를 돌려주는 함수

Pred(X)

X보다 한 순서 앞의 값을 돌려주는 함수

Succ(X)

X보다 한 순서 뒤의 값을 돌려주는 함수

High(변수 또는 형)

주어진 형에 대해 주어질 수 있는 가장 큰 값을 돌려주는 함수

Low (변수 또는 형)

주어진 형에 대해 주어질 수 있는 가장 작은 값을 돌려주는 함수

Inc(X)

X 값을 한 순서 뒤의 값으로 증가시켜 주는 프로시저

Dec(X)

X 값을 한 순서 앞의 값으로 감소시켜 주는 프로시저

표준 함수 중에서 정수형을 받아 들이도록 되어 있는 것들은 대부분 Int64형 값을 32비트로 잘라서 받아 들이게 되어 있다. 하지만, 앞에서도 얘기한 바와 같이 High, Low, Succ, Pred, Inc, Dec, IntToStr, IntToHex 등은 Int64형의 매개변수를 완전히 지원하지만 Ord함수는 받아들이지 못한다.

▶ 집합형 (Set)

집합형은 파스칼에 있는 독특한 자료형이다. 집합형은 기본이 되는 Ordinal타입의 256개 이하의 요소로 구성된 모음을 가질 수 있다.

Type
   TintSet = set of 1..250;
Var
   Set1, Set2 : TintSet;

Set1 := [1, 3, 5, 7, 9];
Set2 := [2, 4, 6, 8, 10];

▶ 배열형

배열은 같은 형의 원소로 구성된 순서가 있는 집합이다. 배열은 정적/동적으로 할당될 수 있다.

[정적배열]
정적배열은 보통 부분 범위형을 써서 다음의 형태로 정의한다.

Var
   MyArray : array[1..100] of integer;

이렇게 하면 정수의 배열이 1부터 100까지 100개가 MyArray라는 이름으로 만들어진다. 이 상태에서 MyArray[3]은 MyArray배열의 세번째 원소를 가리키게 된다. 기본형 자리에 다시 배열을 만들면 2차원 배열이 된다.

Var
   MyArray : array[1..10] of array[1..50] of Real;
   MyArray1 : array[1..10,1..50] of Real;
//MyArray와 MyArray1은 같은 뜻이다.

[동적배열]
동적배열은 델파이 4에서 새로 추가된 기능이다. 동적배열은 크기나 길이를 고정시키지 않는다. 대신에 배열에 값을 할당하거나 SetLength 프로시저를 통해 크기를 변경시킬 때에 동적으로 메모리를 재할당하게 된다.

Var
   MyDynamicArray : array of Real;

SetLength(MyDynamicArray, 20);

위와 같이 하면 MyDynamicArray에는 20개의 Real변수가 첨자 0부터 19까지 할당되게 된다. 동적 배열은 항상 0부터 시작하는 정수의 첨자를 갖는다. 2차원 이상의 동적배열을 만들고자 할때는 다음과 같이 array of …를 반복해서 사용한다.

Type
   TwoDimDynamicArray: array of array of Real;
Var 
   TwoDimReal : TwoDimDynamicArray;

SetLength(TwoDimReal, 20);   //전체 행을 10개로 지정
SetLength(TwoDimReal[0], 5);   //첫번째 행에 5개의 실수 배열

▶ 레코드형

C/C++ 등에서 struct형과 매우 유사한 구조로 여러 종류의 자료형으로 된 복합 자료형을 제공한다. 레코드형의 각각의 구성 요소가 되는 변수를 필드라고 한다.

Type
   레코드형이름 = record   // 예약어 record로써 레코드선언을 시작한다.
      필드1 : 형1;        // 각각의 필드마다 다른 형을 줄 수 있다.

      필드n : 형n;
end;                      // 레코드 선언의 끝
Type
   Tstudent = record
      Name : String;
      No : Integer;
      Rec : Double;
end;
이렇게 선언한 레코드의 멤버는 다음과 같은 방법으로 다룰 수 있다.
var
   MyRec : Tstudent;
Begin
   MyRec.Name := '홍길동';
   MyRec.No := 20;
   MyRec.Rec := 2.4;
End;
이것은 With문을 사용하여 다음과 같이 사용할 수 있다.
With MyRec do
Begin
   Name := '홍길동';
   No := 20;
   Rec := 2.4;
End;

▶ 포인터형

포인터는 다른 변수의 메모리 주소값을 갖는 변수이다. 포인터가 다른 변수의 주소값을 가지고 있으면 이를 포인터가 그 변수를 가리키고 있다고 말한다.

Procedure Tform1.Button1Click(Sender: Tobject);
Var
   X, Y : Integer;
   P : ^Integer;      // 정수형 변수를 가리키는 포인터 변수 P의 선언
Begin
   X := 17;
   Y := 0;
   P := @X;        // P에는 X의 주소가 대입된다.
   Y := P^;         // Y에는 P가 가리키고 있는 값이 대입된다.
   ShowMessage(IntToStr(Y));        //확인 : 17
End;

포인터 변수의 사용법은 다음과 같다.

[포인터 변수의 선언]
위 예의 4번째 줄처럼 포인터 변수를 선언할 때는 그 포인터 변수가 가리킬 대상 변수의 형 앞에 꺽쇠 표시(^)를 붙여서 포인터임을 표시한다.

[포인터 변수에 주소값의 대입]
포인터 변수를 사용하려면 먼저 어떤 주소값을 가지도록 해야 한다. 가리키고자 하는 변수 이름 앞에 @ 연산자를 쓰면 그 변수의 주소값이 얻어진다. 8번째 줄을 참고하라.

[포인터 변수를 통한 값의 참조]
포인터 변수 자체는 주소값이므로 그를 통해 간접적으로 대상을 가리켜야 한다. 이를 위해 형 선언시와는 반대로 꺽쇠 표시(^)를 포인터 변수 뒤에 붙인다 9번째 줄을 참고하라.

2.2 Punctuators

Delphi는 다음의 약속된 기호를 사용한다.

Brackets ('[]')
하나 또는 하나 이상의 brackets를 사용하여 Array 요소를 지정 하기 위해 사용한다.

Parentheses ('()')
조건부, Group expression!, Operator 또는 함수 호출이나 함수 매개변수 지정에 사용한다.

Braces ('}')
주석을 사용할 경우 '{' 부터 '}' 까지를 주석으로 본다.

Comma (',')
함수의 매개변수 요소를 구분하기 위한 구분자로 사용한다.

Semicolon (';')
모든 문장의 끝에 사용한다.

Colon (':')
Go to문과 함께 사용되는 분기 지점 표시를 위해 사용한다.

Slashes ('//')
해당 라인을 주석으로 본다.

Equal sign ('=') : 초기화
변수에 초기치를 할당하기 위해 사용한다.

 

2.3 연산자 / 제어문

연 산 자

▶ 산술연산자

연산자

연산

연산항 타입

결과형

+

덧셈

정수와 실수형에 적용

정수나 실수

-

뺄셈

정수와 실수형에 적용

정수나 실수

*

곱셈

정수와 실수형에 적용

정수나 실수

/

실수 나눗셈

정수와 실수형에 적용

실수

div

정수 나눗셈

정수형에만 적용

정수

mod

나머지

정수형에만 적용

정수

▶ 논리연산자

연산자

연산

연산항 타입

결과형

Not

Bitwise negation

정수형

정수형

And

Bitwise and

정수형

정수형

Or

Bitwise or

정수형

정수형

Xor

Bitwise xor

정수형

정수형

Shl

Bitwise shift left

정수형

정수형

Shr

Bitwise shift right

정수형

정수형

▶ 관계연산자

연산자

연산

결과형

=

같다

Boolean

<>

같지 않다

Boolean

<

작다

Boolean

>

크다

Boolean

<=

작거나 같다

Boolean

>=

크거나 같다

Boolean

▶ 문자열연산자

연산자

연산

적용 대상

결과형

+

문자열 결합

문자열형에 적용

String

제 어 문

▶ 제어문의 종류

[판단문]
IF 문
CASE 문

[반복문]
FOR 문
While .. Do 문
Repeat .. Until 문

[제어문 제어요소]
Break 문
Continue 문

▶ IF문

if문에는 2가지 형식이 있다. if .. then과 if .. then .. else.
조건에 만족하는 경우 해당 문장을 수행하고, 조건에 만족하지 않는 경우는 해당 문장을 수행하지 않고 pass한다. 하나의 조건 이외의 다른 조건에 대한 부분 조건의 수행을 달리 하기를 원하는 경우 else if문을 사용한다.
If … then의 문법은 다음과 같다.

if <condition1> then
begin
   statement1-1;
   statement1-2;
end
else if <condition2> then
begin
   statement2-1;
   statement2-2;
end;

Ex1.

if i < 10 then
begin
   showmessage('i는 10보다 작다.');
end
else if i > 10 then
begin
   showmessage('i는 10보다 크다.');
end;

Ex2.

if i <= 10 then
begin
   if i = 10 then
      showmessage('i는 10이다')
   else
      showmessage('i는 10보다 작다.');
end
else if i > 10 then
begin
   showmessage('i는 10보다 크다.');
end;

If 문은 위의 예처럼 중첩될 수도 있다.
Begin … End 사이에 들어가는 문장의 수가 하나라면 위의 예제처럼 Begin … End 를 생략해도 좋다.
또 한가지 주의할 점은 Else 앞에는 세미콜론(;)을 하면 안된다는 것이다.

▶ Case문

Case문은 복잡한 if 조건을 읽기 좋게 만들 수 있는 대안이다.

Case <variable> of
   <value1> : <하나 또는 하나 이상의 문장>;
   <value2> : <하나 또는 하나 이상의 문장>;
Else
   <하나 또는 하나 이상의 문장>
   //Case값의 어느 경우에도 해당되지 않는 모든 값에 대해 적용된다.
End;

위의 은 Ordinal타입이다(String타입은 유효하지 않다.) 
에 나타나는 각 값은 case 문에서 유일해야 한다.
값 중에 과 같은 값을 갖는 것이 없다면 else절에 있는 statement가 실행된다.

Ex.

Case i of
   1..5 : Caption := 'Low';
   6..9 : Caption := 'High';
   0, 10..99 : Caption := 'Out of range';
Else
   Caption := ' ';
End;

▶ For … Do 문

제어변수에 의해 지정된 수만큼 반복작업을 수행한다.

For <제어변수> := <초기값> to <최종값> do
   statement;
또는
For <제어변수> := <초기값> downto <최종값> do
   statement;

For문장은 <초기값>을 <제어변수>에 할당한다. 그리고 나서 순환 후에 <초기값>의 값을 증가시키거나 감소시키면서 반복적으로 statement를 실행한다. ( for … to 는 증가 / for … downto 는 감소 )

Ex.

Sum := 0;
For X := 1 to 10 do
Begin
   Sum := Sum + X;   
End;
Edit1.Text := IntToStr(Sum);   //Sum은 1부터 10까지의 합

▶ While문

While문은 컨트롤하는 Boolean식이 참이면 단일의 문장이나 begin … end 블록속에 묶인 일련의 문장들을 반복한다. 만일 Boolean식이 거짓이면 While문의 코드는 한번도 실행되지 않을 수 있다.

While <condition> do
begin
   Statement1;
   Statement2;
end;

Ex.

Sum := 0;
X := 1;
While X <= 10 Do
begin
   Sum := Sum + X;
   Inc(X);
end;
Edit1.Text := IntToStr(Sum);   //Sum은 1부터 10까지의 합

이 때, For문은 X를 자동으로 증가시키므로 X를 증가시키는 부분이 필요없었지만, While문은 X를 증가시키는 부분이 필요하다. ▶ (Inc(X))

▶ Repeat문

반복수행을 필요로 하는 문장에 대해 반복수행문을 수행한 후 조건검사를 하기 때문에 반복수행문을 적어도 한번은 수행을 한다.

Repeat
   statements;
Until <condition>;

Ex.

Sum := 0;
X := 1;
Repeat
   Sum := Sum + X;
   Inc(X);
Until X > 10;
Edit1.Text := IntToStr(Sum);   //Sum은 1부터 10까지의 합

▶ Break와 Continue

두 프로시저 Break와 Continue는 while, repeat, 그리고 for 블록의 실행을 제어한다.
Break문은 프로그램 흐름으로 하여금 반복 구조를 빠져 나오고 Continue문은 루프의 다음 문장을 수행하도록 만든다.

Ex.

X := 1;
// While True는 무한루프이지만 break를 사용하면 빠져나올 수 있다.
While True do
begin
   Inc(X);
   If X>10 then 
   begin
      Showmessage('X가 10보다 크다');
      Break;
   end;
 end;

For X := 1 to 10 do 
Begin
   // X가 3으로 나눌 수 있는 수이면 메시지를 뿌려주고 For문의 End절로 넘어간다
   if (X mod 3)=0 then
   begin
      Showmessage('X는 3의 배수이다');
      Continue;
   end;
   //Continue문에 걸리면 이문장은 수행하지 않는다.
   Edit1.Text := Edit1.Text + IntToStr(X); 
end;


 

2.4 프로시저와 함수

오브젝트 파스칼의 프로시저와 함수의 차이점은 단 한가지로 규정지을 수 있다.
프로시저는 결과를 돌려주지 않고, 함수의 결과를 돌려준다는 점이다. 그러나 이 둘 사이에 커다란 구분은 갖지말자. 단지 결과값의 처리를 어떻게 하느냐가 다를 뿐이니깐…

프로시저와 함수를 호출하려면 먼저 필요한 프로시저와 함수를 정의해야 한다.

프로시저와 함수의 정의는 크게 두 부분으로 나누어 진다. 한가지는 프로그램의 다른 부분에서 해당 프로시저와 함수의 선언을 정의하는 부분이고, 다른 한 부분은 실제 프로시저나 함수의 내용을 적는 부분(몇 개의 문장들로 이루어진 블록)이다. 
프로시저의 구성은 다음과 같다.

Procedure <프로시저 이름>(매개변수목록);
[선언부]
begin
   [문장들]
end;

Procedure는 프로시저의 시작임을 알리는 예약어 이며, <프로시저 이름>은 프로시저를 호출할 때 사용되는 이름이다. (매개변수목록)은 프로시저를 호출한 쪽에서 프로시저에게 넘겨주는 입력값들의 목록이고, [선언부]는 프로시저에서 사용할 변수나, 상수, 레이블들을 선언하는 곳이다. 만약 사용되는 변수나 상수들이 없다면 생략가능하다. 마지막 [문장들]은 실제 프로시저의 행위를 정의하는 문장들이다.

다음의 예는 문자열의 순서를 반대로 바꾸고 그것이 폼 상의 한 메모 객체에서 디스플레이되는 프로시저를 보여주고 있다.

Procedure ReverseStr(s: String);
Var
   r: String;
   i: Byte;
Begin
   // 문자열 변수 r에 널(null)을 설정한다.
   r := ' ';
   // 문자열을 마지막 문자부터 첫번째 문자까지 반복한다.
   for i := length(s) downto 1 do
      // s내의 문자를 r의 끝에 연결한다.
      r := r + s[i];
   // Memo객체를 비우고 결과를 나타낸다.
   Form1.Memo1.Lines.Clear;
   Form1.Memo1.Lines.Add(r);
End;

프로시저 안에 하고싶은 모든 내용(문자를 꺼꾸로 출력하기)을 넣었다. 
이제 문자를 꺼꾸로 출력하고 싶으면, ReverseStr이라는 프로시저를 부르기만 하면된다.

함수 선언은 프로시저 선언과 같지만 함수 선언이 함수가 리턴시키는 값의 타입을 포함시켜야 한다는 점이 다르다.

Function <함수 이름>(매개변수목록):<리턴타입>;
[선언부]
begin
   [문장들]
end;

다음 함수 Reverse는 앞의 예와 같지만 Reverse가 반대순서로 바뀐 문자열을 메모에 직접 디스플레이 시키는 대신에 리턴시킨다는 점이 다른다.

Function Reverse(s: String) : String;
Var
   r: String;
   i: Byte;
Begin
   r := ' ';
   For i := length(s) downto 1 do
      r := r + s[i];
   Reverse := r;   // Result := r 도 같은 결과를 얻을 수 있다.
End;

그러면, 함수와 프로시저를 호출하는 문장을 살펴보자.

Procedure Tform1.Button1Click(Sender: Tobject);
Begin
   
   ReverseStr(Edit1.text);                     // 프로시저를 호출하는 경우
   Memo1.Lines.Add(Reverse (Edit1.Text));      // 함수를 호출하는 경우
   
End;

Reverse함수에서는 메모 컴포넌트에 보여주지 않고 결과값만을 받기 때문에, 위와 같이 메모 컴포넌트에 결과값을 넣는 부분이 들어간다.

▶ 매개변수 전달방법
프로시저와 함수는 모두 매개변수를 받을 수 있다. 실제 매개변수의 수와 종류는 일치해야 한다.
각 방법의 매개변수를 유심히 살펴보자.

Call by value
이 방법에서는 변수를 값으로 전달하는 방식으로 컴파일러는 변수의 값을 복사하여 원래 값이 아니라 복사한 값이 전달된다. 그러니까 윈도우 탐색기에서 파일을 복사하여 다른 디렉토리에 붙여쓰기를 하는 것처럼, 변수의 내용을 복사하여 프로시저의 매개변수로 붙여쓰기하는 것과 마찬가지라고 생각하면 좋을 듯 싶다.
그러면 프로시저내에서 이 매개변수를 지지고 볶든 간에, 원래 변수에 저장되어 있던 값들은 그대로이다.

Procedure add(x,y : integer);

Call by Reference
이 방법은 변수의 값을 복사하여 쓰는 것과는 달리, 방번호는 같은데 변수와 매개변수의 사용하는 이름이 다르다고 생각하자. 100번 방에 있는 값을, 호출할 때는 A(변수)라고 부르고 프로시저에서는 A1(매개변수)이라고 부른다고 가정하면 같은 방에 있는 값이므로 A1이 바뀌면 A도 자연히 따라 바뀌게 되는 것이다.

Procedure ReadData(Var rec : string);

Call by Const
상수로 매개변수를 전달하는 것은 프로시저 내에서 값을 변경하지 못하도록 컴파일러에 알린다.
아래의 예처럼 매개변수 s를 const로 전달하는 경우는 WriteData라는 프로시저 안에서 s의 값을 바꿀 수가 없다. Read Only 변수라고 생각하면 됨.

Procedure WriteData(const s : string);


2.5 클래스 구조

오브젝트 파스칼에서 가장 큰 특징은 클래스라는 자료 구조를 가지고 있다는 점이다.
클래스의 구조에 대해 알기 위해서 먼저 여러 개념에 대한 이해가 필요하다. 아래의 내용이 좀 이해하기 힘들더라도 일단 천천히 읽어 내려가기 바란다.

▶ 오브젝트, 인스턴스, 클래스
오브젝트 파스칼에서 오브젝트는 레코드형 변수와 마찬가지로 여러 개의 요소들로 이루어진 자료들의 모음이다. 레코드에서 각각의 변수 요소들을 필드라고 하듯이 오브젝트에 대해서도 각각의 변수 요소들을 필드라고 한다. 오브젝트의 필드들은 적절하게 외부에 가려져 있으며, 보통 이들을 직접 참조하는 대신 오브젝트에서 제공하는 프로시저를 호출하여 작업을 처리한다. 이런 프로시저들을 메소드라 하고, 오브젝트는 이와 같이 상태를 보존하는 필드와 동작을 행하는 메소드로 구성된다. 또한 필드에 대한 무책임한 참조로부터 보호하기 위해 적절한 참조 방법을 제공하기도 하는데, 이를 프로퍼티라고 하고, 한 클래스에 속한 필드, 메소드, 프로퍼티 등을 그 클래스의 멤버라고 한다.

클래스는 레코드형과 마찬가지로 일종의 자료형이다. 클래스형으로 정의된 변수는 그 자체로 아직 오브젝트가 되는 것은 아니다. 오브젝트가 되어 메모리에 자리잡기 위해서는 인스턴스화하는 작업이 필요한데, 이를 생성(Create)이라고 한다. 이렇게 생성된 오브젝트를 오브젝트 또는 인스턴스라고 부른다. 오브젝트는 클래스에서 정의한 필드, 프로퍼티, 메소드를 가지고 있게 된다. 오브젝트의 사용이 끝나면 메모리에서 삭제하는 것을 파괴(Destroy)한다고 한다.

클래스를 선언할 때 항상 그 클래스의 모든 멤버들을 선언해야 하는 것은 아니다.
기존에 선언된 클래스를 계승받아 새로운 클래스에 필요한 멤버들만 선택해서 선언하면 된다. 이때, 새로 선언된 클래스를 후손(Descendant)이라 하고, 계승받은 기존의 클래스를 조상(Ancestor)이라고 부른다. 모든 클래스의 선언시에 항상 계승받을 선조 클래스를 지정하게 되어 있으면 생략하면 기본적으로 Tobject 클래스로부터 직접 계승받는 것으로 된다. Tobject 클래스는 모든 클래스의 가장 높은 조상이 되며, 모든 오브젝트가 갖춰야 할 기본적인 기능들을 구현해 놓은 클래스이다. 후손 클래스는 새로운 멤버를 계속 추가할 수만 있고 제거할 수는 없다.

클래스의 선언은 다른 형 선언과는 달리 유닛이나 프로그램의 가장 바깥에서만 행할 수 있다. 보통 유닛의 인터페이스 절에 선언하게 된다.
다음은 클래스 사용법의 예이다.

Type
   TMyClass = class(TObject)          // TMyClass 클래스의 선언----------------(1)

end;

var
   MyObject : TMyClass;              // TMyClass형의 변수 MyObject선언---------(2)

MyObject := TMyClass.Create;         // MyObject의 생성------------------------(3)

MyObject.Free;                       // MyObject의 파괴------------------------(4)

TMyClass는 TObject로 부터 계승받았다.(1) 그러므로 선조는 TObject가 되고 후손은 TMyClass가 된다. 그리고 (2)에서 TmyClass형으로 변수 MyObject를 선언하였다. 아직까지 오브젝트가 생성된 것은 아니다. 단지 선언만 했을 뿐이다. (3)에서 마침내 생성을 시켜 주었다. 그리고 필요가 없게 되면 (4)에서처럼 파괴를 시켜주어야 한다.
TMyClass형의 Create라는 메소드는 위에서 만들어 주지 않았다. 그러면 어떻게 사용할 수 있었을까?
바로 TObject에 기본적으로 정의되어 있으므로 TMyClass는 TObject에서 상속받기만 하면 TObject의 메소드를 마치 자신의 것인양 쓸 수 있게 되는 것이다.

▶ 클래스멤버의 가시성 지정
앞에서 클래스 안의 필드들이 외부에서 적절하게 가릴 수 있다고 언급한 바 있다. 사실 오브젝트 파스칼은 클래스 선언에 있어서 필드만이 아니라 클래스의 모든 멤버에 대해 외부에서 그것을 참조할 수 있는지의 여부를 상세하게 지정할 수 있는 기능을 가지고 있다. 
아래에 설명하는 총 4개의 지시자로 클래스 각각의 멤버의 가시성을 지정할 수 있다.

[Public]
Public지시어는 특별한 제한을 갖고있지 않는 필드나 메소드를 나타낸다. 메소드가 Public으로 선언되면 그 클래스의 어떤 사용자도 사용할 수 있다.

[Private]
Private 지시어는 클래스가 정의된 Unit의 외부에서 접근하거나 알아보지 못하도록 프로시저, 함수나 필드를 정의하기 위해 사용된다. Private로 선언된 식별자의 범위는 선언을 포함하는 모듈로 제한된다. 같은 Unit에서 정의된 개체는 서로의 Private 필드와 메소드를 사용할 수 있다.

[Pretected]
Protected지시어는 Public과 Private중간형태로 파생 클래스이면 그 상위 클래스의 모든 정보를 참조하여 사용할 수 있다.

[Published]
의미는 Public과 같으나 해당 개체의 Object Inspector에 나타나는 속성을 정의하는 곳이다.

Unit UnitA;

type
   TA = Class(Tobject);
   Private
      PriA : Integer;
   Protected
      ProA : integer;
   Public
      PubA : Integer;
   Procedure Access;
End;
Procedure TA.Access;
Begin
   PriA := 1;     // OK
   ProA := 2;     // OK
   PubA := 3;     // OK
End;
Unit UnitB;
Uses UnitA;

type
   TB = Class(TA);
   Public
      Procedure Access;
End;
Procedure TB.Access;
Begin
   PriA := 4;      // 오류 발생 : Undeclared identifier
   ProA := 5;      // OK
   PubA := 6;      // OK
End;

UnitA에 선언된 TA클래스의 메소드에서는 TA클래스에서 선언된 모든 멤버(private, protected, public)를 참조할 수 있다. 
그러나 TA클래스에서 계승받아 UnitB에 선언된 TB클래스는 TB클래스가 후손 클래스이고 TA클래스는 선조 클래스이다. 후손 클래스에서는 선조 클래스의 멤버 중 Private영역의 멤버를 참조하지 못한다.
따라서 TA 클래스의 Private에 선언되어 있는 PriA는 TB클래스에서 사용할 수 없다. 당연히 에러가 발생할 수 밖에…..
그리고, 또 한가지 유의해야 할 점은 TA클래스의 변수 A를 생성하여 사용한다고 가정할 때, TA클래스의 사용자는 TA클래스에서 public으로 선언한 멤버만을 참조할 수 있다.(아래)

Unit UserUnit;
uses UnitA;

procedure TForm1.Button1Click(Sender: Tobject);
var
   A : TA;
Begin
   A := TA.Create;
   A.priA := 7;              // 오류 발생 : Undeclared identifier
   A.proA := 8;              // 오류 발생 : Undeclared identifier
   A.pubA := 9;              // OK
   A. Free;
End;


 

2.5 OOP(Object Oriented Programming)의 특징

▶ Inheritance(계승)

클래스가 다른 클래스에서 속성을 상속 받은 다음 고유의 속성을 추가하도록 하는 능력.
메소드에서 예약어 inherited가 다음과 같이 메소드 이름과 함께 나타나게 되면 이것은 평범한 메소드의 호출을 의미한다. 다만, 현재 클래스에 있는 메소드를 호출하는 것이 아니라 선조 클래스의 해당 이름으로 된 메소드를 호출하게 된다. (바로 직계 선조에 그런 메소드가 없다면 연속해서 선조의 선조를 차례대로 찾아 호출한다.)

begin

   inherited AMethod(…);         // 선조 클래스의 AMethod를 호출한다.

또한, 메소드 이름없이 다음과 같이 inherited가 나타나면 그 메소드와 같은 이름으로 된 선조 클래스의 메소드를 호출하게 된다.

Procedure TMyClass.BMethod(AParam: Atype);
Begin
   Inherited;       // TMyClass의 선조 클래스의 BMethod(AParam)를 호출한다.
End;

▶ Polymorphism(다형성)

다형성은 다양한 객체가 동일한 메시지에 적절히 반응할 수 있다는 것을 의미한다. 예를 들면, 다형성이 없을 때에는 다양한 개체들로 하여금 멍멍짓도록(Bark) 다음과 같은 코드를 작성해야 한다.

Doberman.DobermandBark;
IrishSetter.IrishSetterBark;
Spaniel.SpanelBark;

다형성이 있으면, 각 타입의 객체를 위해 다른 메소드를 갖고 있어야 할 필요는 없다. 단순히 다음과 같은 코드를 작성하기만 하면 된다.

Doberman.Bark;
IrishSetter.Bark;
Spanel.Bark;

프로그래머들은 Doberman의 Bark가 Spaniel의 Bark와 다르다는 것을 알아야 할 필요는 없다. 모든 프로그래머들이 알아야 하는 모든 것은 개가 멍멍짓기를 원한다면 단지 Bark 메소드를 호출하기만 하면 된다는 점이다.

▶ Encapsulation(캡슐화)

개체의 사용자로부터 자세한 구현 내용을 숨기는 능력.
캡슐화는 개체를 사용한 응용프로그램에서 변경없이 개체를 수정 가능하게 한다.
OOP의 목표중 하나는 블랙박스인 객체를 구축하는 것이다. 자동차와 마찬가지로 여러분은 블랙박스안에는 무엇이 들어있고 그것이 어떻게 작동하는 지 알지 못할 수도 있다.
여러분이 알아야 할 모든 것은 그것이 작동하며 그것을 운전할 만한 충분한 기술을 갖고 있다는 사실이다. 만약 우리가 자동차를 운전하기 위해서 연소 엔진의 내부 동작에 대해서 모든 것을 알아야 한다면, 고속도로는 교통체증이 훨씬 줄어들 것이다.

▶ 코드에서 개체유형 정의

Type
   TPerson = class(TObject);
   Protected
      BirthDate : String;
      MaritalStat : Integer;
      Name : String;
End;

▶ 코드에 계층과 Inheritance 요소 추가

Type
   TEmployee = class(TPerson);
   Protected
      EmpNo : Integer;
      BaseRate : Real;
   Public
      Function Salary : real; virtual;
End;

▶ 프로그래밍 개체에서 Polymorphism

Type
   TsalesEmployee = class(Temployee)
   Protected
      CommisionRate : Real;
      SalesAmt : Real;
   Public
      Function Salary : Real; override;
End;
Type
   ThourlyEmployee = class(Temployee)
Protected
   Hrs : float;
Public
   Function Salary : real; override;
End;
Implementation
Function TsalesEmployee.Salary : Real;
Begin
   Salary := (SalesAmt * CommisionRate)+(BaseRate*40.0);
End;
Function ThourlyEmployee.Salary : Real;
Begin
   Salary := Hrs * BaseRate;
End;

▶ 프로그램에서 Polymorphism 사용

Var
   Jim : SalesEmployee;
   Jeff : HourlyEmployee;
Implementation
Procedure PrintSalary(Var Emp : Employee);
Begin
   Writeln(Emp.Salary);
End;
// Call PrintSalary…
   PrintSalary(Jim);
   PrintSalary(Jeff);

▶ 요약

OOP이면의 가장 큰 구동력중 하나는 코드 재사용이다. 상속 계층은 다른 프로그래밍 환경보다 쉽게 코드 재사용을 촉진하는 구조를 형성한다. OOP는 당연히 개발자들로 하여금 상속 계층의 측면에서 생각하도록 만들며 그 결과 개발자들은 코드를 공유하는 클래스 계층을 작성하는 경향이 있다.

OOP기법을 이용하여 개발한 어플리케이션은 유지관리하기가 더 쉽다. 만약 어플리케이션이 추가 기능이 필요하면, 기존 코드를 변경할 필요는 없다. 단순히 새로운 세대의 클래스를 작성하고 나서 구 작동기능을 상속하고 새 작동기능을 추가하거나 구 작동기능을 재정의한다. 사실상, 이 기능은 개발자들에게 매우 중요하므로 여러분은 세개의 클래스를 작성하고 나서 그것들을 Component 팔레트에 위치시킴으로써 Delphi 어플리케이션 환경을 완전히 재정의할 수 있다는 것을 알게 될 것이다.

Delphi는 OOP의 가장 중요한 부분인 상속, 다형성, 그리고 캡슐화를 완전히 지원한다. 비록 Delphi를 사용하기 위해서 OOP를 이해해야 할 필요는 없지만 그것을 이해하는 프로그래머들은 우수하고 복잡한 어플리케이션을 구축할 수 있을 뿐만 아니라 Delphi VCL 클래스 계층에 그들 자신의 새로운 클래스를 추가함으로써 Delphi 개발 환경을 향상시킬 수 있을 것이다. 이런 이유 때문에 OOP에 대한 지식과 경험이 있는 개발자들은 첨단 데이터베이스 어플리케이션을 작성하는 데 중요한 이점을 갖게 될 것이다.

반응형

댓글