언어 자료구조 알고리즘/Escort 자료구조와 STL

[자료구조와 STL] 4. vector 인덱스 연산을 통해 사용하기

언제나휴일 2016. 4. 18. 09:50
반응형

2. 1 인덱스 연산을 통해 사용하기

 

 vector에서 인덱스 연산을 사용하면 보관된 요소의 개수에 상관없이 인덱스 연산 한 번으로 원하는 요소에 접근할 수 있습니다. 이러한 장점을 사용하기 위해서는 보관할 요소의 특정 키를 특정 알고리즘을 통해 보관하거나 보관된 위치를 구할 수 있어야 할 것입니다.

 

 보관할 요소의 특정 키를 특정 알고리즘을 통해 보관하거나 보관된 인덱스를 구할 수 있으면 vector를 인덱스 연산을 통해 사용하면 효과적입니다. 그리고 특정 키를 입력 인자로 받았을 때 알고리즘을 통해 얻어온 인덱스의 최대값이 결정될 수 있으면 보다 효과적입니다.

 

 

 

 인덱스 연산을 통해 vector를 사용하면 vector 생성 시에 저장소의 크기를 최대값으로 하고 모든 원소를 초기값으로 설정해야 합니다. 사실 vector의 인덱스 연산은 보관된 요소를 참조하기 때문입니다. 그리고 보관하거나 삭제 혹은 검색 등의 작업에서 원하는 인덱스에 있는 값이 초기값인지 여부를 확인하여 사용해야 합니다. vector 개체를 생성할 때 최대 보관할 요소의 개수(사용할 최대 인덱스+1)와 초기값을 입력 인자로 전달하여 생성합니다.

 

vector<Stu *> base(MAX_STU,0);

 

 만약, vector 개체가 특정 클래스의 멤버 필드로 캡슐화되어 있다면 초기화 과정을 통해 vector의 저장소 크기를 조절하고 초기값으로 모든 요소의 값을 설정하는 작업을 할 필요가 있습니다.

 

typedef vector<Stu *> StuCollection;

class StuManager

{

    StuCollection base;

};

StuManager::StuManager(void)

{

    base.resize(max_stu,0);

}


vector 개체의 생성 시의 논리적인 메모리 구조

[그림 2] vector 개체의 생성 시의 논리적인 메모리 구조

  

 보관할 때는 보관할 인덱스의 요소를 참조하여 초기값인지 확인할 필요가 있습니다.

 

if(base[num-1])

{

    cout<<"이미 보관된 학생이 있습니다."<<endl;

    return;

}

base[num-1] = new Stu(num,name);

 

 삭제할 때도 해당 인덱스의 요소를 참조하여 초기값이 아닌 경우에만 삭제해야 할 것입니다. 삭제할 때에는 보관된 요소를 지우는 것이 아니고 초기값으로 설정하면 됩니다.

 

if(base[num-1]==0)

{

    cout<<"보관된 학생이 없습니다."<<endl;

    return;

}

base[num-1] = 0;

 

 보관된 전체 요소에 대해 어떠한 작업을 수행할 때도 실제 보관된 것인지 초기값으로 설정된 것인지 확인하는 과정이 필요합니다.

 

for(int i = 0; i<max_stu; i++)

{

    if(base[i])

    {

        cout<<base[i]<<endl;

    }

}

 

 이제 vector의 인덱스 연산을 사용하는 예제 프로그램을 작성해 보기로 합시다.

 

소재: 학생 관리 프로그램

 요구 사항

   프로그램 시작 시에 관리할 최대 학생 수를 설정할 수 있어야 합니다.

   사용자에 의해 메뉴를 선택하여 선택한 기능을 수행하는 것을 반복합니다.

      학생 정보 추가 (학생의 정보는 번호와 이름이 있습니다.)

      학생 정보 삭제

      번호로 학생 정보 검색

      이름으로 학생 정보 검색

      전체 보기

 

 vector의 인덱스 연산을 사용하는 학생 관리 프로그램은 학생 정보에 대응되는 Stu 클래스와 학생 정보들을 관리하는 StuManager로 구성하겠습니다.


클래스 다이어그램

[그림 3] 클래스 다이어그램

 

 먼저, 학생 정보에 해당하는 형식을 정의해 보기로 합시다. 학생 정보에는 번호와 이름이 있어야 하는데 이들에 대한 정보는 생성 시에 입력 인자로 전달받게 할게요. 특히, 학생 번호는 상수화 멤버 필드로 지정하도록 하겠습니다.

 

class Stu

{

    const int num; //상수 멤버 변수, 반드시 초기화해야 함

    string name;

public:

    Stu(int num,string name):   num(num),name(name) //멤버 초기화 구문

    {

    }

};

 

 그리고 학생 번호와 이름을 제공하는 메서드를 추가할게요.

 

int GetNum()const {    return num;    }

string GetName()const{    return name;    }

 

 마지막으로 개체 출력자를 제공하도록 하겠습니다.

 

friend ostream &operator<<(ostream &os,Stu *stu)

{

    os<<"번호:"<<stu->num<<"이름"<<stu->name<<endl;

    return os;

}

 

//Stu.h

#pragma once

#include "EhGlobal.h"

class Stu

{

    const int num; //상수 멤버 변수

    string name;

public:

    Stu(int num,string name): num(num),name(name) //멤버 초기화 구문

    {

    }

    int GetNum()const{    return num;    }

    string GetName()const{   return name;    }

    friend ostream &operator<<(ostream &os,Stu *stu)

    {

        os<<"번호:"<<stu->num<<"이름"<<stu->name<<endl;

        return os;

    }

};

 

 이번에는 학생 개체를 관리하는 StuManager를 만들어 봅시다. 먼저, Stu 형식이 정의된 Stu.h를 포함하고 학생 개체를 vector를 이용하여 보관할 것이므로 vector 파일도 포함하세요. vector std 이름 공간에 정의되어 있으므로 vector를 사용하겠다는 것을 표현합니다. 그리고 vector template 클래스로 제공하고 있어 매번 템플릿 인자를 명시하는 것이 불편하므로 typedef를 이용하여 Stu *를 보관하는 vector StuCollection으로 정의하겠습니다.

 

#include "Stu.h"

#include <vector>

using std::vector;

typedef vector<Stu *> StuCollection;

 

 StuManager에는 학생 개체들을 보관하기 위한 멤버 필드가 필요하겠죠. 그리고 요구 사항에 명시된 최대 관리 학생 수를 보관할 멤버 필드도 선언하겠습니다.

 

class StuManager

{

    StuCollection base;

    const int max_stu;

};

 

 StuManager를 사용하는 곳은 진입점인 main 함수 외에는 없습니다. 이를 위해 생성자와 소멸자, 사용자와 상호작용하는 Run 메서드를 제공할게요.

 

StuManager(void);

~StuManager(void);

void Run();

  

 생성자 메서드에서는 요구 사항에 따라 최대 관리할 학생 수를 설정해야 합니다. 최대 관리할 학생 수를 관리하는 멤버 필드인 max_stu가 상수화 멤버 필드로 지정되어 있으므로 초기화 기법을 사용해야겠지요. 그리고 최대 관리할 학생 수는 사용자에게 입력받아 설정하면 됩니다. 최대 학생 수가 지정되면 vector의 버퍼공간을 늘려주기 위해 resize 메서드를 이용하여 저장소의 크기를 늘려주고 모든 요소를 0으로 설정합니다.

 

StuManager::StuManager(void): max_stu(SetMaxStu())//초기화 구문

{

    base.resize(max_stu,0); //보관한 개수를 max_stu로 조절(늘어난 곳은 0으로 보관)

}

int StuManager::SetMaxStu()

{

    cout<<"최대 관리할 학생 수를 입력하세요"<<endl;

    return ehglobal::getnum();

}

 

 Run 메서드에서는 사용자에게 메뉴를 선택하게 하고 선택한 메뉴에 따라 해당 기능을 호출하는 것을 반복하면 되겠죠.

 

 int key=0;

 while((key = SelectMenu())!= ESC)

 {

     switch(key)

     {

     case F1: AddStu(); break; //학생 추가

     case F2: RemoveStu(); break; //학생 삭제

     case F3: SearchStuByNum(); break; //번호로 학생 검색

     case F4: SearchStuByName(); break; //이름으로 학생 검색

     case F5: ListAll(); break; //전체 보기

     default: cout<<"잘못된 메뉴를 선택하였습니다."<<endl;

     }

     cout<<"아무키나 누르세요"<<endl;

     ehglobal::getkey();

}

 

 Run 메서드에서 호출하는 각 메서드들은 StuManager 내부에서만 필요한 멤버이므로 다른 곳에서 접근할 수 없게 private으로 접근 수준을 지정하면 됩니다.

 

class StuManager

{

private:

    keydata SelectMenu();//메뉴를 보여주고 메뉴를 입력 받는 메서드

    void AddStu();//학생 추가 메서드

    void RemoveStu();//학생 제거 메서드

    void SearchStuByNum();//번호로 학생 검색 메서드

    void SearchStuByName();//이름으로 학생 검색 메서드

    void ListAll();//전체 보기 메서드

    int SetMaxStu();//관리할 수 있는 최대 학생수를 입력받아 설정하는 메서드

};

 

 SelectMenu 메서드에서는 메뉴를 보여주고 난 후에 사용자가 입력한 키를 반환하세요.

 

keydata StuManager::SelectMenu()

{

    ehglobal::clrscr();

    cout<<"메뉴 [ESC]:종료"<<endl;

    cout<<"[F1]:학생 추가 [F2]:학생 삭제 [F3]:번호로 검색 ";

    cout<< "[F4]:이름으로 검색 [F5]:전체 보기"<<endl;

    cout<<"메뉴를 선택하세요"<<endl;

    return ehglobal::getkey();

}

 

 AddStu 메서드에서는 추가할 학생의 번호를 입력받은 후에 존재 여부를 확인합니다. 만약, 보관된 학생 정보가 없으면 추가할 학생의 이름 정보를 입력받아 Stu 개체를 생성하고 vector에 보관하면 될 것입니다. 여기에서는 vector의 인덱스 연산을 사용하는 예이므로 해당 학생 번호를 가지고 보관할 인덱스를 구합시다. 그리고 해당 인덱스에 보관된 값이 0인지 확인을 통해 이미 보관되었는지 확인하면 될 것입니다. 보관할 때도 인덱스 연산을 통하여 생성한 Stu 개체 정보를 대입하면 됩니다. StuManager에서는 생성한 Stu 개체의 정보를 보관하는 것이지만 vector 내부에서는 해당 인덱스에 있는 요소를 변경하는 것입니다.

 

void StuManager::AddStu()

{

    int num = 0;

 

    cout<<"추가할 학생 번호를 입력하세요. 1~"<<max_stu<<endl;

    num = ehglobal::getnum();

 

    if((num<=0)||(num>max_stu))

    {

        cout<<"범위를 벗어났습니다."<<endl;

        return;

    }

 

    if(base[num-1]) //번호 -1 위치에 보관하기로 했음, 초기에 0으로 보관했음

    {

        cout<<"이미 보관된 학생이 있습니다."<<endl;

        return;

    }

 

    string name = "";

    cout<<"이름을 입력하세요"<<endl;

    name = ehglobal::getstr();

    base[num-1] = new Stu(num,name);

}

 

 RemoveStu 메서드에서는 삭제할 학생의 번호를 입력받은 후에 존재 여부를 확인하여 있다면 해당 Stu 개체를 소멸하고 해당 인덱스 요소의 값을 0으로 지정하면 되겠죠. 앞에서도 얘기했듯이 vector입장에서는 Stu 개체를 보관하거나 삭제하는 것이 아니고 이미 보관된 요소의 정보를 변경하는 것입니다. vector를 사용하는 StuManager에서는 특정 인덱스 요소의 값이 0이면 보관이 안 된 것으로 취급하고 그 이외의 값이면 보관된 것으로 취급하는 것입니다. , vector를 인덱스 연산으로 사용할 때에는 약속된 초기값을 설정하여 해당 인덱스 요소의 값이 초기값인지 아닌지에 따라 보관되었는지를 판단하면 됩니다.

 

void StuManager::RemoveStu()

{

    int num = 0;

 

    cout<<"삭제할 학생 번호를 입력하세요. 1~"<<max_stu<<endl;

    num = ehglobal::getnum();

 

    if((num<=0)||(num>max_stu))

    {

        cout<<"범위를 벗어났습니다."<<endl;

        return;

    }

    if(base[num-1]==0)

    {

        cout<<"보관된 학생이 없습니다."<<endl;

        return;

    }

    delete base[num-1]; //개체를 소멸

    base[num-1] = 0;    //초기값으로 다시 설정

}

 

 SearchStuByNum 메서드에서는 검색할 학생의 번호를 입력받은 후에 인덱스 연산을 통해 존재 여부를 확인합니다. 있다면 해당 요소의 Stu 개체 정보를 보여주면 되겠죠.

 

void StuManager::SearchStuByNum()

{

    int num = 0;

    cout<<"검색할 학생 번호를 입력하세요. 1~"<<max_stu<<endl;

    num = ehglobal::getnum();

 

    if((num<=0)||(num>max_stu))

    {

        cout<<"범위를 벗어났습니다."<<endl;

        return;

    }

 

    if(base[num-1]==0)

    {

        cout<<num<<"번 학생은 보관되지 않았습니다."<<endl;

        return;

    }

 

    cout<<base[num-1]<<endl; //개체 출력자를 구현하였기 때문에 사용할 수 있음

}

 

 SearchStuByName 메서드에서는 검색할 학생의 이름을 입력받은 후에 같은 이름을 갖는 학생을 찾아 개체 정보를 보여주면 될 것입니다. 입력한 이름의 학생을 찾기 위해서 차례대로 인덱스 연산으로 보관된 학생 개체가 있는지 확인하여 있다면 입력한 이름과 비교하면 되겠죠.

 

void StuManager::SearchStuByName()

{

    cout<<"검색할 학생 이름을 입력하세요."<<endl;

    string name = ehglobal::getstr();

    for(int i = 0; i<max_stu; i++)

    {

        if(base[i]) //학생이 보관되었는지 확인

        {

            if(base[i]->GetName() == name)

            {

                cout<<base[i]<<endl;

                return;

            }

        }

    }

    cout<<name<<" 학생은 보관되지 않았습니다."<<endl;

}

 

 ListAll 메서드에서는 인덱스 연산을 이용하여 학생 정보를 보여주면 되겠죠.

 

void StuManager::ListAll()

{

    for(int i = 0; i<max_stu; i++)

    {

        if(base[i]) //학생이 보관되었는지 확인

        {

            cout<<base[i]<<endl;

        }

    }

}

 

 마지막으로 StuManager 소멸자에서 생성한 모든 Stu 개체를 소멸해야겠지요. C++에서는 동적으로 생성한 개체를 개발자가 소멸해야 합니다. 개발할 때 개발자가 소멸의 책임을 다하지 않는 경우가 많이 있습니다. 실제 이를 하지 않아도 컴파일 오류가 발생하지 않고 동작에도 큰 문제가 없어 보이거든요. 하지만 작성하는 프로그램이 서버 프로그램이거나 라이브러리 형태라고 한다면 필요없는 개체에 할당된 메모리를 소멸하지 않으면 메모리 누수가 발생하여 메모리 폴트가 발생하는 치명적인 버그가 될 수 있습니다. 습관적으로 개체를 생성하는 코드를 작성할 때 소멸하는 코드를 작성하시기 바랍니다.

 

StuManager::~StuManager(void)

{

    for(int i = 0; i<max_stu; i++)

    {

        if(base[i]) //학생이 보관되었는지 확인

        {

                delete base[i];

        }

    }

}

반응형