List 클래스 템플릿 (1)

Yama·2023년 12월 28일
0

어소트락 수업

목록 보기
30/55

줄이기.

struct CharInfo
{
public:
	wchar_t szName[20];
	int		HP;
	int		MP;
	int		SP;

	int		MaxHP;
	int		MaxMP;
	int		MaxSP;
   
public:
	void SetInfo(const wchar_t* _strName, int _HP, int _MP, int _SP)
	{
		wcscpy_s(this->szName, _strName);
		HP = MaxHP = _HP;
		MP = MaxMP = _MP;
		SP = MaxSP = _SP;
	}

	CharInfo()
		: HP(0), MP(0), SP(0)
		, MaxHP(0), MaxMP(0), MaxSP(0)
	{}
    
	~CharInfo()
	{}    
    
};

int main()
{
	List<CharInfo> charlist;
    
    CharInfo  info = {};
    
    wcscpy_s(info.szName, L"Warrior");
	info.HP = 10;
	info.MP = 10;
	info.SP = 10;
	info.MaxHP = 10;
	info.MaxMP = 10;
	info.MaxSP = 10;
}
template<typename T>
class List
{};
  • 구조체(CharInfo)를 자료형으로 받아서 List 템플릿을 만들어서 info값에 값들을 채워 넣고있당.
  • info값을 메인함수에 노가다 형식으로 값들을 넣었다.
  • 기본 생성자를 호출해서 하는법.
struct CharInfo
{
public:
	wchar_t szName[20];
	int		HP;
	int		MP;
	int		SP;

	int		MaxHP;
	int		MaxMP;
	int		MaxSP;

public:
	void SetInfo(const wchar_t* _strName, int _HP, int _MP, int _SP)	// 4
	{
		wcscpy_s(this->szName, _strName);								// 5
		HP = MaxHP = _HP;												// 6
		MP = MaxMP = _MP;
		SP = MaxSP = _SP;
	}

	CharInfo()															// 3
		: HP(0), MP(0), SP(0)
		, MaxHP(0), MaxMP(0), MaxSP(0)
	{}
	CharInfo(const wchar_t* _strName, int _HP, int _MP, int _SP)		// 2
		: HP(0), MP(0), SP(0)
		, MaxHP(0), MaxMP(0), MaxSP(0)
	{
		SetInfo(_strName, _HP, _MP, _SP);
	}

	~CharInfo()
	{}

};
List<CharInfo> charlist;

int main()
{
	CharInfo info(L"Warrior", 100, 30, 50);								// 1
    
    charlist.push_back(info);											// 7
	return 0;
}
  • L"Warrior", 100, 30, 50 인자들을 생성자에 전달해서 하는법.
  • 정보세팅함수를 만들어서
    • 앞의 코드를 요약한것이다.
  • // 1번은 실행시키면 3번이 아니라 2번으로 인자를 전달해서 모든 구조체 맴버들을 초기값을 초기화 시킨다음에 , 전달받은 값들로 SetInfo 함수인 4번으로 값을 전달했다.
  • C에서는 클래스가 생겨서 맴버함수라는 개념이 생겨서 전용을 만들어 줄수 있었당.
  • 구조체는 이게 왜 되나용? C 구조체에서 안됬지만 C++구조체와 클래스는 맴버함수 접근 제한자와 생성자 소멸자 다 가능하다.
  • 구조체는 아무것도 안적어주면 public 클래스는 아무것도 안적어주면 private.
  • 5번 wcscpy_s(this->szName, _strName);
    • 문자열의 주소 알려주면 szName으로 _strName의 값을 복사한다.
      • 널 문자 만나기 전까지 복사한다.
  • 1번은 임시 객체 or 이름 없는 지역변수라고 말한다.
    • 푸시백에 딱한번 이름 없는 상태로 전달용 지역변수를 만들고 전달시켜버린것.
  • 6번은 HP의 초기값으로 100을 받아서 그걸 꽉채워놔야되니까 MaxHP에 넣고 HP에 넣는다.
	charlist.push_back(CharInfo(L"Warrior", 100, 30, 50));
  • 1번과 7번은 붙여서 요약이 가능하다

클래스 템플릿을 이용한 리스트 구현하깅.

template<typename T>
struct Node
{
	T			Data;		// 1
	Node<T>*	pNext;		// 2
	Node<T>*	pPrev;		// 3
	//Node* pNext;			// 4
	//Node* pPrev;			// 5
};

template<typename T>
class List
{
private:
	Node<T>* 	m_pHead;		// 6
	Node<T>* 	m_pTail			// 7	
	int			m_CurCount;		// 8

public:
	List()						// 9
		: m_pHead(nullptr)
		: m_pTail(nullptr)
		, m_CurCount(0)
	{}
	~List()						// 10
	{}
};
  • 1번은 노드의 저장할 값
  • 2번은 노드의 다음 주소
    • 4번이랑 같고 T를 지워도 이미 전 클래스에서 T를 알려줌으로 안알려줘도 된다.
  • 3번은 이전 노드의 주소값을 가지고잇는데
    • 5번이랑 같고 T를 지워도 이미 전 클래스에서 T를 알려줌으로 안알려줘도 된다.
  • 6번은 노드의 시작 포인터의 주소의 맴버
  • 7번은 노드의 끝 포인터의 주소의 맴버
    • 6,7번의 T는 생략불가능하다.
  • 8번 리스트는 본인한테 들어온 데이터가 현재 몇개인지 세는 맴버함수
  • 9번은 기본 생성자 초기값을 준것이다. ,
  • 10번은 모든 노드를 지우는 코드가 들어와야된다.(뒤에)
// struct Ndoe에 구현
	Node(const T& _Data, Node<T>* _Next, Node<T>* _Prev)
		: Data(_Data)
		, pNext(_Next)
		, pPrev(_Prev)
	{}
  • 미리 값을 받을때 생성자를 만들어 두깅.
// 클래스 List에 구현
public:
	void push_back(const T& _Data);
  • T&로 한 이유는 저장하는 T가 덩치가 큰 놈 일수도 있으니 레퍼런스를 사용해서 8바이트만 사용해서 전달한다.
  • const를 붙인이유는 입력받는거니까 원본값을 바꿀 이유도없기떄문에 명시적으로 const 붙인거다.

push_back 함수 구현.

  • 클래스 내부 선언이 너무 복잡해 지니까 밖에다 템플릿 따로 만들어서 작성할것이다.
template<typename T>
/*inline*/ void List<T>::push_back(const T& _Data)						// 1
{
	//Node<T>* pNewNode = new Node<T>;									// 3
    
	//pNewNode->Data = _Data;											// 4
	//pNewNode->pNext = nullptr;										// 5
	//pNewNode->pPrev = nullptr;										// 6

	Node<T>* pNewNode = new Node<T>(_Data, nullptr, nullptr);			// 7

	if (0 == m_CurCount)//nullptr == m_pHead)							// 2
	{
		m_pHead = pNewNode;
		m_pTail = pNewNode;												// 9
	}
	else																// 8
	{
		m_pTail->pNext = pNewNode;
		pNewNode->pPrev = m_pTail;
		m_pTail = pNewNode;
	}

	++m_CurCount;
}
  • 1번처럼 헤더에서 처리하다 보니까 기본적으로 인라인 취급이 되는데 무조건 인라인이 되는것이 아니기 때문에 반드시 처리된다는 보장이 없다 그래서 걍 지우자.
  • 입력되는 데이터가 최초일떄
    • 2번은 입력된 데이터가 최초일때 쓰는것.
    • 0 == m_CurCount, nullptr == m_pHead 같은 조건.
  • 3번은 새로운 노드를 만들어 준것이다.
    • 4,5,6은 노드의 맴버들한테 초기값을 준것이다.
  • 7번은 _Data, nullptr, nullptr이런 인자를 받는 생성자를 구현 해뒀으니 3,4,5,6,을 합쳐서 7번처럼 한줄로 작성 가능.
  • 8번은 최초로 들어온 데이터가 아니면 발동.!
  • 9번은 첫번째 노드만 있으면 처음과 끝은 같은거니까 처음과 끝의 주소는 같은걸 받는다.

끝의 노드 주소도 알아야하는 이유

	Node<T>*	m_pHead;
	Node<T>*	m_pTail;
  • 원래 헤드노드의 주소만 만들어서 단방향으로 리스트를 연결해두면 O(N)다.
  • 근데 마지막 노드의 주소를 알면 데이터의 입력을 구현 효율을 O(N/2)으로 바꿀수 있다.
	else															
	{
		m_pTail->pNext = pNewNode;		// 1
		pNewNode->pPrev = m_pTail;		// 2
		m_pTail = pNewNode;				// 3
	}
  • 테일이 가리키곳으로 가서 넥스트로가서 새로생긴 노드를 주소를 넣고
  • 새로 생긴 노드도 본인의 프리뷰포인터에 원래 알고있던 테일을 쌍방향으로 알려주고
  • 마지막으로 테일 포인터가 새로운 곳을 갖게 한다. 2번쨰 데이터가 꼬리가 된다.
  • 쌍방향으로 연결하면 데이터를 헤드와 테일의 효율 좋은 방향으로가서 접근이 빨라짐.

gpt형님.

  • 새 노드를 리스트 끝에 연결
    • 1번은 현재 리스트의 마지막 노드(m_pTail)의 pNext 포인터를 새 노드(pNewNode)로 설정합니다. 이는 새 노드를 리스트의 끝에 추가하는 작업입니다.
  • 새 노드의 이전 노드 설정
    • 2번은 새 노드의 pPrev 포인터를 현재의 마지막 노드(m_pTail)로 설정합니다. 이는 새 노드가 리스트의 마지막 노드가 되기 전의 마지막 노드와 연결되어야 함을 나타냅니다.
  • 리스트의 마지막 노드 업데이트
    • 3번은 리스트의 m_pTail 포인터를 새 노드로 업데이트합니다. 이는 새 노드가 이제 리스트의 마지막 노드임을 나타냅니다.

List 클래스의 소멸자 구현

	~List()
	{
		Node<T>* pNode = m_pHead;			// 1
		while (pNode)						// 2
		{
			Node<T>* pNext = pNode->pNext;	// 3
			delete pNode;					// 4
			pNode = pNext;					// 5
		}
	}
  • Node Next의 값을 입시로 옮겨버리고 옮겨났던 Next의 값을 Node에 다시 옮긴다.

  • 1번에 헤드포인터 주소를 옮겨놓고 2번에 주소가 nullprt값이 아니라면 반복문 들어가서 그 Node의 Next주소 받아놓고 4번처럼 Node 지워 버린다. 3번을 안하면 넥스트 주소를 안받아버리면 노드의 넥스트 정보를 잃어버리기때문이다. 그리고 5번 3번에서 저장해둔 걸 노드의 값에 넣어버린다.

  • 초기화

    • 1번은 pNode라는 포인터를 리스트의 헤드인 m_pHead로 초기화합니다. Node<T'>는 T가 어떤 데이터 유형일 수 있는 템플릿화된 노드 클래스임을 나타냅니다.
  • 리스트를 통한 반복

    • while (pNode)
      이 while 루프는 pNode가 null이 아닐 때 계속 진행되므로, 리스트의 마지막에 도달할 때까지 각 노드를 순회합니다.
  • 노드 삭제

    • 3번은 리스트의 다음 노드를 가리키는 임시 포인터 pNext를 생성합니다.
    • 4번은 현재 pNode가 가리키고 있는 노드를 삭제합니다.
      • 이 작업을 수행하기 전에 pNext에 다음 노드를 저장하는 것이 중요하다.
      • why? pNode를 삭제하면 리스트의 나머지 부분에 대한 접근이 손실되기 때문입니다.
    • 5번은 pNode를 리스트의 다음 노드로 설정하여 반복을 진행합니다.

강의코드

main.cpp

#include <iostream>

#include "List.h"

struct CharInfo
{
	wchar_t szName[20];
	int		HP;
	int		MP;
	int		SP;

	int		MaxHP;
	int		MaxMP;
	int		MaxSP;

public:
	void SetInfo(const wchar_t* _strName, int _HP, int _MP, int _SP)
	{
		wcscpy_s(szName, _strName);
		HP = MaxHP = _HP;
		MP = MaxMP = _MP;
		SP = MaxSP = _SP;		
	}

	CharInfo()
		: HP(0), MP(0), SP(0)
		, MaxHP(0), MaxMP(0), MaxSP(0)
	{}

	CharInfo(const wchar_t* _strName, int _HP, int _MP, int _SP)
		: HP(0), MP(0), SP(0)
		, MaxHP(0), MaxMP(0), MaxSP(0)
	{
		SetInfo(_strName, _HP, _MP, _SP);
	}

	~CharInfo()
	{}
		
};

List<CharInfo> charlist;

int main()
{
	//CharInfo(L"Warrior", 100, 30, 50) - 임시객체, 이름없는 지역변수
	charlist.push_back(CharInfo(L"Warrior", 100, 30, 50));
	charlist.push_back(CharInfo(L"Archer", 80, 50, 40));
	charlist.push_back(CharInfo(L"Wizzard", 50, 100, 20));

	return 0;
}

List.h

#pragma once


template<typename T>
struct Node
{
	T			Data;
	Node<T>*	pNext;
	Node<T>*	pPrev;

	Node()
		: Data()
		, pNext(nullptr)
		, pPrev(nullptr)
	{}

	Node(const T& _Data, Node<T>* _Next, Node<T>* _Prev)
		: Data(_Data)
		, pNext(_Next)
		, pPrev(_Prev)
	{}
};

template<typename T>
class List
{
private:
	Node<T>*	m_pHead;
	Node<T>*	m_pTail;
	int			m_CurCount;

public:
	void push_back(const T& _Data);
	

public:
	List()
		: m_pHead(nullptr)
		, m_pTail(nullptr)
		, m_CurCount(0)
	{}

	~List()
	{
		Node<T>* pNode = m_pHead;
		
		while (pNode)
		{
			Node<T>* pNext = pNode->pNext;
			delete pNode;
			pNode = pNext;
		}
	}
};

template<typename T>
void List<T>::push_back(const T& _Data)
{
	Node<T>* pNewNode = new Node<T>(_Data, nullptr, nullptr);

	if (0 == m_CurCount) //nullptr == m_pHead)
	{
		m_pHead = pNewNode;
		m_pTail = pNewNode;
	}
	else
	{
		m_pTail->pNext = pNewNode;
		pNewNode->pPrev = m_pTail;
		m_pTail = pNewNode;
	}

	++m_CurCount;
}

1차 23.12.28
2차 23.12.29
3차 24.01.02
4차 24.01.03
5차 24.01.04

0개의 댓글