배열 포인터
배열 포인터
형식
타입 (* 변수명)[크기] 로 지정
- 어떤 의미로는 함수 포인터와 유사하다
반환타입 (*변수명)(인자목록)
의미
타입 * 크기 만큼의 Size를 갖는
‘배열’을 가리키는 포인터
- 이전에 말하였듯
포인터는 무언가를 가리키는 주소값이며
그 가리키는 위치를 얼마만큼 읽어야 하는지에 대한
타입(크기 값 추정)이 있어야 한다
포인터 크기 자체는 void* 처럼
다른 포인터 크기와 같다
(32 : 4Byte, 64 : 8Byte)
예시
int arr[3] = { 100, 200, 300 };
int (*ptr)[3] = &arr; // 배열 포인터 선언
// 배열 포인터를 이용하여 배열 요소 접근
cout << "(*ptr)[0]: " << (*ptr)[0] << endl; // 100
cout << "(*ptr)[1]: " << (*ptr)[1] << endl; // 200
cout << "(*ptr)[2]: " << (*ptr)[2] << endl; // 300
포인터 연산 시
포인터는 +1이나 ++을 하면
자신이 가진 Type의 크기만큼 이동한다
따라서 배열 포인터는
선언한 타입 x 크기 만큼 이동
배열 포인터의 특징
-
다차원 배열의 특정 행을 포인터로 표현이 가능하기에
타입 크기를 정확히 알 수 있음
(ex : void func(int (p)[4]) → p는 한 행이 int 4개로 구성된 배열 포인터)
(void func(int arr[3])은 실제로는 int로 변환되기에 실제로는 위험하다) -
배열 크기를 정확히 구할 수 있기에
(타입 x 길이)
다른 배열과 잘 호환되지 않음
(int (*p)[3] = &arr4; → arr4가 int[4]이면 컴파일 에러)
다만… 주의할 점이 있다
말그대로 ‘포인터’ 이기에
실제 함수로 전달되었을 땐
이게 1차원 배열이 전달되었는지 2차원 배열이 전달되었는지
판단하기 힘들다!
int arr[3] = { 100, 200, 300 };
int (*ptr)[3] = &arr; // 배열 포인터 선언
func(ptr);
void func(int (*p)[3])
{
int x = p[0][0]; // 100에 접근
}
위 예시에서 보면 알겠지만 이 뉘앙스는 사실
int b = 10;
int* a = &b;
func(a);
void func(int* arr)
{
int x = arr[0]; // 10에 접근
}
과 매우 유사하다
그렇기에 실제로는
int arr[2][3] = {1,2,3,4,5,6};
int (*ptr)[3] = arr; // 첫번째 행을 가리킴
or
int (*ptr)[2][3] = &arr; // [2][3]을 가리키지만 [0][0][0] 등으로 가리키거나 (*ptr)[0] 등으로 써야 한다
이것처럼 그냥 그 ‘크기’의 배열을 가리킬 수 있는
‘포인터’처럼 인식해야 한다
예시처럼 (*ptr)[0] 등으로 사용하면
가독성이 괜찮기는 한데…
실제 사용처?
사실 이 포인터는 그닥… 사용되진 않는다
위에서 보았듯이 결국 ‘기존 포인터’와 비슷하게 동작하기도 하며
특유의 ‘복잡한 표현’ (함수 포인터와 비슷한) 때문에
딱히 사용할만한 메리트가 없다는 것…
매개변수로 전달 시
보통 이러한 방식이 더 가독성이 좋다
void(int* arr, size_t size) -> 아 arr이 배열이고 size가 그 사이즈를 넣으면 되겠구나
void(int arr[3]) -> 아 arr은 실제로 int* 이지만 그래도 배열 크기를 3이여야 겠구나
또한 C++에서는 STL 과 같은 컨테이너를
더 자주 사용하기에 그냥 stl 컨테이너를
const reference 타입으로 넘겨 받거나
필요에 따라 move 등을 쓰는 편이 더 효율적이고 안전할 것 같다
- 동적 배열이라면 vector가 존재하며
정적 배열이라면 array<T,N>을 사용하면
더 가독성 좋고 안전하게 사용할 수 있다
댓글남기기