List 클래스 템플릿 (3)

Yama·2024년 1월 2일
0

어소트락 수업

목록 보기
32/55

지금까지 구현한 operator ++, --에는 반환타입 문제가 있었다.

	CharInfo info = *listiter;			// 1
	const CharInfo& info = *listiter;	// 2
  • 1번처럼 하면 복사가 일어나서 연산이 더 많아진다.
  • 2번처럼 데이터를 받을때도 레퍼런스로 받아야 바톤터치하듯이 참조를 계속 넘겨서 불 필요한 복사를 하지않기 위해서 계속 레퍼런스 해야한다.
  • 꺼내올 데이터를 수정할 생각없고 읽어올 생각이라면 안전하게 const 선언.
	int a = 10;
	++(++(++a));
  • 13이 나온다. 이 코드처럼 우리의 operator ++도 같은걸 구현해야됨.
	++(++listiter);
  • 위의 코드 처럼 안된다.
    • 반환타입이 void 이기 떄문이다.
      • 반환타입을 자기 자신을 지칭해줘야 저 연산이 가능해진다.
int Add(int a, int b)
{
	int c;

	return c;
}
  • 복사된 객체가 반환되니까 리턴 c해도 자기 자신이 전달하는 것이 아니당.
  • 지역변수안에서 객체를 만들어서 c가 반환되는게 아니라 c의 복사 버전이 다시 복사객체를 만들어서 복사한다.
  • 그래서 이런 복사말고 원본을 전달할려면 포인터와 레퍼런스를 이용해서 전달가능
		iterator* operator ++()							// 1
		{
			assert(m_Owner && m_TargetNode);
			m_TargetNode = m_TargetNode->pNext;
			
			return *this;								// 2
		}
	++(*(++listiter));
  • 포인터를 이용한 증감 연산자 구현.
  • 1번처럼 자기자신을 포인터를 사용해 반환형으로으로 선언해준것.
  • 2번은 자기자신이 this니까 그거의 원본인 *포인터를 사용해서 반환했당.
		iterator& operator ++()							// 1
		{
			assert(m_Owner && m_TargetNode);
			m_TargetNode = m_TargetNode->pNext;

			return *this;								// 2
		}
	++(++(++listiter));									// 3
  • 레퍼런스를 이용한 증감 연산자 구현.
  • 1번처럼 자기자신을 레퍼런스를 사용해 반환형으로 선언했다.
  • 2번은 자기자신이 this니까 그거의 원본인 *포인터를 사용해서 반환했당.
  • 3번은 자기자신을 반환하게 만들었기 때문에, 다시 ++ 함수를 재 호출 가능하게 됬당.

지금까지 구현한 operator ++, --에는 후위 연산자 문제가 있었다.

	int i = 0;
	int i2 = i++;	// 1
	int i2 = ++i;	// 2
  • 1번은 대입->후위연산
  • 2번은 전위연산->대입
	List<CharInfo>::iterator listiter2;
	listiter = charlist.begin()
    
    listiter2 = listiter;		
    listiter2 = ++listiter;
    listiter++;

  • 맨 마지막 코드에서 오류가 발생한다.
  • 우리가 원하는 형태 뒤에 붙어서 호출하는걸로는 연산자 우선순위가 바뀌지 않는다.
    • 그러므로 우회해서 구현했다.
int Add(int, int);
int main()
{
	return 0;
}
int Add(int c, int d)
{
	return c + d;
}
  • 지역변수명이 선언과 구현할떄 꼭 일치할 필요는 없다.
    • 구현할떄 내부에서만 코드를 일치시키면 된다.
		iterator operator++(int)	// 1
		{
			iterator copyiter;
			copyiter = *this;		// 2

			++(*this);				// 3

			return copyiter;		// 4
		}	
  • 우리가 구현하는건 진짜 연산자를 구현하는게 아니라 연산자 형태를 가지는걸 호출할떄 맴버함수를 구현하고있는 것이다.
    • 맴버함수를 구현하는것이당.
  • 1번은 너가 후위연산 ++ 구현하고싶다면 ++하고 의미없는 int인자를 받는 형태로 구현해라.
    • 뒤에 붙은 경우 뒤에 붙는 ++로 매핑해주겠다는것.
    • float적으면 안된다. 규칙이 int타입으로 정해져있다.
  • 1번은 iterator&가 아닌 이유는 ++에서는 반환값이 함수 끝나고 사라지지않는데 ++후위연산에서는 지역이 끝나면 사라지기 때문에 연산값을 더 쓰더라도 복사로 해야한다.
  • 2번은 증가하기전에 값을 복사했다.
  • 3번은 증가시켰당
  • 4번은 증가되기 전값을 복사해줌.
		iterator& operator--()
		{
			assert(m_Owner && m_TargetNode);
			assert(m_Owner->m_pHead != m_TargetNode); // iterator 가 begin 이다.			
			m_TargetNode = m_TargetNode->pPrev;

			return *this;
		}
  1. Assertions:

    • assert(m_Owner && m_TargetNode): 이 assert 문은 m_Ownerm_TargetNode가 둘 다 nullptr이 아님을 확인합니다. 즉, 반복자가 유효한 컨테이너에 연결되어 있고, 타겟 노드가 존재한다는 것을 보장합니다.
    • assert(m_Owner->m_pHead != m_TargetNode): 이 assert 문은 m_TargetNode가 컨테이너의 첫 번째 노드(헤드)가 아님을 확인합니다. 이는 반복자가 컨테이너의 begin 위치에 있지 않음을 보증합니다. 만약 반복자가 begin 위치에 있으면, 이전 요소로 이동할 수 없기 때문입니다.
  2. Node Movement: m_TargetNode = m_TargetNode->pPrev; 이 라인은 반복자의 현재 타겟 노드를 그 노드의 이전 노드(pPrev)로 업데이트합니다. 이는 반복자를 한 위치 뒤로 이동시킵니다.

  3. Return Statement: return *this;는 수정된 반복자의 참조를 반환합니다. 이를 통해 메소드 체인(method chaining)이나 다른 연산에 이 반복자를 바로 사용할 수 있습니다.

		iterator operator --(int)
		{
			iterator copyiter;
			copyiter = *this;
            
			--(*this);
			return copyiter;
		}
  • 작업 2번한다 선언하고 대입.
	iterator copyiter(*this);
  • 복사 생성자 호출하는 코드.
	iterator copyiter = (*this);
  • 두번작업할걸 생성자를 받아서 한번에 작업한다.

insert 함수 구현

	List<CharInfo> CharList;
	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));

	List<CharInfo>::iterator iter = CharList.begin();
  • 집어넣는 순서. 지금은 비긴이라 워리어를 가리키고잇다.
	iterator insert(iterator _targetIter, const T& _Data);
  • 선언
  • 타켓노드, 입력받은 데이터들.
	template<typename T>
	typename List<T>::iterator List<T>::insert(iterator _targetIter, const T& _Data)
    {
	}
  • 반환타입이 이너 클래스인 경우 앞에 typename를 붙여주어야 한다.
    • 템플릿은 컨트롤 '+' + '.' 하면 제대로 안만들어주기때문에 살짝 수정해야한다.
    • .눌러도 템플릿이라 맴버를 컴파일러가 잘 못찾아줄수있당.
	assert(_targetIter.m_Owner == this);
  • iterator가 해당 리스트가 소유한 데이터를 가리키는 상태인지 확인.
  • 오너가 리스트 본체인지 확인한다.
	if (m_pHead == _targetIter.m_TargetNode)
	{
		push_front(_Data);					// 1

		return iterator(this, m_pHead);		// 2
	}
  • if문은 insert 위치가 맨 처음이라면, push front로 처리한다.
  • 1번은 insert 위치를 맨 앞으로 지정했기 때문에 push_front와 동일한 효과이다.
  • 2번은 push_front가 끝나고 나면 m_pHead 에 새로운 데이터를 저장하는 노드의 주소가 들어있다.
	else
	{	
    	// 1
		Node<T> pNewNode = new Node<T>(_Data, _targetIter.m_TargetNode, _targetIter.m_TargetNode->pPrev);
		// 2
		_targetIter.m_TargetNode->pPrev->pNext = pNewNode;	
		// 3
		_targetIter.m_TargetNode->pPrev = pNewNode;
        // 4
		++m_CurCount;
		// 5
		return iterator(this, pNewNode);
	}
  • 1번은 3가지를 요약해둔것
    1. 입력되는 데이터를 저장하는 노드를 만든다.
    2. 새로 생성된 노드가 _targetIter 가 가리키는 노드를 다음으로 가리킨다.
    3. 새로 생성된 노드가 _targetIter 가 가리키는 노드의 이전을, 이전으로 가리킨다.
  • 2번은 _targetIter의 이전 노드로 가서, 그 노드가 새로 생성된 노드를 Next로 지정하게 한다.
  • 3번은 _targetIter 가 가리키는 노드의 이전을 새로 생성된 노드로 지정한다.
  • 4번은 데이터 카운트 증가
  • 5번은 새로운노드를 가리키는 iterator을 만들어서 반환
	iter = CharList.insert(iter, CharInfo(L"Theif", 60, 30, 100));
  • warrior에 Theif를 insert 함(현재 카운터 4, iter는 theif를 가리킴)
	++iter;
	iter = CharList.insert(iter, CharInfo(L"Theif", 60, 30, 100));
  • Archer가리킨다.(현재 카운트 4, iter는 theif를 가리킴 w->t->a->wi

friend 클래스

friend 클래스의 기본 개념

  1. 접근 권한: friend 클래스는 다른 클래스의 private 및 protected 멤버에 접근할 수 있습니다. 이는 일반적으로 다른 클래스에서 불가능한 일입니다.

  2. 양방향이 아님: 클래스 A가 클래스 B를 friend로 선언하면, A는 B의 private 및 protected 멤버에 접근할 수 있지만, B는 자동적으로 A의 private 및 protected 멤버에 접근할 수 없습니다. B도 A의 멤버에 접근하려면, B 역시 A를 friend로 선언해야 합니다.

  3. 선언 위치: friend 선언은 클래스 내부에서 이루어집니다. 이 선언은 public, private, 또는 protected 영역 어디에 위치해도 상관없습니다. 접근성은 friend 선언의 위치에 영향을 받지 않습니다.

예시:

class ClassA 
{
    friend class ClassB; // ClassB는 ClassA의 friend
private:
    int secretValue;
};

class ClassB 
{
public:
    void accessClassA(ClassA& a) 
    {
        // ClassB는 ClassA의 private 멤버에 접근할 수 있습니다.
        a.secretValue = 10;
    }
};
  • ClassBClassAfriend이므로 ClassB 내의 메소드는 ClassA의 private 멤버 secretValue에 접근할 수 있습니다.
  • 내가 허락을 해줘야 접근할수 있는것.

erase 함수

	iterator erase(iterator _targetIter);
  • _targetIter가 가리키는 데이터를 삭제하고, 삭제한 다음을 가리키는 iterator 를 반환

의사코드.

template<typename T>
typename List<T>::iterator List<T>::erase(iterator _targetIter)
{
	// 데이터 카운트 감소

	// _targetIter 가 가리키고 있는 노드(삭제할 노드)
	// 삭제할 노드 이전노드의 Next 를 삭제할 노드 Next로 교체
	// 삭제할 노드 다음 노드의 Prev를 삭제할 노드 Prev로 교체.

	// 예외상황(삭제할 노드가 Head 이거나 Tail 인 경우)

	// _targetIter 가 가리키고 있는 노드를 delete(동적할당 해제)


	// 삭제된 노드의 다음을 가리키는 iterator을 반환해준다. 
	return iterator();
}

강의코드

main.cpp

#include <iostream>

#include <vector>
using std::vector;

#include <list>
using std::list;

#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;
//list<CharInfo> stdcharlist;

int main()
{
	int a = 10;
	++(++(++a));

	//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));

	List<CharInfo>::iterator listiter;

	listiter = charlist.begin();
	const CharInfo& info = *listiter;

	// 자기자신을 반환하게 만들었기 때문에, 다시 ++ 함수를 재 호출 가능
	++(++(++listiter));

	// 후위 연산자
	int i = 0;
	int i2 = ++i;

	List<CharInfo>::iterator listiter2;
	listiter = charlist.begin();
	listiter2 = listiter++;


	// insert 함수 테스트
	List<CharInfo> CharList;
	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));

	// Warrior 를 가리킴
	List<CharInfo>::iterator iter = CharList.begin();

	// Warrior 에 Theif 를 insert 함( 카운트 4, iter 는 theif 를 가리킴)
	// iter = CharList.insert(iter, CharInfo(L"Theif", 60, 30, 100));

	// Archer 가리킴
	// 카운트 4, iter 는 theif 를 가리킴, W -> T -> A -> Wi
	++iter;
	iter = CharList.insert(iter, CharInfo(L"Theif", 60, 30, 100));


	// erase 테스트

	return 0;
}

List.h

#pragma once

#include <assert.h>

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);
	void push_front(const T& _Data);


	class iterator;

	iterator begin() { return iterator(this, m_pHead); }
	iterator end() { return iterator(this, nullptr); }


	// _targetIter 가 가리키는곳에 _Data 를 저장시키고, 새로 저장한 데이터를 가리키는 iterator 를 반환해준다.
	iterator insert(iterator _targetIter, const T& _Data);

	// _targetIter 가 가리키는 데이터를 삭제하고, 삭제한 다음을 가리키는 iterator 를 반환
	iterator erase(iterator _targetIter);


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;
		}
	}

public:
	class iterator
	{
	private:
		List<T>* m_Owner;
		Node<T>* m_TargetNode;

	public:
		T& operator *()
		{
			// Owner(List) 가 설정되어있지 않으면, 정상적인 iterator 가 아니다.
			// Owner 가 설정되어 있어도, m_TargetNode 가 nullptr 이면 End Iterator 이기 때문에
			// 마지막의 다음을 가리키는 상태의 iterator 에게 * 로 접근기능을 쓰면 에러
			assert(m_Owner && m_TargetNode);
			return m_TargetNode->Data;
		}

		// 1. ++, -- 반환타입 문제
		// 2. ++, -- 후위연산자 문제
		iterator& operator ++()
		{
			// enditerator 에서 ++ 하는 경우
			assert(m_Owner && m_TargetNode);
			m_TargetNode = m_TargetNode->pNext;

			return *this;
		}

		iterator operator++(int)
		{
			iterator copyiter = *this;

			++(*this);

			return copyiter;
		}

		iterator& operator --()
		{
			assert(m_Owner && m_TargetNode);
			assert(m_Owner->m_pHead != m_TargetNode); // iterator 가 begin 이다.
			m_TargetNode = m_TargetNode->pPrev;

			return *this;
		}

		iterator operator --(int)
		{
			iterator copyiter = *this;

			++(*this);

			return copyiter;
		}

		bool operator == (const iterator& _otheriter)
		{
			if (m_Owner == _otheriter.m_Owner && m_TargetNode == _otheriter.m_TargetNode)
				return true;
			else
				return false;
		}

		bool operator != (const iterator& _otheriter)
		{
			return !((*this) == _otheriter);
		}

	public:
		iterator()
			: m_Owner(nullptr)
			, m_TargetNode(nullptr)
		{}

		iterator(List<T>* _Owner, Node<T>* _TargetNode)
			: m_Owner(_Owner)
			, m_TargetNode(_TargetNode)
		{}

		~iterator()
		{}

		friend class List<T>;
	};
};

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;
}

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

	if (nullptr != m_pHead)
	{
		m_pHead->pPrev = pNewNode;
	}
	else
	{
		m_pTail = pNewNode;
	}

	m_pHead = pNewNode;
	++m_CurCount;
}


template<typename T>
typename List<T>::iterator List<T>::insert(iterator _targetIter, const T& _Data)
{
	assert(_targetIter.m_Owner == this);

	if (m_pHead == _targetIter.m_TargetNode)
	{
		push_front(_Data);

		return iterator(this, m_pHead);
	}
	else
	{
		Node<T>* pNewNode = new Node<T>(_Data, _targetIter.m_TargetNode, _targetIter.m_TargetNode->pPrev);

		_targetIter.m_TargetNode->pPrev->pNext = pNewNode;

		_targetIter.m_TargetNode->pPrev = pNewNode;

		++m_CurCount;

		return iterator(this, pNewNode);
	}
}

template<typename T>
typename List<T>::iterator List<T>::erase(iterator _targetIter)
{
	return iterator();
}

1차 24.01.02
2차 24.01.03
3차 24.01.04
4차 24.01.05
5차 24.01.09

0개의 댓글