[C++] 클래스 이용한 배열

꿈별·2023년 5월 30일
0

C++

목록 보기
19/27

✔클래스로 가변배열 만들기

가변배열
: 힙 메모리 영역에 동적할당해서 데이터 넣어줬었음
배열 시작주소, 최댓값, 한곗값 필요

  • 클래스 외부에 멤버 공개 안 함 (private)
    (💡접근 제한자 명시 안 하면 기본적으로 private)

  • 생성/소멸자는 public
    (private이면 컴파일러가 호출 못 함)

  • 동적할당
    : C의 malloc, free 대신 -> C++의 new, delete 키워드 사용하기

  • 생성/소멸자는 헤더 파일에 선언하고 cpp 파일에서 정의
    -> 클래스 밖에서 구현할 땐 어디에 선언된 멤버 함수인지 지칭해줘야 한다.

CArr::CArr()	//생성자
{
}

CArr::~CArr()	//소멸자
{
}

-> CArr 클래스 안에 선언된 생성/소멸자 CArr()/~CArr()라는 뜻

  • 멤버 변수 선언했던 순서대로 초기화해 주는 것이 좋다.

  • 만약

int* p = new int;

와 같이 자료형 한 개 크기만큼 할당해 줬다면

delete p;

그냥 이렇게 delete 하나로 해제하면 된다.

-> But 가변 배열은 여러 묶음으로(배열 형태로) 받아왔으니 delete[] 포인터명 형태로 해제한다.

💡 new / delete

C의 malloc은 내가 요청한 크기만큼 메모리를 동적 할당해서 주소 반환해주고, 그 공간을 포인터로 가리켜 사용하였다.
(할당받은 메모리를 어떤 자료형으로 사용할지는 사용자 마음. 가리키는 포인터 타입에 따라 갈림)

  • 클래스는 객체 생성될 때 생성자가 꼭 호출되어야 하는데, 이 때 생성자 호출은 컴파일러가 알아서 한다.
    • 컴파일러 입장에서는 동적 할당한 메모리 공간이, 해당 클래스의 객체가 들어올 곳이라는 걸 알아야 생성자를 호출하지 않을까?

-> 클래스가 들어올 공간이라는 확신이 필요함. C에서는 어느 용도(자료형)로 쓰일지 포인터로 가리켜서 알려줬지만, C++의 클래스에서는 컴파일러가 자동으로 호출할 수 있도록 어떤 자료형으로 사용할지 명시를 해줘야 함.
👉 그래서 malloc은 할당할 크기를 받아갔지만, new는 자료형을 받아감

  • delete도 new와 마찬가지로, 해제할 공간이 어떤 용도(자료형)로 사용되고 있었는지 알려줘야 함

예시

class CTest
{
private:
	int a;
    
public:
	CTest()
    	: a(10)
    {
    }
};
CTest* pTest = new CTest;
delete pTest;
  • 동적할당 과정 )
  1. 클래스 멤버 변수의 크기를 계산허여 메모리 할당
int a => 4btye
  1. 자료형 CTest의 객체 pTest 가 들어올 예정이므로 생성자 호출하여 멤버 초기화
  2. 2까지 마친 주소를 pTest에게 반환

1) 데이터 추가 함수

  • 구조체로 구현할 때는 배열의 주소와 데이터를 넘겨줬었지만,
    클래스는 애초에 멤버 함수가 객체를 통해 호출되기 때문에 어떤 객체인지 굳이 주소를 다시 알려줄 필요가 없다.

선언

public:
	void push_back(int _Data);
	void resize(int _iResizeCount);

정의

void CArr::push_back(int _Data)
{
	//힙 영역에 할당한 공간이 다 찬 경우
	if (m_iMaxCount <= m_iCount)
	{
		//재할당
		resize(m_iMaxCount * 2);
	}
	//데이터 추가한 뒤, 후위 연산자로 한계치 1 증가
	m_pInt[m_iCount++] = _Data;
}

-> resize(m_iMaxCount * 2);
: 데이터 추가할 때 공간이 부족하면 최대치의 2배로 늘리지만, 재할당 함수를 직접 호출할 때는 확장할 크기를 받아와서 그만큼 재할당한다.

2) 메모리 재할당 함수

  • 구조체와는 다르게 public인 이유?
    -> 구조체 때처럼 메모리 꽉 찼을 때만 재할당하는 게 아니라, 사용자가 미리 공간을 넓혀놓을 수 있도록 하기 위해
  • 이제는 할당할 공간의 카운트(_iDataCount)를 받아온다.
    만약 배열이 10개 저장할 수 있는데 5개로 재할당하는 건 말이 안 된다.
    원래 크기보다 작아지면 기존 데이터 손실될 수 있는건데?
    -> 예외처리 해주기.

선언

public:
	void push_back(int _Data);
    
	void resize(int _iResizeCount);

정의

void CArr::resize(int _iResizeCount)
{
	// 현재 최대 수용량 보다 더 적은 수치로 확장하려는 경우
	if (m_iMaxCount >= _iResizeCount)
	{
		assert(nullptr);
	}

	// 1. 리사이즈 시킬 개수만큼 동적할당하기
	int* pNew = new int[_iResizeCount];

	// 2. 기존 공간 -> 새로운 공간 으로 데이터 복사
	for (int i = 0; i < m_iCount; ++i)
	{
		pNew[i] = m_pInt[i];
	}
	// 3. 기존 공간은 메모리 해제
	delete[] m_pInt;

	// 4. 배열이 새로 할당된 공간을 가리키게 한다.
	m_pInt = pNew;

	// 5. MaxCount 변경점 적용
	m_iMaxCount = _iResizeCount;
}

클래스로 만든 가변배열 사용 예시

  • 구조체와 클래스 비교
//구조체
tArr arr = {};
InitArr(&arr);

PushBack(&arr, 10);
PushBack(&arr, 20);
PushBack(&arr, 30);

ReleaseArr(&arr);

//클래스

CArr carr;
carr.push_back(10);
carr.push_back(20);
carr.push_back(30);

-> 클래스는 객체 생성될 때 생성자가 자동 호출되면서 함께 초기화됨,
그리고 carr는 지역변수이므로 main함수 종료될 때 소멸자도 호출되니까 직접 할당해제 안 해줘도 됨.

+ 연산자 오버로딩

: 구조체로 구현할 때 미흡했던 부분 보완하기

  • 실제 기본 문법에서 제공하는 배열처럼 사용하고 싶다!

CArr carr;
carr.push_back(10);
carr.push_back(20);
carr.push_back(30);

carr[1]; //2번째로 추가했던 데이터(인덱스 1)의 값을 가리키고 싶다

-> 💡 연산자 오버로딩 하기


선언

int operator[] (int idx);

정의

int CArr::operator[](int idx)
{
	return m_pInt[idx];
}

  • carr[1] = 40; 처럼 대입까지 할 순 없을까?
    -> 연산자 함수 반환 타입이 int라서 안 됨. 함수의 반환 값은 임시적으로 저장되는 공간에 넣어뒀다가 꺼내온다. (원본이 아님)
    지금 arr[1]의 복사본 값을 받아와놓고 거기다 대입을 하려는거

  • 그렇다면 원본에 접근해야 한다.
    -> 클래스 CArr주소로 접근해야 함

선언(수정)

int* operator[] (int idx);

정의(수정)

int* CArr::operator[](int idx)
{
	return m_pInt + idx;
}

활용

int* p = carr[1];
*carr[1] = 100;
  • ❗ 근데 이건 원래 의도랑 다르다. 배열처럼 인덱스에 접근하고 값을 수정하게 하려고 한 건데.... 포인터로 주소를 받아오는 방식이 아니라. 인덱스 값을 그대로 받아오고 싶고.
    인덱스의 값을 바로 수정하고 싶다.

-> 💡 해결 : 레퍼런스
👉 참조 변수를 반환해야 한다.
선언

int& operator[] (int idx);

정의

int& CArr::operator[](int idx)
{
	return m_pInt[idx];
}

활용

int iData = carr[1];
carr[1] = 100;

-> 컴파일러 동작은 포인터와 똑같지만, 사용자 입장에서는 다이렉트 수정이 가능한 것으로 보임
-> 반환시키고자 하는 변수의 참조를 전달함으로써 , 받아오는 쪽(연산자함수)에서는 반환타입이 곧 원본이 됨


[참고]
https://youtu.be/v4Nn_EeCVKA

0개의 댓글