CPP_어소_66_erase (2)

CJB_ny·2022년 7월 7일
0

CPP_AROTHO

목록 보기
65/83
post-thumbnail

erase함수 구현 하기

erase함수가 인자로 받아온 iterator가 가르키는 부분(데이터를)을 삭제를 하겠다라는 말이다.

-1이라면 end iterator라고 보기로 했었다.

중간에 예외처리가 하나 더 늘어나야한다. 👍

지금 m_dataCout <= iter.m_i_indexNumber인데

데이터가 10개 기준일 때 m_dataCount가 10이라면은

인덱스가 10이거나 그 이상이면 안된다.

10개이면 index는 최대가 9여야 하니까. ㅇㅋ?


데이터 옮기기? ❗

만약에 이런 경우

0 1 2 3 인데 2를 삭제를 하고 3을 2자리에 옮기면

m_dataCount는 하나 줄여준다.

그다음에 3의 자리는 어떻게 해야할까?

=> 굳이 다른 값으로 초기화 하거나 할 필요 없다.

다음 데이터는 3의 자리에 들어올 것이기 때문이다.

우리는 그냥 옮겨 오기만 하면된다.

삭제 규칙 👍

데이터가 4개 일때

인덱스 2번을 삭제 해달라하면 옮기는 횟수 = 1번

인덱스 1번을 삭제 해달라고하면 옮기는 횟수 = 2번

즉,

dataCount - (indexNumber + 1) == n번 이다.

예시

dataCount == 10일때 0번째 인덱스 삭제해달라고하면

총 몇번 땡겨야하나?

=> dataCount - (index(0) + 1) 이니까

10 - (0 + 1) = 9번 당기면 된다.

옮겨오는 방식 ❗

1번 인덱스를 삭제한다고 했을 때

2번 인덱스에서 먼저 1번 인덱스로 옮겨야됨.

반환하는 것의 뜻 ❗❗❗

"복사 생성자"

이렇게 for문을 돌아서 반환되는 iterator는 무엇이냐?

지금 erase 사용방법을 보면은

erase를 해서 다시 vector< int > :: iterator testIter로 받아주고 있는데


iterator를 다시 복사본을 넘겨준다.

=>

복사생성자?? 이다.

왜냐하면

vector< int >의 iterator클래스의 객체를 만드는데 이 객체의 이름은

testIter이고 생성하자마자

할당을 받는다 == 복사생성자.


지금 erase를 하게되면 원래의 iterator는 기능을 상실해서 오류가 난다.

그래서 기능을 상실했기 때문에 문제가 없기 하게하기 위해서
erase함수가 다시 정상적인 iterator를 반환 하는 것이다.

또한 iter의 index는

바뀔 이유가 없기 때문에

복사생성자를 통해 넘겨주면 될거같다.

this, m_startAddr을 넘겨주어야한다.

표준 라이브러리의 vector와 비교

vector에서는 erase를 하고나서 iterator(vecIter) '*' 접근을 할려고하면은

에러가 난다.

반면에 우리가 방금 구현한 erase함수에는 이게 그대로 사용이 가능함.

복사생성자를 통해 return으로 사용가능하고 문제없는 iterator를 반환했기 때문에.

std::vector와 똑같이 동작을 하려면 인자로 받아온 원본녀석 한테 알림을 주어야한다.

더이상 사용할 수 없다고.

변수 추가 boolean

iterator의 멤버 변수로 bool m_i_valid 추가하고

기본생성자에서는 false, 일반 생성자? 에서는 true로 초기화를 해준다.

(그런데 인자를 받는 생성자 조금만 수정해주도록 하자.)

operator * 조건 추가

이렇게 유효하지 않기 때문에

m_i_valid가 false인 경우 에러를 내뱉도록 해준다.

즉, false == m_i_valid이니까 => !m_i_valid

false인 경우 "참"이니까,

true일 경우가 들어오면 반전 시켜서 false로 보는 것임.

그려면 false == m_i_valid와 같으니까

erase 수정 ❗❗❗

m_i_valid 덕분에 erase함수가 조금더 구현이 쉬워졌다.

왜냐?

일단 erase함수를 수정할 수도 있으니까 const는 없애주고 ref로만 받아주도록 하자.(원본을 받는다)

그리고

참조로 받아온 iter의 m_i_valid는 false로 더이상 "유효하지 않다"라고 명시해준다.

그리고 return 하는 것은 복사본인데

복사 생성자를 통해서

m_i_valid는 true인 유효한 녀석을 반환 해줄 것이다.

이렇게되면 erase하였을 때 std::vector처럼

삭제하고나서의 iterator는 유효하지 않다라고 명시가 가능하고

반환하는 녀석은 유효한 iterator로 반환이 가능하다.

매모리 구조 특징상 같은 곳을 가르키고 있지만(데이터도 똑같지만)
인자로 받은 녀석과의 차이는 m_i_valid가 true, false인지의 차이이다.

그래서 erase하고나서는

반드시 되돌려 받아야한다.

이거 이외에는 사용할 수 없는 iterator이기 때문에...

erase 함수 조건 수정 && 내가 컴파일 에러 냇던 부분들

지금

iter.m_i_vecAddr->m_startAddr != this->m_startAddr || this->end() == iter || this->m_dataCount <= iter.m_i_indexNumber

이런데

이게 아니라

iter의 m_i_vecAddr이 CArr* 타입이라

m_i_vecAddr의 주소와 해당 함수를 호출한 객체의 주소가 같은지 비교를 하는 것이다.

객체

지금 이부분에서 내가 iter. 을 안찍어 주었었는데 이렇게 수정을 함.

짝수 제거 ❗

if문에서 조건을 보고 삭제를 해주도록 한다.

그런데 이렇게하면 문제가 발생하는데

지금 68번째 줄에서 가르키고 있는 myIter2를 제거를 하는데

우리가 이전에 뭐했나? 제거하고 나면 m_i_valid = false가 되는데

그다음에 for문에서 ++myIter2 이게 될까? => 안된다.

그래서 다시 정상 동작하기 위해서 다시 받아주어야한다.

기억!

1~5제거 ❗❗

근데이거 출력하면

2, 4 있음... 시발 왜??

이거 자세히 보면은

veciter가 가르키는 것을 가변배열안에서 제거를 시키고

그 다음을 가르키는 것을 되돌려주었다.

먼저 1이 제거 되었다고 생각을 하면은 다음가르키는게 2이다.

(수가 1부터 10까지 벡터에 저장되어있다고 했을 때)

그러면 erase를 하고나서 받는 것은 2인데

for문의 마지막 조건으로 ++veciter를 해주고있음

그래서 2, 4 남음.

그래서 1~5는 맞지만 안걸리는 경우

else로 추가를 해주어야한다.

수행하는 구문을 없애줌.

아까 터진이유

데이터 10개일 경우

지우다 보면 10번째에서 지우고 다음 end iterator를 반환을 받으니까 터진 거였음.

🛫 내가 햇갈렸던 문법 정리 🛫

1. this

erase함수의 시작부분에서 if로 예외처리를 해주었었는데

if (iter.m_i_vecAddr != this || end() == iter || m_dataCount <= iter.m_i_indexNumber)
	{
		assert(nullptr);
	}

iter.m_i_vecAddr은 CArr* 타입이다.

나는 this가 해당함수를 호출한 "객체"의 변수명? 정도로만 알고 넘어갔는데

iter.m_i_vecAddr != this 처럼 연산이 가능한 이유는

https://digiconfactory.tistory.com/entry/C-%ED%8F%AC%EC%9D%B8%ED%84%B0-this-pointer-C-this%EC%9D%98-%EC%9D%98%EB%AF%B8%EC%99%80-%EC%82%AC%EC%9A%A9%EB%B2%95

"this는 객체의 주소를 저장한 포인터 변수"

이기때문에 erase를 호출한 객체도 CArr 타입의 포인터 변수라면

CArr* this정도.

즉, ❗❗

포인터 변수는 -> 사용
객체는 . 사용

#include <iostream>
using namespace std;

// this 사용법을 이해하기 위한 cs파일
// [ 알아두고 갈 것]
// this 포인터는 객체의 주소를 저장한 포인터 변수이다.
// 사용자가 작서하지 않아도 this를 사용할 수 있다.
// 자동으로 생성이 되지만, public으로는 접근이 불가능하다.
// 내부 메소드 안에서 사용해야한다.

template <typename T>
class MyPosition
{
private:
	T			posX;
	T			posY;

public:
				MyPosition();
				~MyPosition();
				 
public:
	void		SetPosX(const T& valueX);
	T&			GetPosX();
	void		SetPosY(const T& valueY);
	T&			GetPosY();
	void		ShowThisAddr();

};

#pragma region 함수구현부
template <typename T>
MyPosition<T>::MyPosition()
	:
	posX(0),
	posY(0)
{
}
template <typename T>
MyPosition<T>::~MyPosition()
{
}

template <typename T>
typename void MyPosition<T>::SetPosX(const T& valueX)
{
	 this->posX = valueX;
}

template <typename T>
typename void MyPosition<T>::SetPosY(const T& valueY)
{
	this->posY = valueY;
}

template <typename T>
typename T& MyPosition<T>::GetPosX()
{
	return this->posX;
}

template <typename T>
typename T& MyPosition<T>::GetPosY()
{
	return this->posY;
}
template<typename T>
void MyPosition<T>::ShowThisAddr()
{
	cout << "this : 0x" << this << endl;
}
#pragma endregion

int main()
{
	MyPosition<int> myPos; // myPos라는 MyPosition<int>의 객체생성
	
	cout << "객체만 생성하고의 x : " << myPos.GetPosX() << endl;
	cout << "객체만 생성하고의 y : " << myPos.GetPosY() << endl;
	cout << endl;

	myPos.SetPosX(10);
	myPos.SetPosY(20);

	cout << "set 함수 호출하고 나서의 x : " << myPos.GetPosX() << endl;
	cout << "set 함수 호출하고 나서의 y : " << myPos.GetPosY() << endl;
	cout << endl;

	// ShowthisAddr은 this의 주소를 출력한다. 즉 객체의 포인터 변수 주소값을 출력한다.
	myPos.ShowThisAddr();

	MyPosition<int>* ptrPos; // 포인터 변수
	ptrPos = &myPos;

	cout << "ptrPos : 0x" << ptrPos << endl;
	cout << endl;

	return 0;
}

출력결과는

다음과 같다.

주소를 출력 하는 부분에서 알 수 있는게

이는 this가 myPos 객체의 주소라는 것이다.

ptrPos의 this와 myPos의 this가 일치하는 것을 볼 수 있다.

2. '.' '->' 차이점

둘다 일단, 클래스나 구조체에서 자체 필드(변수)를 사용시 이용되는 연산자이다.

'.' : 참조로 요소 선택(점 앞에 변수나 포인터가 아닌 저장하려는 데이터가 있을 때 사용)

'->' : 포인터 요소 선택 (화살표 앞에 포인터가 있을 경우 사용)

제일 밑에서

Monster* monster2 = new Monster();

는 동적으로 힙메모리에 new키워드로 생성한 곳(주소)를 Monster 포인터 변수로 monster2라는 이름으로 "힙 메모리의 주소"를 monster2에 가지고 있는 것이다.

이것은 포인터 변수 이기 때문에

멤버 변수에 접근을 할 때는 monster2->Damage 와 같이 -> 연산자를 사용 하여야한다.


https://devbot.tistory.com/70


profile
https://cjbworld.tistory.com/ <- 이사중

0개의 댓글