[object pascal] class,operator overloading, Result

spring·2020년 11월 9일
0

class

먼저 class 에 대해 이야기를 좀 하자면

object pascalclass는 단순히 record의 확장판이다.

마치 Cstruct에 함수포인터로 객체지향을 흉내낸것 처럼...

생성자와 소멸자도 직접 호출해줘야 한다. 생성자 직접 호출로 인한 장점은 없는것 같지만...소멸자 직접 호출로 인한 장점은 존재한다.(물론 단점이 더많다. 불편해!!!)

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== 키워드가 존재한다. javasuper에 해당.

C++,Javathis에 해당하는 ==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++연산자 오버로딩 공부할때는 어려웠는데 이제는 그게 모국어가 된듯....

아까 작성한 generic pair에서 주 블록만 수정한것이다.

전역 연산자 오버로딩

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-}를 쓰면 되는데, 그렇게 하면 표준 라이브러리들이 컴파일 에러가 생길수 있다.

profile
Researcher & Developer @ NAVER Corp | Designer @ HONGIK Univ.

0개의 댓글