class
먼저 class
에 대해 이야기를 좀 하자면
object pascal 의 class
는 단순히 record
의 확장판이다.
마치 C
의 struct
에 함수포인터로 객체지향을 흉내낸것 처럼...
생성자와 소멸자도 직접 호출해줘야 한다. 생성자 직접 호출로 인한 장점은 없는것 같지만...소멸자 직접 호출로 인한 장점은 존재한다.(물론 단점이 더많다. 불편해!!!)
simple constructor,destructor example
program test;
{$mode objfpc}
type
{전통적으로 record나 class명 앞에 T를 붙인다.}
{기본적으로 TObject를 상속받으나 가독성을 위해 직접 써주자}
TIntegerPair=class(TObject)
public
first:longint;
second:longint;
constructor Create;
destructor Destroy;override; {소멸자는 웬만하면 override여야 함}
end;
constructor TIntegerPair.Create;
begin
inherited Create;{부모의 생성자를 먼저 호출해줘야함}
self.first:=0; {사실 모든 변수는 0으로 초기화 되어있다}
self.second:=0;
end;
destructor TIntegerPair.Destroy;
begin
inherited Destroy; {소멸자의 끝에서 부모의 소멸자를 호출해준다}
end;
{==================================================}
var
p:TIntegerPair;
begin
p:=TIntegerPair.Create; {이렇게 생성자 호출}
p.first:=4;
writeln(p.first,' ',p.second);{4 0 이 출력}
p.Destroy; {소멸자를 직접 호출해줘야함}
end.
마치 C++
에서 소멸자를 virtual
으로 선언 하듯이 여기서도 override
키워드를 사용한다.
또한 class
는 겉으로 보기엔 record
의 확장형이지만, 바인딩, 다형성등의 이유로 class
는 사실 모두 포인터이다.
실제로 sizeof(p)
를 출력하면 4
가 출력되며 생성자 없이 사용할경우 (당연하겠지만) 초기화는 커녕 빈 포인터에서 사용한 꼴이 되므로 RTL이 발생하게 된다.
object pascal
역시 C++
, Java
와 마찬가지로 접근 제한자(pascal 에서는 가시성 속성 이라 칭함) 이라는 것이 있다.
보통 public
, protected
, private
이 3가지가 존재한다.
C++
은 기본적으로 private
Java
는 기본적으로 default
이지만
object pascal
은 {$M+}
일 경우 기본적으로 ==published
== 라는 속성을 가진다.
{$M-}
일 경우 ==public
== 속성을 가진다.
근데 이건 그냥 문서에 나온 이야기일 뿐이고, 실제로는 그냥 public
으로 사용하면 된다.
필드는 간단하게 말해 멤버변수에 해당한다. 더이상 설명할 것이 없고, 대부분의 필드는 private
이다.
메소드 역시 간단하게 말해 멤버함수에 해당한다.
메소드에서는 부모 클래스의 함수를 호출할 수 있는 ==inherited
== 키워드가 존재한다. java
의 super
에 해당.
C++
,Java
의 this
에 해당하는 ==self
== 키워드 역시 존재한다.
프로퍼티는 일반적은 객체지향언어에서 setter/getter
에 해당한다.
필드에 바로 값을 넣지않고 setter/getter
를 사용함으로써 좀더 안전한프로그래밍을 할 수 있다.
C++
이 메소드 앞에 static
을 붙이듯이 메소드(선언,정의 모두) 앞에 class
를 붙이면 된다.
이건 그냥 직접 코드를 보고 이해하는게 빠르다. (참 프로그래머의 자세)
program generic_pair;
{$mode objfpc}
type
generic TPair<T1,T2>=class(TObject)
private
_first:T1;
_second:T2;
public
constructor Create;
constructor Create(val1:T1;val2:T2);
destructor Destroy;override;
function GetFirst:T1;
procedure SetFirst(val:T1);
function GetSecond:T2;
procedure SetSecond(val:T2);
property First:T1 read GetFirst write SetFirst;
property Second:T2 read GetSecond write SetSecond;
end;
constructor TPair.Create;
begin
{부모생성자와 [이름,인수,반환] 이(가) 같을경우 생략가능}
inherited;
end;
constructor Tpair.Create(val1:T1;val2:T2);
begin
{이 경우에는 Create생략 불가}
inherited Create;
self._first:=val1;
self._second:=val2;
end;
destructor TPair.Destroy;
begin
inherited;
end;
function TPair.GetFirst:T1;
begin
Result:=self._first;
end;
procedure TPair.SetFirst(val:T1);
begin
self._first:=val;
end;
function TPair.GetSecond:T2;
begin
Result:=self._second;
end;
procedure TPair.SetSecond(val:T2);
begin
self._second:=val;
end;
{============================================================}
type
mypair=specialize TPair<longint,extended>;
var
a:mypair;
begin
a:=mypair.Create(2,3.0);
writeln(a.First,' ',a.Second);
a.Destroy;
end.
C++
은 template
사용시 타입 재정의를 하지 않는다.
내가 예전에 만든 BCDL
이라는 라이브러리가 있는데 이는 순수 C언어
의 define
전처리기를 통해 C++
의 template
을 흉내낸 라이브러리이다.
당연하겠지만 define
이기 때문에 전방선언을 피할수 없다.
C++
은 이러한 기능을 컴파일러 수준에서 지원하는데, 해당 템플릿의 선언부를 찾아서, 그에 맞는 코드를 알맞은 위치에 풀어서 적어야 한다. (이런것 때문에 C++ 컴파일러 만드는것이 더럽게 어렵다)
사실 다른언어들이 이러한 기능을 지원하지 않는 이유는 단순히 컴파일러만드는것이 더럽게 어렵기 떄문이다.
object pascal
역시 쉬운길을 택한건지 , 기존의 방식을 고수한건지 알수는 없지만 전방선언을 해야한다.
포인터를 쓸려면 전방선언을 해야하지않는가?
프로시저 타입과 메소드 타입
이 부분은 이해가 약간 어려웠는데, 예전에 C++
의 nullptr
에서 공부한게 도움이 되었는지 그것을 생각하고 이해가 되었다.
http://hackspring.blogspot.kr/2016/06/c11-nullptr.html 의 끝부분에 설명되어있다. 이게 더 어렵다.
우선 알아둬야 할것이, C/C++
에서는 함수의 이름은 함수의 주소가 되지만, pascal
에서는 그렇지 않다.
인자가 없는 함수,프로시저는 괄호없이도 호출될수 있으니까!!!
pascal에서 함수의 주소를 얻기위해서는 변수와 마찬가지로 @를 붙여주어야 한다. (일관성 좋고!)
우선 코드부터 보자. 프로시저 타입이나 함수타입이나 그게 그거다.
procedure type
type
ProcedureCompareFunc=function(a,b:pointer):longint;
method type
type
MethodCompareFunc=function(a,b:pointer):longint of object;
차이점이라곤 뒤에 of object
가 붙은것 뿐이다.
프로시저 타입은 일반적인 프로시저/함수의 주소를 담을수 있는 자료형이다.
메소드 타입은 말 그대로 메소드의 주소를 담을 수 있는 타입이다.
type
Sorter=class(Tobject)
{...}
cmp1:ProcedureCompareFunc;
cmp2:MethodCompareFunc;
{...}
end;
{============================================}
Rect=class(Tobject)
function compare(a,b:pointer):longint;
end;
function Rect.compare(a,b:pointer):longint;
begin
Result:={...};
end;
function compare(a,b:pointer):longint;
begin
Result:={...};
end;
var
st:Sorter;
r:Rect;
begin
{...}
st.cmp1:=@compare; {OK 프로시저타입에 프로시저를 대입}
st.cmp2:=@r.compare;{OK 메소드타입에 메소드를 대입}
{거꾸로 하면 타입 에러남}
end;
연산자 오버로딩
여기에 대해서는 삽질을 정말많이 했다. 예전에 C++연산자 오버로딩 공부할때는 어려웠는데 이제는 그게 모국어가 된듯....
전역 연산자 오버로딩
type
mypair=specialize TPair<longint,extended>;
{============요렇게 요렇게==============}
operator+(a,b:mypair):mypair;
begin
Result:=mypair.Create;
Result.First:=a.First+b.First;
Result.Second:=a.Second+b.Second;
end;
{====================================}
var
a,b,c:mypair;
begin
a:=mypair.Create(2,3.0);
b:=mypair.Create;
b.First:=5;
b.Second:=2.4;
c:=a+b;
writeln(c.First,' ',c.Second);
a.Destroy;
end.
위 처럼 아주 간단하다. 프로시저/함수를 짜는것 처럼 짜면된다.
옛날에는 연산자 오버로딩 가능한 연산자들보고 이걸 외워야 하나....하고 답답했던적이 있었지만,
실제로 연산자 오버로딩을 구현하는일은 극히 드무니...괜스레 self 두통 갖지말자.
Result
난 함수의 반환값을 저장할때 Result
는 Delphi에서만 되는줄 알고 있었는데,
이건 object pascal
의 문법이였다. (pascal 에서 쓰니까 안된거였다.)
free pascal compiler
에서 {$mode objfpc}
를 사용하면 object pascal 로 컴파일 할 수 있다.
Result
는 확장구문 {$X+}
가 정의되어 있어야 사용할 수 있다, 그러나 기본적으로 선언되어 있다.
만일 Result를 사용하기 원치 않는다면 {$X-}
를 쓰면 되는데, 그렇게 하면 표준 라이브러리들이 컴파일 에러가 생길수 있다.