C++Builder Programming Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
C++빌더 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
컴포넌트/라이브러리
메신저 프로젝트
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
볼랜드포럼 광고 모집

C++빌더 강좌/문서
C++Builder Programming Tutorial&Docments
[219] 쉽고 간편하게 쓰는 데이타와 관리 클래스 예제
김태선 [cppbuilder] 26631 읽음    2010-10-03 15:29
보통 프로그램은 데이타와 그것을 관리하는 코드로 이루어져 있습니다.
객체 지향적인 입장에서 보면
데이타 객체와 데이타 객체를 관리하는 관리 클래스로 구성되어 있다고 말할 수도 있습니다.
이 둘은 하나일 수도 있고 둘로 나뉘어져 있을 수도 있고 또 여러개로 나뉘어져 있을 수도 있습니다.

데이타와 데이타를 관리하는 클래스를 묶어서 코딩하는 것은
객체지향적이고 유지보수에 좋기 때문에, 실무에서는 매우 빈번하게 사용됩니다.

하지만 아직 초보이거나 데이타와 관리 클래스를 묶어서 쓰는 기본 방식에 익숙지 않으면
데이타와 데이타를 다루는 기능이 소스의 여기저기 산개하게 됩니다.
스파게티 코드가 되어 나중에 유지보수 할때 답답하게 됩니다.

그런 의미에서 가장 가벼운 방식이면서도
여러가지 프로젝트에 유용하게 많이 사용될 수 있는 예를 하나 만들어 보겠습니다.

여기서 눈여겨 봐야 할 것은 전체 구조입니다.
그리고 네이밍 즉 명칭을 어떻게 붙이는가가 생각 이상 중요하니, 좋은 작명가가 되도록 노력해야 합니다.

예로 학생 데이타를 다루는 프로그램을 만들어 보겠습니다.

학생 데이타는 구조체로 TStudent 로 명칭을 붙입니다.
이 데이타는 그대로 파일로 저장도 되어야 하기 때문에 기본 형만으로 정의 합니다.

struct TStudent
{
    bool    Active;                // 레코드 존재유무.
    int        No;                    // 학번. 유일키로 쓰는 항목.
    char     Name[100];
    int        Age;
    char    Job[100];

    TStudent()
    {
        Clear();
    }
    void     Clear()
    {
        //Name = "";                            // String 객체라면 이렇게 먼저 해제해 주어야 한다.
        ZeroMemory(this, sizeof(*this));
    }
};

대략 이런 식으로 구성했습니다.
눈 여겨 봐야 할 것은 구조체이지만, 구조체의 데이타와 직접 관련 있는 기능은
바로 이 구조체에 메소드를 만들어 기능을 추가합니다.
대부분의 경우 구조체를 모두 0으로 초기화 하기 때문에, ZeroMemory... 로 초기화 시킵니다.
항목을 일일이 초기화 하지 않고 한방에 초기화 하기 때문에 매우 편리합니다.
TStudent 생성자에 Clear() 메소드를 호출하게 했는데,
이는 TStudent가 메모리에 자리 잡는 즉시 초기화 되게 한 것입니다.
이는 로칼 변수로 TStudent 선언된 경우라도 내용이 깔끔하게 초기화 시키기 때문에, 이런 방식의 코드가 좋습니다.

만일 구조체 내에 String Name; 식으로 기본형이 아닌 객체를 넣는 경우는,
Clear 할때 객체를 먼저 해제해주는 작업을 선행해야 합니다.
여기서 String Name; 을 쓰지 않고 char Name[100]; 으로 쓴 것은,
이 구조체 자체를 파일로 저장하기 위해서입니다.
한방에 저장하고 읽어 낼려면 레코드 내에 데이타가 자리 잡아야 하기 때문입니다.


그러면 이 데이타를 관리하는 클래스를 만들어야 하는데,
이는

class CStudent
{
public:
    TStudent *Data;
    int    Size;

public:
    CStudent()
    {
        Data = NULL;
        Size = 0;
    }
    Add(...
    Delete(...
       
};

식으로 만들면 됩니다.

관리 클래스는 TList 클래스를 연상하면 됩니다.
그기에 필요한 기능을 덧붙인 것으로 보면 되는데,

여기 제가 만든 예제에서는 TList 를 상속 받거나 하진 않고
독립적으로 구성되어 있습니다.
TList의 경우는 한 아이템당 4바이트 포인트만 수용할 수 있기 때문에
데이타 레코드는 별도로 메모리를 할당 받고 해제하는 작업을 동반하게 마련입니다.
만일 너무 많은 할당과 해제가 일어난다면 성능에도 좋지 않고, 메모리 단편화 문제가 생깁니다.
이는 형 자체를 직접 저장할 수 없었던 델파이의 레코드 다룸 방식이기 때문에,
C++에는 굳이 그렇게 할 필요가 없습니다.

물론 그렇게 성능이나 메모리에 민감한 프로그램이 아니라면 TList 식의 구성도 무방합니다.
요즘은 하드웨어 성능이 좋아서,
코딩이 하드웨어에 최적화 되지 않아도 동작에 큰 문제가 없는 경우가 대부분입니다.

데이타가 배열 형태이면 예제처럼 메모리를 한방에 할당 받아서 쓰는 방식이 좋고,
데이타가 map list 등의 형태 같으면 STL을 쓰는 것이 좋습니다.

물론 이 경우도 예로 보인 데이타와 데이타 관리 클래스, 데이타 관리 클래스의
기본 메소드는 거의 차이가 없는 일정한 구조를 가지게 됩니다.

아래에 있는 하나의 소스에 모든 것을 다 구현했지만
UI와 비지니스 로직이 그런대로 잘 분리되어 있습니다.

그래서 고수가 아니라면 코드를 잘 눈여겨 보면 나름대로 도움이 될 것입니다.



데이타는 TStudent,
클래스는 CStudent,
생성된 객체는 Student.

여기서 네이밍의 일관성을 볼수 있습니다.
뭘 어떻게 하던지 간에 프로그래머 마음이지만
자신도 뭔가 일관성 있는 네이밍 부여 규칙을 가지는게 좋습니다.

필자의 경우는 일반적으로 데이타는 T로 시작하고
클래스는 C로 시작하고, 객체는 대문자를 선행하는 단어  명칭을 씁니다.



예제에서는 학생의 학번, 이름, 나이를 입력받고
리스트에 이름과 나이를 표시하고, 학번으로 지울 수도 있게 되어 있습니다.
리스트에는 이름과 나이 데이타만 표시되고, 각 Row의 Data 프로퍼티에
키로 사용된 학번을 저장해 놓습니다.
그러면 이 키로 Student 객체를 통해 원래의 데이타를 찾을 수 있게 됩니다.

간단하지만 하나의 기본 구조를 가진 프로그램이 됩니다.
실무에서는 이것보다 훨씬 많은 기능이 들어가는 경우가 많습니다.


프로그램 종료시 입력해 놨던 값이 자동으로 저장되고
다시 프로그램을 실행하면 저장해 놨던 데이타를 읽어 CStudent 클래스 내에 적제하고
화면 리스트 컨트롤을 통해 표시합니다.







아래는 전체 소스입니다.

하나의 간단한 예일 뿐 대단한 기능이 있는 것은 아닙니다.


//---------------------------------------------------------------------------

#include 
#pragma hdrstop

#include "Unit1.h"
#include "TFILE.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------

#define INDEX_NOTFOUND		(-1)

// 데이타 구조체 (레코드)

struct TStudent
{
	bool	Active;				// 레코드 존재유무.
	int		No;        			// 학번. 유일키로 쓰는 항목.
	char 	Name[100];
	int		Age;
	char	Job[100];

	TStudent()
	{
		Clear();
	}
	void 	Clear()
	{
		//Name = "";							// String 객체라면 이렇게 먼저 해제해 주어야 한다.
		ZeroMemory(this, sizeof(*this));
	}
};

// 관리 클래스

class CStudent
{
public:
	TStudent *Data;
	int	Size;

public:
	CStudent()
	{
		Data = NULL;
		Size = 0;
	}
	CStudent(int size)
	{
		Data = NULL;
		SetSize(size);
	}
	void	SetSize(int size)
	{
		if (Data)		// 사이즈가 변했을때 기존 내용을 옮겨야 한다면, 이 부분에 옮기는 처리를 추가해야 한다.
			delete[] Data;
		Data = new TStudent[size];
		Size = size;
	}
	~CStudent()
	{
		if (Data)
			delete[] Data;
	}
	// R:index
	int		Add(TStudent& data)
	{
		if (!Data)
			return INDEX_NOTFOUND;
		int idx = FindBlank();
		if (idx >= 0)
			Data[idx] = data;
		return idx;
	}
	void	Delete(int idx)
	{
		if (!Data)
			return;
		if (idx < Size)
			Data[idx].Clear();
	}
	// R:index
	int		FindNo(int No)
	{
		for(int c = 0; c < Size; c++)
		{
			if (Data[c].Active && Data[c].No == No)
				return c;
		}
		return INDEX_NOTFOUND;
	}
	TStudent *Find(int No)
	{
		for(int c = 0; c < Size; c++)
		{
			if (Data[c].Active && Data[c].No == No)
				return Data + c;
		}
		return NULL;
	}
	// R:index
	int		FindBlank()
	{
		for(int c = 0; c < Size; c++)
		{
			if (Data[c].Active == false)
				return c;
		}
		return INDEX_NOTFOUND;
	}
	// R:index
	int		FindName(String name)
	{
		for(int c = 0; c < Size; c++)
		{
			if (name == Data[c].Name)
				return c;
		}
		return INDEX_NOTFOUND;
	}
	void    Load(char *filename)
	{
		TFILE  file(filename, "rb");
		if (file.fp == NULL)
			return;
		int filesize = file.GetFileSize();
		SetSize(filesize / sizeof(TStudent));
		file.fread(Data, filesize, 1);
	}
	void	Save(char *filename)
	{
		TFILE  file(filename, "wb");
		if (file.fp == NULL)
			return;
		file.fwrite(Data, Size * sizeof(TStudent), 1);
	}
};

CStudent	Student(100);
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
	Student.Load("Student.dat");
	for(int c = 0; c < Student.Size; c++)
		if (Student.Data[c].Active)
			AddStudent(Student.Data[c]);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
	Student.Save("Student.dat");
}
//---------------------------------------------------------------------------
void __fastcall TForm1::BitBtn1Click(TObject *Sender)
{
	TStudent  st;
	st.No = EditNo->Text.ToIntDef(0);
	strcpy(st.Name, EditName->Text.c_str());
	st.Age = EditAge->Text.ToIntDef(0);
	st.Active = true;
	if (st.No == 0 || st.Name[0] == 0 || st.Age == 0)
	{
		ShowMessage("제대로 입력하시오.");
		return;
	}


	if (Student.FindNo(st.No) != INDEX_NOTFOUND)
	{
		ShowMessage("이미 있는 학번이오");
		return;
	}
	if (Student.Add(st) == INDEX_NOTFOUND)
	{
		ShowMessage("overflow");
		return;
	}

	AddStudent(st);
}

void	TForm1::AddStudent(TStudent& st)
{
	TListItem *item = LV1->Items->Add();
	item->Caption = st.Name;
	item->SubItems->Add(st.Age);
	item->Data = (void *)st.No;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::LV1Change(TObject *Sender, TListItem *Item,
	  TItemChange Change)
{
	int itemindex = LV1->ItemIndex;
	if (itemindex < 0)
		return;
	int No = (int)LV1->Items->Item[itemindex]->Data;
	int idx = Student.FindNo(No);
	LNo->Caption = String(Student.Data[idx].Name) + " 의 학번은 " + Student.Data[idx].No;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::BitBtn2Click(TObject *Sender)
{
	int No = EditNo2->Text.ToIntDef(0);
	int idx = Student.FindNo(No);
	if (idx == INDEX_NOTFOUND)
	{
		ShowMessage("없는 학번이오");
		return;
	}

	TListItems *items = LV1->Items;
	for(int c = 0; c < items->Count; c++)
	{
		if (items->Item[c]->Data == (void *)Student.Data[idx].No)
			items->Item[c]->Delete();
	}

	Student.Delete(idx);
}
//---------------------------------------------------------------------------


첨부 파일은 여기에 사용한 소스가 들어 있습니다.

그럼.
이경문 [gilgil]   2010-10-23 02:45 X
구조체(structure)나 클래스(class)를 파일, 메모리 혹은 네트워크로 송수신하는데 있어서 요즘에는 xml 방식을 사용하는 것도 좋습니다. 무엇보다도 xml은 다양한 자료 구조를 표현할 수 있기 때문이죠. 최신에 나오는 컴파일러들이 화면 디자인을 전부 xml format으로 저장하는 것도 그러한 연유에서입니다.

그리고 일반 자료 구조는 stl이나 boost 정도만 익히고 있는 것이 좋습니다. map, hash_map 정도만 알아도 웬만한 검색 최적화는 이루어질 수가 있습니다.

그리고 보통 class는 대문자로 시작, instance(object)는 소문자로 시작하는 것이 요즘 추세더라구요.
김태선 [cppbuilder]   2010-10-25 14:22 X
좋은 의견이군요 ^^.

+ -

관련 글 리스트
219 쉽고 간편하게 쓰는 데이타와 관리 클래스 예제 김태선 26631 2010/10/03
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.