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

2024. 3. 18. 13:48Programming Language/C++

CArr.h

#pragma once
class CArr  // (C는 class라는 의미로 사용했다.)
{
private:
	int* m_pInt;  // 주소값 (m = 멤버라는 의미로 사용했다.)
	int m_iCount;  // 현재 들어와있는 데이터 개수
	int m_iMaxCount;  // 최대 개수

public:
	void push_back(int _iData);  // 가변 배열로 따지면 데이터 추가 함수이다.

public:
	CArr();  // 가변 배열로 따지면 => 배열 초기화 함수
	~CArr();  // 가변 배열로 따지면 => 배열 메모리 해제 함수
};

 

CArr.cpp

#include "CArr.h"

// 생성자
CArr::CArr()
	: m_pInt(nullptr)
	, m_iCount(0)
	, m_iMaxCount(2)
{
	m_pInt = new int[2];  // int 자료형 2개만큼 할당한다.
}

// 소멸자
CArr::~CArr()
{
	delete[] m_pInt;
}

 

Arr.h

#pragma once

// 가변 배열 자료형 tArr (int)
typedef struct _tagArr
{
	int* pInt;
	int iCount;
	int iMaxCount;
}tArr;

// 배열 초기화(초기값을 줄뿐 문법적으로 진짜 초기화는 아니다.) 함수
void InitArr(tArr* _pArr);

// 데이터 추가 함수
void PushBack(tArr* _pArr, int _iData);

// 공간 추가 확장 함수
void Reallocate(tArr* _pArr);

// 배열 메모리 해제 함수
void ReleaseArr(tArr* _pArr);

 

Arr.cpp

#include <iostream>
#include "Arr.h"

// 배열 초기화
void InitArr(tArr* _pArr)
{
	_pArr->pInt = (int*)malloc(sizeof(int) * 2);
	_pArr->iCount = 0;
	_pArr->iMaxCount = 2;
}

// 공간 추가 확장
void Reallocate(tArr* _pArr)
{
	// 1. 2배 더 큰 공간을 동적할당한다.
	int* pNew = (int*)malloc(_pArr->iMaxCount * sizeof(int) * 2);

	// 2. 기존 공간에 있던 데이터들을 새로 할당한 공간으로 복사시킨다.
	for (int i = 0; i < _pArr->iCount; ++i)
	{
		pNew[i] = _pArr->pInt[i];
	}

	// 3. 기존 공간은 메모리 해제
	free(_pArr->pInt);

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

	// 5. iMaxCount 변경점 적용
	_pArr->iMaxCount *= 2;
}

// 뒤에 데이터 추가
void PushBack(tArr* _pArr, int _iData)
{
	if (_pArr->iMaxCount <= _pArr->iCount)
	{
		// 재할당
		Reallocate(_pArr);
	}

	// 데이터 추가
	_pArr->pInt[_pArr->iCount++] = _iData;
}

// 배열 메모리 해제
void ReleaseArr(tArr* _pArr)
{
	free(_pArr->pInt);
	_pArr->iCount = 0;
	_pArr->iMaxCount = 0;
}

 

main.cpp

#include <iostream>
#include "Arr.h"
#include "CArr.h"

class CTest
{
private:
	int a;

public:
	CTest()
		: a(10)
	{

	}
};

int main()
{
	// 클래스 동적 할당 예시
	CTest* pTest = new CTest;  // a = 10
	delete pTest;  // pTest의 타입이 CTest*라는 것을 확인한다.

	// 1. 가변 배열 예시
	tArr arr = {};  // 클래스 배열 초기화
	InitArr(&arr);  // 초기값 호출
	
	// 값 넣기
	PushBack(&arr, 10);
	PushBack(&arr, 20);
	PushBack(&arr, 30);

	// 메모리 해제
	ReleaseArr(&arr);

	// 2. CArr 예시
	CArr carr;  // 가변 배열에서 InitArr(&arr);까지 포함되어있다.
	carr.push_back(10);
	carr.push_back(20);
	carr.push_back(30);
	// carr은 지역변수이기 때문에 소멸은 신경쓰지 않아도 된다.

	// CArr 예시의 경우 C에서 제공해주는 기본 문법 배열은 아니다.
	// 내가 만든 배열도 carr[1];로 2번째로 넣었던 20을 지칭하고 값을 변경하는 것이 가능했으면 좋겠다.
	//carr[1] = 40;  // 오류 => 애초에 배열(주소)가 아니라 객체이다.
	// operator 함수를 이용해서 만들면 된다.
	int iData = carr[1];
	//carr[1] = 200;  // 오류 => 반환 타입이 int이다.
	// 함수의 반환 타입은 호출된 함수와 함수끼리 이 함수가 종료될 때 반환한 값을 임시적으로 저장되어있는 곳에서부터 값을 넣어놓고 꺼내온다.

	// operator[] 함수가 종료되고나서 돌아올 때 CArr 객체의 m_pInt가 가르키고 있는 곳에 index 1 즉, 두 번째 데이터를 갖고와서 200을 넣어주려고 한다.
	// 하지만 현재 상황은 갖고 있던 값을 임시 공간에 넣어놓고 그 복사본을 받아온 것이다.
	// 임시 공간에 200을 넣어봤자 수정할 수도 없고 진짜 내가 원하는 곳을 수정하는 것이 아니다.
	// operator[] 함수는 반환 타입이 int이기 때문이다.
	// m_pInt[idx]에 들어있던 값을 전달시키고 main() 쪽에서 꺼내온 것뿐이다.
	// 원본 그 자체가 아니다.
	// 원본 그 자체에 접근할 수 있는 방법 중 해당 변수의 주소를 알려줘야 하는 방법이 있다.
	// int에서 int* 타입으로 바꿔보자.
	// 그럼 주소이기 때문에 역으로 접근하여 주소로 가볼 수 있다.
	// 애초에 []는 주소 연산이기 때문에 원본으로 접근하는 부분을 없애면 그것이 바로 주소이다.
	// 시작으로부터 몇 칸 떨어진 곳의 주소값 그 자체를 바로 반환한다.
		// *(m_pInt + idx); → m_pInt + idx;
	int* p = carr[1];
	*carr[1] = 100;
	// 원래 배열 문법이랑 전혀 관련이 없다.
	// 진짜 배열은 아니지만 마치 배열과 같은 기호로 인덱스로 접근해서 값을 수정해볼 수 있다.

	// 나는 인덱스 1로 꺼내온 것이 값이고 다이렉트로 수정하고 싶은데, 이 과정들이 가능하려면 c++에서 제공하는 래퍼런스를 사용해야 한다.
	// 즉, 반환 타입이 int*가 아닌 int&여야 한다.
	// 반환하는 곳을 그대로 참조를 전달하면 반환되는 것이 곧 idx와 동일시되는 것이다.
	// 그럼 operator[] 함수에서는 공간 그 자체를 주면 된다.
	// 호출시킨 operator[] 함수에서 반환하겠다고 한 반환 타입이 곧 idx와 동일시됐기 때문에 idx를 수정하는 것이 곧 반환하려는 것을 수정하는 것과 같다.
	// 개념 자체는 주소를 통해서 접근해서 수정하는 것이지만 이는 컴파일러가 알아서 해준다.
	// 덕분에 원하는 형태가 나올 수 있던 것이다.
	int iData = carr[1];
	carr[1] = 100;
	// carr은 배열이 아닌데 배열처럼 사용할 수 있고, 접근해서 값도 확인하고 수정할 수 있게 됐다.

	return 0;
}