iterator (1)

Yama·2023년 12월 27일
0

어소트락 수업

목록 보기
27/55

동적 배열 패턴

	CArr<float> arr_float;
  • 동적배열(CArr)을 하는 타입을 float으로 지정했다.
	std::vector<float> vecFloat;
	vecFloat.push_back(1.1f);
  • 표준 라이브러리에서 제공하는 벡터 또한 저장타입을 알려주면 객체를 만들었다.

표준라이브러리의 벡터

std::vector<int> vecInt;
  • std 이름 공간에 vector라는게 들어가있다 이걸 int로 클래스 템플릿을 바꿔서 vecInt로 만들었당.

reserve와 resize의 차이

  • 둘다 공간을 늘려주는 것이다 방식이 다름.
  • reserve
    • 공간을 미리 잡는다는걸 예약을 건다는 것.
	std::vector<float> vecFloat;
  • 어차피 공간이 모자르면 늘려나갈것이다 벡터는 처음 만들어봤을떄 capacity(맥스카운터)는 0에서 시작한다
    • 데이터가 들어오지않으면 최대 용량은 0이였을것이다.
	vecFloat.push_back(1.1f);
  • 하나가 들어오면 한칸이 늘어난다. 확장규칙이 작다.(조금씩 늘려나간다)
  • 문제 상항
    • 동적 배열을 사용하다가 공간이 부족해지면, 데이터를 새로운 더 큰 공간으로 복사하고, 기존 데이터를 이동시켜야 하는 작업이 필요하며, 이로 인해 연산 비용이 증가합니다.
    • 그러나 동적 배열을 관리할 때 미리 예약(또는 확보)된 공간을 가지고 있는 경우 데이터를 삽입할 때 불필요한 재할당 작업을 피할 수 있습니다.
      • 미리 메모리 공간을 예약하면 성능을 향상시킬 수 있습니다.
    • reserve(100)을 호출하면, vecInt는 100개의 요소를 수용할 공간을 가지게 되며, 이후 데이터를 추가해도 메모리 재할당이 발생하지 않습니다.
      • 이는 성능을 향상시킬 수 있는 중요한 최적화 기법 중 하나입니다.
    • 따라서 예상되는 데이터 양에 따라 미리 메모리 공간을 예약하는 것은 좋은 프로그래밍 실천법 중 하나입니다.
	std::vector<int> vecInt;
	vecInt.reserve(100);

  • 그렇다면 CurCount는 0이고 MaxCount가 100이 된다.
    • 데이터를 50개를 넘는다면 최대 효율로 바로바로 들어갈것이다.
    • 데이터 이전 확장 복사값이 입력되는 과정에서 들지않을것.
  • 이걸 reserve안했다면 new ,delete, 데이터 옮기는 연산이 많이 들은걸 reserve가 해결해줬다.
  • 들어갈 공간이 예측이 뒨다면 미리미리 공간을 reserve로 잡아놓고 하면 좋다.
  • resize
    • 갯수를 디폴트 값으로 채워버린다.
    • int로 벡터를 만들었으니 초기값은 0인 int값으로 채워진다.
    • CurConut 100, MaxCount는 데이터 확장 규칙에 따라 더 커진다.
  std::vector<int> vecInt;
  vecInt.resize(100);
  vecInt[10] = 20;

  vecInt.push_back(100); 	// 1
  vecInt[10] = 20; 			// 2
  • 101번쨰 칸에 100이 들어가고 현재값 101로 늘어날것이다.
  • 해당 자료형의 기본 자료형으로 초기화 된다. 클래스엿다면 그 클래스에 생성자를 호출해서 초기화해준다.
  • 2번은 이미 잡힌 공간인 11번쨰칸에 20을 대입한다.
	vecInt.reserve(100);
    vecInt.at(10) = 11;
  • 예외가 발생한다.
    • 이유: 벡터입장에서는 공간 100개를 늘려놓은거지 데이터를 하나도 입력받은게 없기 때문이다.
    • 현재값이 0인데 11번째 접근할려고하니까 예외처리가 된것이다.

정리

  • reserve는 동적 배열의 특징상 데이터가 입력될떄 공간이 모자라면 새로 확장하는 구조를 피하기 위해 넉넉하게 미리 데이터를 넣을 공간을 할당해준다.
  • 따라서 현재 입력된 데이터 개수는 0, Capacity가 늘어난다.
  • resize 는 reserve 처럼 동적배열이 관리하는 데이터를 입력하는 공간의 크기를 늘리는 점은 같으나
  • reserve 와 다르게 resize는 입력 데이터 타입의 기본 값으로 공간을 채움
  • 따라서 입력 데이터 개수(size) 자체가 늘어난다.

지역 변수 vector 객체와 관리 공간을 교체해서 늘어난 공간을 다시 줄일 수 있다.

  • 벡터(동적배열)함수는 사이즈를 늘리는거에만 초점에 있지 더 줄어들지는 않는다.
std::vector<int> g_vecInt;

int main()
{
	std::vector<int> vecTemp;
	vecTemp = g_vecInt;			// 1
	g_vecInt.swap(vecTemp);		// 2
 	
    return 0;
}
  • 기존의 관리(g_vecInt)하고있던 애랑 스왑을 해준다.
  • 지역 변수 벡터를 하나 만들어서(vecTemp)이것은 새로만든거라 관리하는 공간이 0일것.
  • 1은 기존의 데이터를 넘겨준것.
  • 2는 기존의 관리하고있던 힙영역을 스왑을 해준다.

디자인 패턴 (설계 유형)

  • 매니저 패턴, 반복자(iterator) 패턴, 싱글턴 패턴

반복자(iterator) 패턴(유형)

  • 컨테이너가 가지고 있는 데이터를 접근할떄 반복자를 통해서 특정 데이터에 접근하는 방식
    • 컨테이너 들마다 데이터를 관리하는 방법이 다르다.
      • 동적배열 : 연속적
      • 리스트 : 띄엄띄엄
      • 레드-블랙 트리(Red-Black tree) : 중위순회?
    • 다음 데이터 접근할떄
      • 동적 배열 : 인덱스에 다음거
      • 리스트 : 다음의 주소값
      • 레드-블랙 트리(Red-Black tree) : 중위 순회
    • 다음으로 접근한다는 개념이 컨테이너마다 다 다르다.
  • 컨테이너의 특징에 맞추어서 iterator을 통해 다음으로 이동하면 컨테이너의 특징에 맞추어서 다음 데이터에 접근이 가능하다
  • 컨테이너가 관리하는 데이터를 접근하는 데 있어서 종류에 구분 없이 통일성 있게 사용가능하다.
    • 컨테이너를 만든 사람은 iterator도 제공한다.
    • 자료구조의 데이터를 같이 제공되는 iterator을 통해서 사용자는 데이터에 접근할수 있다.

동적 배열의 데이터 관리 방법.

	std::vector<short> vecShort;

	for (int i = 0; i < 100; ++i)
	{
		// 명시적으로 short변경.
		vecShort.push_back((short)(i + 1)); 		// 1
	}
	for (size_t i = 0; i < vecShort.size(); ++i)	// 2
	{
		//printf("%d\n", vecShort.at(i));			// 3.1
		//cout << vecShort.at(i) << endl;			// 3.2
		cout << vecShort[i] << endl;				// 3.3
	}
  • 1번은 i는 int타입이니까 short벡터이므로 명시적으로 short 캐스팅해서 적어줬다.
  • 2번의 size_t의 크기로 했당.(8바이트)
    • 내부적으로 주소연산을 하고있을거같아서 _int64를 했을거 같다.?
    • 벡터는 자기가 관리하는 데이터의 사이즈를 size_t타입으로 반환하기 때문이다.
    • 나도 size_t타입으로 동일한 형태로 i값을 선언했다리.
  • 3번이랑 3.1,3.2는 같은 의미다.

리스트의 데이터 관리 방법.

	list<short> shortList;

	for (int i = 0; i < 100; ++i)
	{
		shortList.push_back(short(i + 1));
	}
    
    for (size_t i = 0; i < shortList.size(); ++i)
	{
		//printf("%d\n", shortList.at(i));
		//cout << shortList.at(i) << endl;
		//cout << shortList[i] << endl;		// 1
		//cout << shortList.at(i) << endl;	// 2
		//cout << shortList << endl;
	}
  • 1번처럼 리스트를 연결해줄려하면 이런 operator은 인덱스 형태로 접근할 수 없어서 리스트에서는 어색하다.
    • 리스트는 노드를 연결하는 것이디 때문이다.
    • 리스트는 애초에 데이터를 연속적인 공간에서 관리를안하고 끊어져 있어서 저런 오퍼레이터가 있으면 어색하다.
  • 2번은 리스트에 인덱싱에 불리한 구조다.(불가능하다)
    • 일부의 오해의 소지가 있어서 구현을 안해둠.
      • 이게 가능하면 말만 리스트지 실제 데이터 관리는 벡터인가? 이런걸로 오해가능.
    • 한번에 10번째 칸을 띄어서 데이터를 접근하겠다는건대 리스트는 데이터 인덱싱이 불가능하기때문에 at.도 말이 안된다.
    • n번쨰 데이터에 접근할려고 하면 n번 연산을 해야하므로 인덱싱에 불리하다.

iterator 방식으로 설계

벡터

	std::vector<short> vecShort;

	for (int i = 0; i < 100; ++i)
	{
		// 명시적으로 short변경.
		vecShort.push_back((short)(i + 1));
	}

	vector<short>::iterator vec_iter = vecShort.begin();

	for (; vec_iter != vecShort.end(); ++vec_iter)
	{
		cout << *vec_iter << endl;
	}

리스트

	list<short> shortList;

	for (int i = 0; i < 100; ++i)
	{
		shortList.push_back(short(i + 1));
	}
    
	list<short>::iterator list_iter = shortList.begin();
	for (; list_iter != shortList.end(); ++list_iter)
	{
		cout << *list_iter << endl;
	}
  • iterator를 사용하면 비슷한 데이터 순회 방식을 선택할수 있다.
  • 둘다 데이터 접근방식이 다르지만 비슷하게 접근이 가능하다.
  • iterator를 사용하면 컨테이너의 특징에 구회받지 않는다.

iterator 방식 분석

	vector<short>::iterator vec_iter = vecShort.begin();	// 1
    
	for (; vec_iter != vecShort.end(); ++vec_iter)			// 2
	{
		cout << *vec_iter << endl;							// 3
	}
  • 1번은 vector<short'>클래스에 선언된 iterator클래스를 vec_iter의 이름으로 사용하면서 vecShort.begin()값으로 초기화 해준것이다.
  • 2번은 ++vec_iter한 형태는 원래 없지만 형상태에서 두번쨰 데이터 꺼내주고 ++해서 세번쨰 끌어방식을 iterator안에 ++연산자를 오버로딩한것이다.
    • ++연산자를 다음 데이터를 가리키도록 오버로딩한것이다.
  • 3번은 * 연산자 오버로딩을 한것이다.
    • *을 붙었을떄 자기를 리턴시켜주는 것을 클래스안에 구현해둔것.
    • 마치 주소를 접근 연산 하듯이 가리키고 잇는 데이터를 꺼내주는것을 오버로딩한것이다.

set 함수 iterator 구현.

#include <set>
using std::set;

int main()
{
	set<int> intset;

	for (int i = 0; i < 100; ++i)
	{
		intset.insert(i);
	}

	set<int>::iterator setiter = intset.begin();

	for (; setiter != intset.end(); ++setiter)
	{
		cout << *setiter << endl;
	}
}

강의코드

main.cpp

#include <iostream>
using std::cout;
using std::endl;
using std::cin;

#include <vector>
using std::vector;

#include <list>
using std::list;

#include <set>
using std::set;

#include <map>
using std::map;
using std::make_pair;

#include "CArr.h"

class CTest
{
public:
	CTest()
	{}
	~CTest()
	{}
};

std::vector<int> g_vecInt;


int main()
{
	int size = sizeof(CTest);
	CTest* pArr = (CTest*)malloc(16);
	free(pArr);

	CTest* pArr1 = new CTest;
	delete pArr1;

	CTest* pArr2 = new CTest[10];
	delete[] pArr2;

	CArr<int> arr;
	CArr<int> arr1;
	arr.push_back(10);
	arr.push_back(20);
	arr1.push_back(30);

	int value = 0;
	value = arr.at(0);
	value = arr.at(1);
	value = arr.at(2);

	CArr<float> arr_float;

	arr_float.push_back(1.1f);
	arr_float.push_back(2.2f);
	arr_float.push_back(3.3f);

	float fvalue = 0;
	fvalue = arr_float.at(0);
	fvalue = arr_float.at(1);
	fvalue = arr_float.at(2);


	std::vector<float> vecFloat;
	vecFloat.push_back(1.1f);
	fvalue = vecFloat.at(0);
	fvalue = vecFloat[0];

	vecFloat.size();
	vecFloat.capacity();
    
	vecFloat.reserve(10);
	vecFloat.resize(10);
    
	std::vector<int> vecInt;
	vecInt.reserve(100);
	//vecInt.at(10) = 11;

	//vecInt.resize(110);
	//vecInt[10] = 20;

	std::vector<int> vecTemp;
	vecTemp = g_vecInt;
	g_vecInt.swap(vecTemp);

	std::vector<short> vecShort;

	for (int i = 0; i < 100; ++i)
	{
		vecShort.push_back((short)(i + 1));
	}


	//for (size_t i = 0; i < vecShort.size(); ++i)
	//{
	//	//printf("%d\n", vecShort.at(i));
	//	//cout << vecShort.at(i) << endl;
	//	cout << vecShort[i] << endl;
	//}	

	vector<short>::iterator vec_iter = vecShort.begin();

	for (; vec_iter != vecShort.end(); ++vec_iter)
	{
		cout << *vec_iter << endl;
	}


	list<short> shortList;

	for (int i = 0; i < 100; ++i)
	{
		shortList.push_back(short(i + 1));
	}

	//for (size_t i = 0; i < shortList.size(); ++i)
	//{
	//	//printf("%d\n", vecShort.at(i));
	//	//cout << vecShort.at(i) << endl;
	//	//cout << shortList << endl;
	//}

	list<short>::iterator list_iter = shortList.begin();

	for (; list_iter != shortList.end(); ++list_iter)
	{
		cout << *list_iter << endl;
	}

	set<int> intset;

	for (int i = 0; i < 100; ++i)
	{
		intset.insert(i);
	}

	set<int>::iterator setiter = intset.begin();

	for (; setiter != intset.end(); ++setiter)
	{
		cout << *setiter << endl;
	}
    
	return 0;
}

CArr.h

#pragma once


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

public:
	// 
	void push_back(const T& _Data);			// 1

private:
	void Realloc();

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

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

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

template<typename T>
void CArr<T>::push_back(const T& _Data)		// 1
{
	if (m_MaxCount <= m_CurCount)
	{
		Realloc();
	}
	m_pData[m_CurCount++] = _Data;
}

template<typename T>
void CArr<T>::Realloc()
{
	m_MaxCount *= 2;
	T* pNew = new T[m_MaxCount];

	for (int i = 0; i < m_CurCount; ++i)
	{
		pNew[i] = m_pData[i];
	}

	delete[] m_pData;

	m_pData = pNew;
}

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

0개의 댓글