iterator (3)

Yama·2023년 12월 28일
0

어소트락 수업

목록 보기
29/55

begin함수 작동 방식.

	CArr<float> arrFloat;
	arrFloat.push_back(1.1f);
    
	CArr<float>::iterator iter; 		// 1
    
	iter = arrFloat.begin();			// 2
	class iterator;
	iterator begin()
	{
		iterator iter(this, -1);		// 3
		return iter;
	}
  • 1번이 arrFloat 함수의 첫번째를 가리키게 할려고 해서 this로 가리켰던것.
    • 3번의 this는 arrFloat이였당.
  • 2번은 CArr.h의 iterator begin의 return값을 받아서 반환타입을 대입을 받은것이다.

C++11 이전과 이후의 변화

  • C++11 이전
    • 이전 버전에서는 함수의 반환값을 레지스터에 저장한 후 메인 스택에 전달하는 방식을 사용했습니다.
  • C++11 이후
    • C++11 이후부터는 레지스터 대신 이동 연산자(생성자)를 사용하여 메인 함수에 값을 전달합니다. 이는 메모리 관리와 효율성 측면에서 중요한 변화입니다.

정리.

  • 메인 함수에서의 Iterator 호출
    • 메인 함수에서 arrFloat라는 iterator를 스택 메모리에서 호출합니다. 이는 arrFloat의 시작 지점을 나타냅니다.
  • begin 함수의 역할
    • begin 함수는 this 포인터를 통해 arrFloat를 참조합니다. 여기서 주목할 점은, begin 함수의 iterator가 arrFloat의 첫 번째 값을 지목한다는 것입니다.
  • iterator의 반환 과정
    • begin 함수는 첫 번째 값을 리턴하고, 그 함수의 스택은 종료됩니다. 이후, 메인 함수의 iter 변수는 원본 객체의 첫 번째 값을 복사받게 됩니다.
  • begin 함수의 목적
    • begin 함수의 주된 목적은 첫 번째 값을 지목하여 메인 함수의 변수에 반환하는 것입니다. 이 과정을 통해 메인 함수의 iter는 첫 번째 값과 그 주소를 가지게 됩니다.

객체 전달법.

	*iter
  • iterator는 내가 만든 자료형 타입이기떄문에 이런 자료형을 *을 붙이는 기능은 존재하지않기 때문에 만들어줘야한다.
  • 객체.operator+(인자);
    • operator+를 구현할때 인자를 구현해둿다.
  • *iter은 객체.operator;
    • 그래서 아무것도 안받는 형태로 구현을 해야된다.
	public:
		T operator *()
		{
			return this->m_pOwner->m_pData[m_Idx];
		}
  • 위의 방식은 원본만 주고 값을 iter 접근해서 바꿀수 없다.
  • T를 받아서 어떤 컨테이너의 몇번째를 가리키는지 가리킬려고 맴버를 구성해뒀당.
  • oWwner가 가리키고있는 실질적인 데이터를 관리하는 배열 객체다.
    • 다시 그안으로 들어가서 맴버가 3개인데 m_pData는 실질적인 관리하고있는 힙 메모리다.
      • 거기에 접근해서 내가 알고있는 인덱스 번째를 리턴해주는것.
	int* pInt = nullptr;
	int i = 0;
	pInt = &i;
	*pInt = 200;
  • 포인터
    • 원본 값 접근 가능
    • 접근해서 원본값 수정도 되야한다.
  • 위위의 코드는 반환하는거는 리턴된 객체가 원본 그자체를 가리키고있는게 아니다.
	*(*iter) = 3.3f;
	float data = *(*iter);
	data = *(*iter);
	public:
		T* operator *()
		{
			return this->m_pOwner->m_pData + m_Idx;
		}
  • 포인터를 이용한 방식이다. 뭔가 해괴망측함.
  • 이건 포인터를 이용했지만 내가 원하는 모양이 아님 진짜 포인터라는 느낌으로 이러테이터가 가리켜서 기능을 수행했다면 *하나면 수행햇어야지 왜 **을 하는것이냐?
	float data = (*iter);
	*iter = 3.3f;
	data = *iter;
	public:
		T& operator *()
		{
			return m_pOwner->m_pData[m_Idx];
		}
  • 리턴된 타입 그자체가 그 원본 그 자체를 참조하는 식인 &를 이용하면 된다.
  • 가리키고있는 곳을 접근해서 그 공간 자체를 참조 타입으로 반환하는것은 그 리턴된 그자체를 수정할수 있다.
  • 모양도 예쁨.

iterator 증감 연산자 구현

	CArr<float> arrFloat;
	arrFloat.push_back(1.1f);
	arrFloat.push_back(2.2f);

    ++iter;
		void operator ++()
		{
			++m_Idx
		}
        
        void operator --()
		{
			--m_Idx;
		}
  • 인덱스를 가져와서 ++,--해주면된다.
  • 문제가 존재
  • 2개 데이터를 들고있다. 허용가능한 인덱스는 1까지 가능한데 ++ 하고났더니 인덱스가 2번쨰에 도달하면 3번쨰라는뜻 동적배열은 근데 3개의 데이터가 아니라 2개의 데이터를 가지고있다는 것.
  • 데이터 2개밖에 없는 애한테 ++계속하면 내가 가지고있는 인덱스를 초과할수도있다.
	for (; vec_iter != vecShort.end(); ++vec_iter)
	{
		cout << *vec_iter << endl;
	}
  • 어제 백터쪽에서는 end라는 개념이 존재한다.
    • begin상태부터 출발해서 end상태에 도달하는지 체크하는것. end에 도달하지 않았다면 계속 ++하라는 뜻
  • iterator은 end라는 반복자 패턴에서는 끝의 개념이 중요하다.
  • end iterator - iterator 가 컨테이너가 보유한 데이터의 마지막 다음을 가리키는 상태라고 생각한다.
    • 이렇게 설계한 이유
      • 만약에 end가 마지막 데이터를 가리키는 형태가 되었다면 end에 도달했는데 같지 않아야 true인데 end랑 같아버려서 마지막 데이터는 출력을 못하기 떄문이다.
	vector<int> vecInt;
	vecInt.push_back(1);
	vecInt.push_back(2);

	vector<int>::iterator veciter = vecInt.begin();
	++veciter;
	++veciter;	// end
	++veciter;	// 1

  • 1번에서 크래쉬가 난다.
  • Expression can't increment vector iteraotr past end
    • 벡터에 이터레이터를 end를 지나서 증가시킬수 없다는 뜻.
		void operator ++()
		{
			if (m_pOwner && -1 == m_Idx)		// 2
			{
				// end iteraotr 에 ++ 함수를 호출한 경우
				assert(nullptr);
			}

			++m_Idx;

			if (m_pOwner->m_CurCount <= m_Idx)	// 1
			{
				m_Idx = -1;
			}
		}
  • 1번
    • 저 이터레이터가 end상태를 구별을 할수 있는 할수 있는 값으로 마킹을 해둔것.(-1)
    • 데이터 2개밖에 없는데 ++ 했더니 인덱스가 2가 됬다는 뜻 이미 한번 넘어섰다는 의미
    • 인덱스를 -1를 넣어버렸다.
      • 특정 컨테이너를 알면서 그게 -1을 가리킨다 -1인덱스는 없기때문에 그러면 이 컨테이너는 end상태라는걸 표현한것이다.
  • 2번
    • 만약에 관리자(m_pOwner)를 알고 가리키고 있는 m_Idx의 값이 -1이 였다면 애초에 end iterator였다는것. 그거에 ++을 호출 시키면 안되기 떄문에 assert를 걸어 버린것.

end 구현.

arrFloat.end();
	iterator end()
	{
		iterator iter(this, -1);
		return iter;
	}
  • 자기가 관리하고있는 자기가 보유하고잇는 맨마지막에서 다음
  • -1을 넣어서 end라는걸 알려줌.(이미 -1로 규칙으로 정해둠)
    • -1은 마지막에서 +1 다음을 가리키고 있다고 설게했기 때문에 -1 넣었다.

== 구현

	iter == arrFloat.end();	// 1
		bool operator == (const iterator& _otheriter)							// 2
		{
			//*this; _otheriter;												// 1
			if (m_pOwner == _otheriter.m_pOwner && m_Idx == _otheriter.m_Idx)	// 3
			{
				return true;
			}
			else
			{
				return false;
			}
		}
  • 1번의, 비교하는거니까 자기는 *this(iterator), _otheriter는 다른 iterator
  • 2번 원본을 레퍼런스로 받아와서 비교만 할거지 원본은 수정안할거라 const iterator다.
  • 3번 인덱스만 같다고 해서 동일한 이터레이터로 볼수 없다
    • 같은 상태라는것은 같은 컨테이너 안에서도 같은 순번을 가지고 있어야 같은 iterator다.

!= 구현

		bool operator != (const iterator& _otheriter)
		{
        	// 1
			//if (m_pOwner == _otheriter.m_pOwner && m_Idx == _otheriter.m_Idx)	
			//{
			//	return false;
			//}
			//else
			//{
			//	return true;
			//}
            // 2
			return !((*this) == _otheriter);
		}
  • 1번은 ==의 반대의 반환값을 준것이다.
  • 2번은 1번을 줄인것이다.

강의 코드

main.cpp

#include <iostream>

#include "CArr.h"

#include <vector>
using std::vector;

using std::cout;
using std::endl;
using std::cin;


class MyClass
{
private:
	int	m_i;

public:
	// 포함 클래스(Inner Class)
	// 포함 클래스가 선언된 클래스(MyClass) 의 private 까지 접근할 수 있다.
	class Inner
	{
	private:
		float m_f;
	};
};

int main()
{
	int size = 0;

	MyClass c;
	size = sizeof(MyClass);

	MyClass::Inner in;
	size = sizeof(MyClass::Inner);


	CArr<float> arrFloat;
	arrFloat.push_back(1.1f);
	arrFloat.push_back(2.2f);
	arrFloat.push_back(3.3f);

	CArr<float>::iterator iter;
	iter = arrFloat.begin();

	/*float data = (*iter);
	*iter = 3.3f;
	data = *iter;*/

	iter = arrFloat.begin();
	for (; iter != arrFloat.end(); ++iter)
	{
		cout << *iter << endl;
	}

	return 0;
}

CArr.h

#pragma once

#include <assert.h>

// 클래스 템플릿
template<typename T>
class CArr
{
private:
	T*		m_pData;
	int		m_MaxCount;
	int		m_CurCount;

public:
	void push_back(const T& _Data);
	
	class iterator;
	iterator begin()
	{		
		iterator iter (this, 0);
		return iter;
	}

	iterator end()
	{
		iterator iter(this, -1);
		return iter;
	}

private:
	void Realloc();

	// 헤더에 맴버함수를 구현 한 경우
	// 인라인 함수로 처리	
	// 헤더에 실제 구현을 함으로써, 호출시 해당 구현을 그대로 복사한다.
	// 매크로 함수와 유사, 함수 호출 시 별도의 스택 생성 해제 비용이 없다.

	// 인라인 함수를 남발하는 경우 발생하는 문제.
	// 함수 구문이 여기저기 호출하는 곳마다 복사되어서 코드의 양이 엄청나게 늘어날 수 있다.

	// 인라인 처리를 할 함수들의 특징.
	// 구문이 짧다(기능이 간단한다, Get, Set 종류의 함수)
	// 자주 호출 되는 함수
public:
	int size() { return m_CurCount; }	
	int capacity() { return m_MaxCount; }
	T at(int _Idx) { return m_pData[_Idx]; }

	// 반환타입을 참조형태로 반환, 반환된 값을 수정하면 원본값이 수정되는 개념
	T& operator[](int _Idx) { return m_pData[_Idx]; }


public:
	CArr();
	~CArr();

	// 1. 반복자가 접근하려는 데이터를 관리하는 컨테이너의 private 에 손쉽게 접근이 가능
	// 2. 컨테이너 구분없이 동일한 이름을 가져서 반복자 역할 클래스의 이름을 손쉽게 알 수 있게 통일 시킴
	//    (이너클래스가 선언된 컨테이너 클래스가 각각 다르기 때문에 이름 중복문제가 발생하지 않는다.)
	class iterator
	{
	private:
		CArr<T>*	m_pOwner;
		int			m_Idx;

	public:
		T& operator *()
		{
			return	m_pOwner->m_pData[m_Idx];
		}

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

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

		void operator ++()
		{
			if (m_pOwner && -1 == m_Idx)
			{
				// end iteartor 에 ++ 함수를 호출한 경우
				assert(nullptr);
			}

			++m_Idx;

			// end iterator - iterator 가 컨테이너가 보유한 데이터의 마지막 다음을 가리키는 상태
			if (m_pOwner->m_CurCount <= m_Idx)
			{
				m_Idx = -1;
			}
		}

		void operator --()
		{
			--m_Idx;
		}




	public:
		iterator()
			: m_pOwner(nullptr)
			, m_Idx(-1)
		{
		}

		iterator(CArr<T>* _Owner, int _idx)
			: m_pOwner(_Owner)
			, m_Idx(_idx)
		{}

		~iterator()
		{
		}

		
	};	
};

template<typename T>
CArr<T>::CArr()
	: m_pData(nullptr)
	, m_CurCount(0)
	, m_MaxCount(2)
{
	//m_pData = (int*)malloc(sizeof(int) * m_MaxCount);
	m_pData = new T[m_MaxCount];
}

template<typename T>
CArr<T>::~CArr()
{
	//free(m_pData);
	delete[] m_pData;
}

template<typename T>
void CArr<T>::push_back(const T& _Data)
{
	// 데이터가 꽉 차있으면, 
	if (m_MaxCount <= m_CurCount)
	{
		// 저장 공간 추가할당
		Realloc();
	}

	m_pData[m_CurCount++] = _Data;
}

template<typename T>
void CArr<T>::Realloc()
{
	// 1. 새로운 공간 할당
	m_MaxCount *= 2;
	T* pNew = new T[m_MaxCount];

	// 2. 기존 데이터 이동
	for (int i = 0; i < m_CurCount; ++i)
	{
		pNew[i] = m_pData[i];
	}

	// 3. 기존 공간 해제
	delete[] m_pData;

	// 4. 새로운 공간을 가리킨다.
	m_pData = pNew;
}

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

0개의 댓글