본문 바로가기
Delphi, RadStudio

[개발/delphi] 델파이 TList의 활용

by SB리치퍼슨 2015. 7. 3.

TList의 활용.

많은 데이터들을 관리하기 위해서는 리스트를 사용해야 한다.

C++과는 다르게 델파이는 TList라는 클래스가 이를 도와준다.

실제로 TList는 다른 비슷한 기능의 부모 클래스이기도 하다.

일단 데이터를 관리하기 위해서 따로 유닛을 만들어 관리하는 것이 편리하다. 폼의 중간에 던져 놓으면 참 보기 힘들다.

기본 뼈대를 먼저 만들어보자.

unit uMyDataList;
 
interface
 
uses classes;
 
type
  TMyDataList = class(TList)
    private
    protected
    public
  end;
 
implementation
 
end.

짠, 이렇게 간단하다.

이제 담은 동적인 데이터의 형식을 만들어여 한다. 왜냐면 TList는 포인터만 가지고 있을 뿐 데이터 자체를 가지지 않는다.
알고보면 당연한 것이다.

  // type 절에 추가된다.
  PMyDataItem = ^TMyDataItem;  // 포인터 형의 데이터 형식을 정의
  TMyDataItem = record         // 데이터 형식의 정의
     value : integer;          // 넣고자 하는 데이터 형식을 넣는다. 임의로 내용을 정의했다.
    offset : integer;
    others : pointer;         
  end;

데이터 형식의 정의에 대해서는 더 설명하지는 않겠다.

데이터들은 동적으로 관리된다.

자, 이제는 데이터를 추가/삽입/삭제을 하는 루틴이 필요하다.

// interface
...
type
  TMyDataList = class(TList)
    private
    protected
      function GetItem(Index : integer):PMyDataItem;
      procedure SetItem(Index : integer; Item : PMyDataItem);
    public
      // destructor
      // destructor Destroy; override; // 소멸자
      // functions
     function AddItem(Value, offset : integer; others : pointer):integer; // 데이터 추가
      procedure InsertItem(Index : integer; Value, offset : integer; others : pointer); // 데이터 삽입
      procedure DeleteItem(Index : integer); // 데이터 삭제
      procedure Clear; override; // 리스트를 모두 삭제
      //
      property ItemList[Index : integer]:PMyDataItem read GetItem write SetItem; // 해당 데이터이 포인터를 가져온다
  end;
...

아쉽게도 override를 할 수 있는 함수가 몇개 없다.

위와 관련된 부모 클래스 TList의 선언은 아래와 같다. 한번 확인해보자.

    // destructor Destroy; override; // 소멸자. TObject에서 상속된다.
    function Add(Item: Pointer): Integer; // 데이터 추가
    procedure Clear; virtual; // 데이터를 모두 삭제
    procedure Delete(Index: Integer); // 데이터를 삭제
    procedure Insert(Index: Integer; Item: Pointer); // 데이터 삽입
    property Items[Index: Integer]: Pointer read Get write Put; default; // 데이터의 포인터를 가져온다.

정렬과 같은 기능도 있으나 그냥 생략하기로 하겠다.

사실 데이터의 포인터를 가져오는 것은 필요가 없을수도 있다.

type이 없는 Pointer는 다른 type을 가지는 pointer에 타입캐스팅없이 바로 대입이 된다.

먼저 데이터를 추가/삽입하는 부분을 만들어보자.

implementation
...
function TMyDataList.AddItem(Value, offset : integer; others : pointer):integer; // 데이터 추가
var
  NewItem : PMyDataItem; // 동적 생성을 위한 포인터형 변수를 선언.
begin
  result := -1; //  되돌림값 초기 설정. 되돌값은 새로이 추가된 데이터의 인덱스를 되돌린다.
  New(NewItem); // 동적 데이터 생성
  if NewItem<;>;nil then
  try
    // 데이터의 대입.
    NewItem.Value := Value;
    NewItem.offset := offset;
    NewItem.others := others;
    // 리스트에 추가
     result := Add(NewItem);
  except
    // 에러가 나면 추가 작업을 포기한다. 
    // 이미 동적 데이터를 생성했으므로 추가가 실패한 경우에는 생성한 동적 데이터를 없앤다.
    Dispose(NewItem);
  end;
end;
...
procedure TMyDataList.InsertItem(Index : integer; Value, offset : integer; others : pointer); // 데이터 삽입
var
  NewItem : PMyDataItem; // 동적 생성을 위한 포인터형 변수를 선언.
begin
  New(NewItem); // 동적 데이터 생성
  if NewItem<;>;nil then
  try
    // 데이터의 대입.
    NewItem.Value := Value;
    NewItem.offset := offset;
    NewItem.others := others;
    // 리스트에 삽입
     Insert(Index, NewItem);
  except
    // 에러가 나면 삽입 작업을 포기한다. 
    // 이미 동적 데이터를 생성했으므로 삽입이 실패한 경우에는 생성한 동적 데이터를 없앤다.
    Dispose(NewItem);
  end;
end;

New/Dispose는 같이 동적 변수를 생성시키는데 사용한다. 다르게 GetMem/FreeMem을 이용해도 된다.

동적 변수를 생성하고 그 변수의 값을 할당시키는 것으로 함수는 끝난다.

거의 일어날 가능성이 없지만 도중에 예외가 발생하면 동적 변수를 해제하도록 만드는게 좋다.

하지만 string이나 인터페이스 등등이 있는 경우에는 Getmem/Freemem은 사용하지 않는게 좋다.

삽입과 추가는 별로 다르지 않다.

이제는 데이터의 참조하는 프로퍼티(property)를 작성해보자.

interface
...
      property ItemList[Index : integer]:PMyDataItem read GetItem write SetItem; // 해당 데이터이 포인터를 가져온다
      // 이 선언의 의미는 간단하다. 
     // 배열형식으로 접근할 수 있는 데이터의 인덱스는 integer형이며 PMyDataItem이 얻어지며,
     // GetItem 함수로 읽고, SetItem 프로시져로 쓴다는 의미이다.
     // 꼭 선언한 필요는 없지만, 편하게 클래스를 사용하기 위해서는 선언해주는 것이 좋다.
...
implementation
...
// 데이터 포인터를 읽어온다.
function TMyDataList.GetItem(Index : integer):PMyDataItem;
begin
  result := PMyDataItem(Items[Index]);
end;
// 데이터 포인터를 새로운 값으로 써넣는다.
procedure TMyDataList.SetItem(Index : integer; Item : PMyDataItem);
begin
  Items[Index] := Item;
end;
...

아주 간단하다. 그냥 타입 캐스팅과 대입만 하면 끝이다.

파스칼은 형식에 민감해서 포인터를 다룰 때도 이러한 고생이 조금 따르지만,

몇자 더 써넣는다고 손가락이 부러지는 것은 아니라고 생각된다.

이제는 데이터를 삭제할 부분을 만들어보자.

...
procedure TMyDataList.DeleteItem(Index : integer); // 데이터 삭제
var
  Item : PMyDataItem;
begin
  // 데이터를 가져온다
  Item := ItemList[Index];
  // 동적 생성된 변수를 해제한다.
  if Item<;>;nil then begin
    try
      // others는 포인터로 다른 동적 데이터를 담을려고 포인터로 선언한 것이다. 
      // 만약 다른 용도이거나 일반 변수라면 해제할 필요가 없다.
      // 단지 동적 변수 안에 동적 변수를 관리하는 방법을 보여주기 위해 선언한 것 뿐이다.
      if Item.others<;>;nil then Dispose(Item.others);
    except
    end;
    // 동적 변수를 해제한다. 
    // 이후에 리스트에 담긴 ItemList[Index] 포인터는 의미없는 값이 된다.
    dispose(Item);
  end;
  // 리스트에 동적 변수를 가리키는 포인터를 삭제한다.
  // 필요에 따라 ItemList[Index] := nil; 을 부가적으로 넣어도 된다.
  Delete(Index);
end;
...

삭제를 하는 경우에는 생성된 동적 변수의 해제가 필요하다. 그렇지 않으면 메모리에 계속 남아있게 된다.

마지막으로 객체가 파괴되거나 리스트 내용을 모두 삭제할 때 필요한 Clear 프로시저를 만든다.

TList의 소멸자 Destroy는 Clear를 호출한다. 그래서 따로 소멸자를 선언하지 않았다.

...
procedure TMyDataList.Clear; // 리스트를 모두 삭제
var
  Index : integer; // 루프를 위해 선언
  Item : PMyDataItem;
begin
  if Count>;0 then
    for Index:=0 to Count-1 do begin
      Item := ItemList[Index];
      if Item<;>;nil then begin
        try
          // others는 포인터로 다른 동적 데이터를 담을려고 포인터로 선언한 것이다. 
          // 만약 다른 용도이거나 일반 변수라면 해제할 필요가 없다.
          // 단지 동적 변수 안에 동적 변수를 관리하는 방법을 보여주기 위해 선언한 것 뿐이다.
          if Item.others<;>;nil then Dispose(Item.others);
        except
        end;
        // 동적 변수를 해제한다. 
        // 이후에 리스트에 담긴 ItemList[Index] 포인터는 의미없는 값이 된다.
        dispose(Item);
      end;
    end;
  // 부모 클래스의 Clear를 호출한다.
  inherited Clear;
end;
...

Clear 프로시저는 Delete와 마찬가지로 간단하다.

다른 점이라면 루프를 돌면서 각각의 데이터를 해제시킨 후 부모 클래스의 Clear를 호출한다는 점이다.

DeleteItem을 호출해서 해결할 수도 있지만 오버헤드(overhead)를 줄이기 위해 그냥 직접 구현을 했다.

실제로 이 클래스를 사용하면 아래와 같이 할 수 있다.

...
var
  MyOwnList : TMyDataList;
...
  MyOwnList := TMyDataList.Create;
  ...
  MyOwnList.AddItem(10,20,nil);
  MyOwnList.InsertItem(0,10,20,nil);
  MyOwnList.DeleteItem(1);
  Writeln(MyOwnList.ItemList[0].Value);
  ...
  MyOwnList.Free;
...

검색의 구현을 위해서는 루프를 돌려 해당값을 비교하게 만들면 된다.

이로써 간단히 TList를 사용해서 자신만의 데이터들을 담는 리스트를 사용할 수 있게 되었다.

보다 많은 기능들의 구현은 좀 더 깊이 연구하면 직접할 수 있을 것이다.

말주변이 없는 글이지만 도움이 되었기를.



출처: http://blog.naver.com/PostView.nhn?blogId=barcoder&logNo=60053164009


반응형

댓글