[C++] 클래스 템플릿 리스트 구현

꿈별·2023년 6월 3일
0

C++

목록 보기
21/27

✔클래스 템플릿 리스트

  • C++ 기준으로 구조체랑 클래스의 차이는 없다.
    (디폴트 접근제어자 제외)
    -> 리스트는 클래스, 노드는 구조체로 작성한 건 비교적 간단한 코드는 구조체, 복잡한 건 클래스로 구현하기로 임의로 정했기 때문

(1) 기본적인 자료형 선언

  • 리스트는 클래스로, 노드는 구조체로 만들기로 한다.

  • 데이터가 들어오는 게 노드니까 노드를 템플릿으로 만든다.
    -> 구조체 템플릿
    그럼 tListNode 는 구조체 템플릿의 이름이 된다.

  • 리스트 입장에서는, 구조체를 가리키는 포인터 타입이 멤버로 있으므로(m_pHeadNode) 타입을 명확하게 정해줄 필요가 있다.
    ( <T> 자리에 뭐가 들어갈지 알려줘야 함 )
    -> 그럼 리스트도 아직 데이터가 안 정해진 템플릿이 됨.
    -> 클래스 템플릿
    -> 리스트의 타입 T를 정해주면 연쇄적으로 구조체의 타입 T까지 정해진다.

  • 메인에서 float 타입을 받는 리스트 생성
    -> CList<float> list;

코드

//CList.h
template<typename T>
struct tListNode
{
	T			data;
	tListNode*	pNext;
};

template<typename T>
class CList
{
private:
	tListNode<T>*	m_pHeadNode;
	int				m_iCount;
};


(2) 기능 추가

  • 양방향 리스트로 만들기
    -> tListNode 는 구조체가 아니라 구조체 템플릿이다.
    따라서 (본인이랑 똑같은) 다음 노드를 가리키는 포인터를 선언하려면 tListNode<T>* 와 같이 작성해야 한다.
    (tListNode<T> 까지가 구조체 이름임.)
    -> But 구조체 선언 시 자기 자신tListNode로 지칭해도, tListNode<T> 를 의미한다는 것으로 이해하기 때문에 굳이 타입네임까지 적지 않아도 됨.

  • 반면에 이 구조체를 실제로 사용하는 리스트 쪽에서는
    tListNode<T> 로 적지 않으면 문제가 된다.
    (자기 자신을 지칭하는 것이 아니기 때문에)

  • 생성자/소멸자 선언
    -> 생성자 : 리스트 초기화, 소멸자 : 데이터 할당해제

  • push_back() 함수 템플릿

template<typename T>
void CList<T>::push_back(const T& _data)
  • const T& 인 이유
    -> 원본은 수정 X, 참조만 받아오기 위해

    • 예외처리 (최초의 데이터인지/아닌지)
    • 구조체 생성자 만들기
      tListNode()
      (구조체도 클래스와 같기 때문에 생성자 만들 수 있다.)
    • 생성자도 오버로딩이 가능하다.
      tListNode(const T& _data, tListNode<T>* _pPrev, tListNode<T>* _pNext)
    • 구조체 포인터 객체 생성
      tListNode<T>* pNewNode = new tListNode<T>(_data, nullptr, nullptr);
      -> 방금 만들어졌기 때문에 _pPrev, _pNext는 어차피 널포인터 상태
      -> 구조체 객체 생성 시 인자 설정하지 않으면 기본 생성자를 호출하겠다는 의미.

  • push_front() 함수 템플릿

template<typename T>
void CList<T>::push_front(const T& _data)
  • 구조체 포인터 객체 생성
    • 코드
      tListNode<T>* pNewNode = new tListNode<T>(_data, nullptr, m_pHead);
      -> 새 노드는 맨 앞에 삽입되므로, 이전 노드가 존재하면 안 된다. 따라서 _pPrev 는 널포인터
      -> _pNext엔 리스트가 원래 가리키고 있던 헤드의 주소값(m_pHead)을 넣어준다. 그럼 새 노드가 이 기존의 헤드 노드를 가리키게 된다.

코드

#pragma once

template<typename T>
struct tListNode
{
	T				data;
	tListNode<T>*	pPrev;
	tListNode<T>*	pNext;
	
	tListNode()
		: data()
		, pPrev(nullptr)
		, pNext(nullptr)
	{
	}
	tListNode(const T& _data, tListNode<T>* _pPrev, tListNode<T>* _pNext)
		: data(_data)
		, pPrev(_pPrev)
		, pNext(_pNext)
	{
	}
};

template<typename T>
class CList
{
private:
	tListNode<T>*	m_pHead;
	tListNode<T>*	m_pTail;
	int				m_iCount;

public:
	void push_back(const T& _data);
	void push_front(const T& _data);

public:
	CList();
	~CList();
};

template<typename T>
void CList<T>::push_back(const T& _data)
{
	// 입력된 데이터를 저장할 노드를 동적할당
	tListNode<T>* pNewNode = new tListNode<T>(_data, nullptr, nullptr);

	if (nullptr == m_pHead)
	{
		// 처음 입력된 데이터라면
		m_pHead = pNewNode;
		m_pTail = pNewNode;
	}
	else
	{
		// 데이터가 1개 이상일 때 입력된 경우
		// 현재 가장 마지막 데이터(tail)를 저장하고 있는 노드와
		// 새로 생성된 노드가 서로 가리키게 한다.
		m_pTail->pNext = pNewNode;
		pNewNode->pPrev = m_pTail;
		// List가 마지막 노드의 주소값을 새로 입력된 노드로 갱신한다.
		m_pTail = pNewNode;
	}
	// 데이터 개수 증가
	++m_iCount;
}

template<typename T>
void CList<T>::push_front(const T& _data)
{
	// 새로 생성된 노드의 다음을 현재 헤드노드의 주소값으로 채움
	tListNode<T>* pNewNode = new tListNode<T>(_data, nullptr, m_pHead);	
	
	// 현재 헤드노드의 이전노드 주소값을 새로 생성된 노드의 주소로 채움
	m_pHead->pPrev = pNewNode;

	// 리스트가 새로 생성된 노드의 주소를 새로운 헤드주소로 갱신한다.
	m_pHead = pNewNode;

	//데이터 개수 증가
	++m_iCount;
}

template<typename T>
CList<T>::CList()
	: m_pHead(nullptr)
	, m_pTail(nullptr)
	, m_iCount(0)
{
}

template<typename T>
CList<T>::~CList()
{
	tListNode<T>* pDeleteNode = m_pHead;

	while (pDeleteNode)
	{
		tListNode<T>* pNext = pDeleteNode->pNext;
		delete(pDeleteNode);
		pDeleteNode = pNext;
	}
}

[참고]
https://youtu.be/zwbH4ScKctA

0개의 댓글