KoreanFoodie's Study
C++ 기초 개념 5-3 : C++ 캐스팅, 디폴트 인자, 반복자(iterator) 본문
C++ 기초 개념 5-3 : C++ 캐스팅, 디폴트 인자, 반복자(iterator)
GoldGiver 2021. 12. 22. 17:19
모두의 코드를 참고하여 핵심 내용을 간추리고 있습니다. 자세한 내용은 모두의 코드의 씹어먹는 C++ 강좌를 참고해 주세요!
C++ 스타일의 캐스팅
기존의 C 언어에서는 컴파일러가 알아서 캐스팅하는 암시적(implicit) 캐스팅과, 프로그래머가 직접 캐스팅하라고 지정하는 명시적(explicit) 캐스팅이 있었다. 하지만 C++에서는 다음과 같은 4가지의 캐스팅을 제공하고 있다.
1. static_cast : 우리가 흔히 생각하는, 언어적 차원에서 지원하는 일반적인 타입 변환
2. const_cast : 객체의 상수성(const)를 없애는 타입 변환. 쉽게 말해 const int가 int로 바뀐다.
3. dynamic_cast : 파생 클래스 사이에서의 다운캐스팅
4. reinterpret_cast : 위험을 감수하고 하는 캐스팅으로, 서로 관련이 없는 포인터들 사이의 캐스팅 등
N 차원 배열 만들기
우리가 앞서 이야기했던 포인터들과 casting에 대한 지식을 바탕으로, N 차원 배열을 직접 구현해 보자. 먼저, Address라는 구조체를 이용한 Array 클래스를 생성한다.
class Array {
const int dim; // 몇 차원 배열인지
int* size; // size[0] * size[1] * ... * size[dim - 1] 짜리 배열이다.
struct Address {
int level;
// 맨 마지막 레벨(dim-1 레벨)은 데이터 배열
// 그 위 상위 레벨에서는 다음 Address 배열
void *next;
};
Address *top;
public:
Array(int dim, int* array_size) : dim(dim) {
size = new int[dim];
for (int i = 0; i < dim; ++i) {
size[i] = array_size[i];
}
}
};
Array 클래스를 만들었으면, N차원 배열을 실제로 초기화하고 삭제해주는 함수를 각각 만들어야 한다.
void Array::initialize_address(Address *current) {
if (!current) return;
if (current->level == dim-1) {
for (int i = 0; i < size[dim-1]; ++i) {
current->next = new int[size[current->level]];
return;
}
}
current->next = new Address[size[current->level]];
for (int i = 0; i < size[current->level]; ++i) {
(static_cast<Address *>(current->next) + i)->level = current->level + 1;
initialize_address(static_cast<Address *>(current->next) + i);
}
}
void Array::delete_address(Address *current) {
if (!current) return;
for (int i = 0; current->level < dim - 1 && i < size[current->level]; ++i) {
delete_address(static_cast<Address *>(current->next) + i);
}
if (current->level == dim-1)
delete[] static_cast<int *>(current->next);
delete[] static_cast<Address *>(current->next);
}
위의 initialize_address와 delete_address를 조합하여, 생성자와 소멸자를 재정의하면 다음과 같다.
Array(int dim, int* array_size) : dim(dim) {
size = new int[dim];
for (int i = 0; i < dim; ++i) {
size[i] = array_size[i];
}
top = new Address;
top->level = 0;
initialize_address(top);
}
Array(const Array &arr) : dim(arr.dim) {
size = new int[dim];
for (int i = 0; i < dim; ++i) size[i] = arr.size[i];
top = new Address;
top->level = 0;
initialize_address(top);
// 내용물 복사
copy_address(top, arr.top);
}
void copy_address(Address *dst, Address *src) {
if (dst->level == dim-1) {
for (int i = 0; i < size[dst->level]; ++i) {
static_cast<int *>(dst->next)[i] = static_cast<int *>(src->next)[i];
return;
}
}
for (int i = 0; i < size[dst->level]; ++i) {
Address *new_dst = static_cast<Address *>(dst->next) + i;
Address *new_src = static_cast<Address *>(src->next) + i;
copy_address(new_dst, new_src);
}
}
~Array() {
delete_address(top);
delete[] size;
}
operator[] 문제
이제 [ ]를 이용해서 우리가 만든 N차원 배열에 접근해 보자. 만약 arr[1][2] 와 같은 방식으로 N차원 배열에 접근한다고 가정했을 때, [1]은 우리가 만든 Array 클래스를, [2]는 실제 데이터를 리턴해야 한다.
이를 위해서, 우리는 [ ]를 위한 Int wrapper 클래스를 정의해 볼 수 있다.
class Int {
void* data;
int level;
Array *array;
public:
Int(int index, int _level = 0, void *_data = NULL, Array *_array = NULL)
: level(_level), data(_data), array(_array) {
if (_level < 1 || index >= array->size[_level - 1]) {
data = NULL;
return;
}
if (level == array->dim) {
// 이제 data에 우리의 int 자료형을 저장하도록 해야 한다.
data = static_cast<void *>(
(static_cast<int *>(static_cast<Array::Address *>(data)->next) + index));
} else {
// 그렇지 않을 경우 data에 그냥 다음 addr을 넣어준다.
data = static_cast<void *>(
static_cast<Array::Address *>(static_cast<Array::Address *>(data)->next) +
index);
}
}
Int(const Int& i) : data(i.data), level(i.level), array(i.array) {}
operator int() {
if (data) return *static_cast<int *>(data);
return 0;
}
Int& operator=(const int &a) {
if (data) *static_cast<int *>(data) = a;
return *this;
}
Int operator[](const int index) {
if (!data) return 0;
return Int(index, level + 1, data, array);
}
};
Int Array::operator[](const int index) {
return Int(index, 1, static_cast<void *>(top), this);
}
또한, Array 클래스에서는 Int 클래스의 생성자를 호출해서 [ ] 연산자를 처리하도록 만들고 있다.
전체 코드는 다음과 같다.
// 대망의 Array 배열
#include <iostream>
namespace MyArray {
class Array;
class Int;
class Array {
friend Int;
const int dim; // 몇 차원 배열 인지
int* size; // size[0] * size[1] * ... * size[dim - 1] 짜리 배열이다.
struct Address {
int level;
// 맨 마지막 레벨(dim - 1 레벨) 은 데이터 배열을 가리키고, 그 위 상위
// 레벨에서는 다음 Address 배열을 가리킨다.
void* next;
};
Address* top;
public:
Array(int dim, int* array_size) : dim(dim) {
size = new int[dim];
for (int i = 0; i < dim; i++) size[i] = array_size[i];
top = new Address;
top->level = 0;
initialize_address(top);
}
Array(const Array& arr) : dim(arr.dim) {
size = new int[dim];
for (int i = 0; i < dim; i++) size[i] = arr.size[i];
top = new Address;
top->level = 0;
initialize_address(top);
// 내용물 복사
copy_address(top, arr.top);
}
void copy_address(Address* dst, Address* src) {
if (dst->level == dim - 1) {
for (int i = 0; i < size[dst->level]; ++i)
static_cast<int*>(dst->next)[i] = static_cast<int*>(src->next)[i];
return;
}
for (int i = 0; i != size[dst->level]; i++) {
Address* new_dst = static_cast<Address*>(dst->next) + i;
Address* new_src = static_cast<Address*>(src->next) + i;
copy_address(new_dst, new_src);
}
}
// address 를 초기화 하는 함수이다. 재귀 호출로 구성되어 있다.
void initialize_address(Address* current) {
if (!current) return;
if (current->level == dim - 1) {
current->next = new int[size[current->level]];
return;
}
current->next = new Address[size[current->level]];
for (int i = 0; i != size[current->level]; i++) {
(static_cast<Address*>(current->next) + i)->level = current->level + 1;
initialize_address(static_cast<Address*>(current->next) + i);
}
}
void delete_address(Address* current) {
if (!current) return;
for (int i = 0; current->level < dim - 1 && i < size[current->level]; i++) {
delete_address(static_cast<Address*>(current->next) + i);
}
if (current->level == dim - 1) {
delete[] static_cast<int*>(current->next);
}
delete[] static_cast<Address*>(current->next);
}
Int operator[](const int index);
~Array() {
delete_address(top);
delete[] size;
}
};
class Int {
void* data;
int level;
Array* array;
public:
Int(int index, int _level = 0, void* _data = NULL, Array* _array = NULL)
: level(_level), data(_data), array(_array) {
if (_level < 1 || index >= array->size[_level - 1]) {
data = NULL;
return;
}
if (level == array->dim) {
// 이제 data 에 우리의 int 자료형을 저장하도록 해야 한다.
data = static_cast<void*>((
static_cast<int*>(static_cast<Array::Address*>(data)->next) + index));
} else {
// 그렇지 않을 경우 data 에 그냥 다음 addr 을 넣어준다.
data = static_cast<void*>(static_cast<Array::Address*>(
static_cast<Array::Address*>(data)->next) +
index);
}
};
Int(const Int& i) : data(i.data), level(i.level), array(i.array) {}
operator int() {
if (data) return *static_cast<int*>(data);
return 0;
}
Int& operator=(const int& a) {
if (data) *static_cast<int*>(data) = a;
return *this;
}
Int operator[](const int index) {
if (!data) return 0;
return Int(index, level + 1, data, array);
}
};
Int Array::operator[](const int index) {
return Int(index, 1, static_cast<void*>(top), this);
}
} // namespace MyArray
int main() {
int size[] = {2, 3, 4};
MyArray::Array arr(3, size);
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
for (int k = 0; k < 4; k++) {
arr[i][j][k] = (i + 1) * (j + 1) * (k + 1);
}
}
}
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
for (int k = 0; k < 4; k++) {
std::cout << i << " " << j << " " << k << " " << arr[i][j][k]
<< std::endl;
}
}
}
}
또한 순회를 위해, Array 클래스 내부에 Iterator 클래스를 넣어 준다.
class Iterator {
int* location;
Array* arr;
friend Int;
public:
Iterator(Array* arr, int* loc = NULL) : arr(arr) {
location = new int[arr->dim];
for (int i = 0; i != arr->dim; i++)
location[i] = (loc != NULL ? loc[i] : 0);
}
Iterator(const Iterator& itr) : arr(itr.arr) {
location = new int[arr->dim];
for (int i = 0; i != arr->dim; i++) location[i] = itr.location[i];
}
~Iterator() { delete[] location; }
// 다음 원소를 가리키게 된다.
Iterator& operator++() {
if (location[0] >= arr->size[0]) return (*this);
bool carry = false; // 받아 올림이 있는지
int i = arr->dim - 1;
do {
// 어차피 다시 돌아온다는 것은 carry 가 true
// 라는 의미 이므로 ++ 을 해야 한다.
location[i]++;
if (location[i] >= arr->size[i] && i >= 1) {
// i 가 0 일 경우 0 으로 만들지 않는다 (이러면 begin 과 중복됨)
location[i] -= arr->size[i];
carry = true;
i--;
} else
carry = false;
} while (i >= 0 && carry);
return (*this);
}
Iterator& operator=(const Iterator& itr) {
arr = itr.arr;
location = new int[itr.arr->dim];
for (int i = 0; i != arr->dim; i++) location[i] = itr.location[i];
return (*this);
}
Iterator operator++(int) {
Iterator itr(*this);
++(*this);
return itr;
}
bool operator!=(const Iterator& itr) {
if (itr.arr->dim != arr->dim) return true;
for (int i = 0; i != arr->dim; i++) {
if (itr.location[i] != location[i]) return true;
}
return false;
}
Int operator*();
};
Iterator는 다음과 같이 활용한다.
Int Array::Iterator::operator*() {
Int start = arr->operator[](location[0]);
for (int i = 1; i <= arr->dim - 1; i++) {
start = start.operator[](location[i]);
}
return start;
}
int main() {
MyArray::Array::Iterator itr = arr.begin();
for (int i = 0; itr != arr.end(); itr++, i++) (*itr) = i;
for (itr = arr.begin(); itr != arr.end(); itr++)
std::cout << *itr << std::endl;
}
*itr 를 어떻게 이해하면 좋을까? Array::Iterator::operator*() 를 통해, 우리는 Iterator에서 * 연산자를 취하면 Int 타입이 리턴된다는 것을 알 수 있다.
리턴된 Int 타입은, 다시 Int wrapper 클래스 내부의 int( )연산자를 통해 int 타입으로 변환되므로, ostream에서 이를 문제없이 오버로딩할 수 있는 것이다.
'Tutorials > C++ : Beginner' 카테고리의 다른 글
C++ 기초 개념 6-2 : 가상(virtual) 함수와 다형성 (0) | 2022.01.06 |
---|---|
C++ 기초 개념 6-1 : C++ 표준 문자열, 상속, 오버라이딩, protected 키워드 (0) | 2022.01.06 |
C++ 기초 개념 5-2 : 입출력, 첨자, 타입변환, 증감 연산자 오버로딩 (0) | 2021.12.22 |
C++ 기초 개념 5-1 : 연산자 오버로딩 (산술, 비교, 대입 연산자) (0) | 2021.12.22 |
C++ 기초 개념 4-6 : explicit과 mutable 키워드 (0) | 2021.12.22 |