C++ - 클래스 리스트 구현

이강민·2023년 9월 3일

C++

목록 보기
20/22
post-thumbnail

클래스 리스트 구현

이전에서 클래스로 배열 구현이 가능하듯이 리스트도 구현이 가능하다.
사실 모든 알고리즘과 로직에 대해서 가능하다고 말하고싶다.
리스트를 구현해보면서 클래스의 사용방법을 익혀보자

C++로 넘어오면서 사실 클래스와 구조체는 별 차이가 없다.
구조체에서도 접근제어자를 사용할 수 있을 뿐더러 클래스가 하는 일을 모두 수행할 수 있다.
그러나 클래스를 사용하면 사용자가 화살표 연산자 없이 간편하게 사용할 수 있다는 점과 구조체는 default가 public, 클래스는 default가 private이라는 점이 다르겠다.

C++의 구조체 만들기
C와 달리 C++구조체는 typedef 예약어와 struct 구조체명을 의미하는 변수명 없이 바로 struct키워드로 구조체 정의가 가능하다.
우리는 리스트를 구현함으로 각 노드를 생성할 것인데 노드안에는 데이터를 담는 data변수와 다음 주소를 가르키는 변수가 존재해야 한다. 이전 리스트를 업그레이드하여 양방향으로 각 노드에는 이전 노드포인터와 이후 노드포인트가 존재하는 변수를 만들자.

//C++에서의 구조체 만들기
// 구조체에 템플릿을 적용시킬 수 있다. 
template <typename T>
struct tListNode{
 //노드에는 데이터와 양방향 노드의 주소를 담는 변수가 필요하다.
 //데이터 타입은 결정되지 않았으니 템플릿을 사용한다.
 	T data; 
    //이전주소와 이후주소를 담는 변수는 tListNode타입이다.
    tListNode* pPrev;
    tListNode* pNext;
  
}

앞으로 구조체를 생성할때 malloc이 아닌 new 키워드를 사용할 것이다. new 키워드를 생성해야 타입의 크기가 자동적으로 할당되기 때문에 편리하다. new 키워드를 이용하여 구조체를 생성하는 것은 자동적으로 생성자를 통해 힙영역에 공간을 할당한다. 이는 클래스와 마찬가지로 구조체도 생성자와 소멸자를 가진다는 말이된다.

우리는 생성자를 통해서 좀더 간편하게 구조체 생성을 조작할 수 있다.
생성자는 오버로딩이 가능하여 매개변수의 갯수나 타입에 따라 원하는 동작을 하게 만들 수 있다.
다음 생성자를 명시적으로 정의해보자.

//C++에서의 구조체 만들기
// 구조체에 템플릿을 적용시킬 수 있다. 
template <typename T>
struct tListNode{
 //노드에는 데이터와 양방향 노드의 주소를 담는 변수가 필요하다.
 //데이터 타입은 결정되지 않았으니 템플릿을 사용한다.
 	T data; 
    //이전주소와 이후주소를 담는 변수는 tListNode타입이다.
    tListNode* pPrev;
    tListNode* pNext;
    //생성자를 명시적으로 정의한다.
    // 처음 생성되면 아무런 데이터가 들어가있지 않고 구조만 갖춘다.
    tListNode()
    	: data()
        , pPrev(nullptr)
        , pNext(nullptr)
    {
    
    }
    //만약 생성자를 생성할 때 데이터를 바로 넣을 수 있다면 생성자 실행 시 값을 할당한다.
    //템플릿으로 정의하였기에 다음과 같이 사용해야한다.
    // data는 받아들인 그대로 사용하겠다는 의도를 나타내는 const키워드를 사용한다.
    tListNode(const T _data, tListNode<T>* _pPrev, tListNode<T>* _pNext) 
    	: data(_data)
        , pPrev(_pPrev)
        , pNext(_pNext)
       
    {
    
    } 
}

위와 같이 명시적으로 생성자를 정의하면 오버로드되어 매개변수에 맞는 생성자가 실행되어 초기화 된다.

다음은 클래스로 처음 리스트를 가르키는 헤드포인트와 리스트의 데이터를 넣는 기능을 만들어 보자. 양방향 리스트는 리스트를 헤드에서 접근할 수 있고 맨 마지막 테일부분에서도 접근이 가능하다.
헤드의 주소를 담는 변수와 테일을 담는 변수 그리고 전체 노드의 갯수를 넣는 변수를 만들어야 한다. 또한 데이터를 넣을 수 있는 함수기능을 front로 쌓아 갈 것인지, back에서 쌓아 갈 것인지 함수를 만들어 보자

template <typename T>
class CList
{
	private :
    	tListNode<T>* m_pHeadNode;
        tListNode<T>* m_pTailNode;
   		int m_iCount;
    public:
    	// 데이터를 넣는 함수를 정의할 것인데 매개변수로는 
        //원본 데이터를 수정하지 않고 그대로 받아 사용하겠다는 것을 명시한 것이다.
    	void push_back(const T& _data);
        void push_front(const T& _data);
    public:
    	CList();
        ~CList();
}

클래스를 정의하였으니 다음으로 함수를 만들어보자

처음 클래스를 정의할때는 초기 설계대로 작성한다.
하지만 구현 단계나 다음 단계에서 필요한 멤버변수나 메소드가 생길 수 있다.
필요할때마다 확장해서 개발해 나갈 수 있으나 클래스의 정의처럼 객체의 추상적인 부분을 벗어난다면 다른 클래스를 정의하는 것이 더 낳을 수 있다.


앞으로 작성할 코드를 시각적으로 표현 한 것이다.
처음 노드를 생성한다면 첫번째 노드가 곧 헤드이자 테일이되고 하나 더 추가해야 비로소 헤드와 테일이 나눠지는 것을 알 수 있다.

이를 토대로 노드를 추가하는 메소드를 정의해보자

template <typename T>
//클래스에 명시한 것처럼 const키워드와 &를 사용하여 데이터의 무결성과 즉각 수정할 수 있도록 한다.
// 여기서 수정이란 데이터 자체를 수정하는 것이 아닌 값이 변수에 반영하는 것을 의미한다.
void CList<T>::push_back(const <T>& _data)
{
	//처음 들어갈 노드가 필요하다. 노드의 타입은 구조체 타입으로 템플릿으로 정의 되어있다.
    // 주소값을 할당할 것이기에 포인터로 정의한다. 
    // 템플릿으로 작성하면서 데이터는 매개변수로 받아오기 때문에 바로 값을 할당한다. 
    // 아직 헤더와 테일이 결정되지 않았기에 nullptr로 받아온다.
    tListNode<T>* pNewNode = new tListNode<T>(_data, nullptr, nullptr)
	//처음 입력된 값이라면 헤더 값은 nullptr일 것이다.
    if(nullptr == m_HeadNode){
    	//처음 생성된 노드라면 이 노드가 헤더이자 테일이다. 
        m_pHeadNode = pNewNode;
        m_pTailNode = pNewNode;
    }
    else
    {
     //값이 한번이라도 저장되어 있다면 테일노드에 현재노드가 저장되어 있을 것이다. 
     //현재노드(테일)의 다음 노드는 새로 만들어진 노드이다.
     m_pTailNode -> pNext = pNewNode;
     //새로 만들어진 노드의 이전 노드는 현재노드(테일노드)이다.
     pNewNode -> pPrev = m_pTailNode;
     //이제 테일 노드는 새로 만들어진 노드가 되어야 한다.
     m_pTailNode = pNewNode;
    }
    // 노드가 추가되었으니 노드의 갯수를 추가하자.
    ++m_iCount;
}

다음은 앞으로 노드 추가하는 것을 구현해보자

fornt로 노드를 추가하는 모습을 시각화한것이다.
이를 토대로 코드를 작성해보자.

template <typename T>
void CList<T> push_front(cosnt T& _data)
{
	//새로 생성된 노드의 다음 주소는 헤더가 되어야 헤더보다 앞에서 생성된다.
    tListNode<T>* pNewNode = new tListNode<T>(_data, nullptr, m_pHeadNode);
    //현재 헤더의 이전 주소는 새로 만든 노드가 되어야한다.
    m_pHeadNode -> mPrev = pNewNode; 
    //이제 헤더는 새로 만든 노드가 되어야한다.
    m_pHeadNode = pNewNode;

}

생성자의 초기식은 구조체와 비슷하다.
처음 클래스를 정의하면 헤더노드, 테일노드를 전부 nullptr로 설정하고 count도 0으로 초기화한다.

template<typename T>
CList<T>::CList() : 
	m_pHeadNode(nullptr),
	m_pTailNode(nullptr),
	m_iCount(0)
{
}

다음 소멸자는 메인함수가 종료되기 직전에 자동으로 실행되며 힙영역에 생성된 메모리를 모두 해제 시켜야한다.
소멸자를 이용하여 만들어진 객체를 모두 메모리에서 해제시켜보자.

template <typename T>
CList<T>::~CList()
{
	//처음 노드를 접근하기 위해 delete노드를 만든다. 
    //이 노드는 헤더의 주소를 담는다.
    tListNode<T>* pDeleteNode = m_pHeadNode;
    //반복문을 사용하여 메모리를 해제한다. 
    while(pDelteNode)
    {
    	//임시로 다음노드의 주소를 저장할 변수를 만든다.
        //DeleteNode의 다음 주소를 저장한다.
    	tListNode<T>* pNextNode = pDeleteNode -> pNext;
        //DeleteNode에 저장된 현재 노드의 메모리를 해제한다.
        delete(pDeleteNode);
        // 임시로 저장된 다음 노드를 삭제할 pDeleteNode에 저장한다.
        pDeleteNode = pNextNode;
        //pDelteNode가 더이상 없을 때까지 반복한다.
    }
}
profile
AllTimeDevelop

0개의 댓글