이중 포인터
포인터 변수는 주소(포인터)를 담는 변수입니다.
포인터 변수는 주소를 단순히 담고있는 역할만 하는 것이 아닌, 담고있는 주소값을 찾아갔을 때 존재하는 데이터를 조회하기 위해 데이터의 크기도 알고 있어야 합니다. 그리고 이를 타입으로 표현합니다.
예를 들어 int * a; 의 경우, a는 주소값을 담는 포인터 변수이며, 그 주소에 가면 int가 있다는 뜻입니다.
이중 포인터에서도 이는 동일합니다.
** doublePtr; 의 경우, doublePtr는 주소값을 담는 포인터 변수이며, 그 주소에 가면 주소(포인터)가 있다는 뜻입니다.
이중 포인터는 자신이 담고 있는 주소를 찾아가면, 다시 주소가 나오는 변수입니다.
포인터의 배열
포인터의 배열은 배열의 각 원소가 포인터인 배열을 의미합니다.
즉 다음과 같습니다.
char * strArr[3] = {"구독", "광고", "눌러주세요"};
위의 예시에서 char * strArr[] 타입은 배열의 각 요소가 char의 주소임을 의미합니다.
이는 조금 다르게 쓸 수 있습니다.
char arr1[] 로 선언된 char 배열은
char * arr 로 선언된 char의 주소와 동일한 타입입니다.
char* arr2[] 로 선언된 char* 배열은
char* * arr2 로 선언된 char* 의 주소와 동일한 타입입니다.
즉 다음과 같습니다.
char * arr[] == char ** arr
배열의 포인터
배열의 포인터는 포인터 변수이나, 그 주소를 찾아가면 배열이 나오는 포인터를 의미합니다.
다음과 같이 사용합니다.
char (* p) [10];
위 코드에서 포인터 변수 p가 가진 주소를 찾아가면 char [10] 배열이 나옵니다.
배열의 포인터도 포인터다
배열의 포인터도 포인터입니다.
배열이 무엇을 담고 있는지에 상관없이 배열의 포인터는 단지 해당 주소를 가리키고 있는 것일 뿐입니다.
너무 특별한 취급을 하실 필요가 없는데, 예를 들면 다음 코드의 결과가 무엇일까요?
printf("%lu\n", sizeof(int(*)[2][2]));
printf("%lu\n", sizeof(int(**)[2][2]));
printf("%lu\n", sizeof(int*(*)[2][2]));
printf("%lu\n", sizeof(int*(**)[2][2]));
printf("%lu\n", sizeof(int** (*)[2][2]));
printf("%lu\n", sizeof(int** (**)[2][2]));
위의 모두가 다 포인터이기 때문에 전부 8이 출력됩니다.
다음과 같이 여러 포인터가 복잡하게 사용되어도 결국은 포인터이기에 사용하던 대로 사용하실 수 있습니다.
int main() {
int a,b,c,d;
a = 1;
b = 2;
c = 3;
d = 4;
int * pa, * pb, * pc, * pd;
pa = &a;
pb = &b;
pc = &c;
pd = &d;
int ** ppa, ** ppb, ** ppc, ** ppd;
ppa = &pa;
ppb = &pb;
ppc = &pc;
ppd = &pd;
int ** pptrBox[2][2] = {{ppa, ppb}, {ppc, ppd}};
int ** (*ptrArr) [2][2] = &pptrBox;
int ** (**doublePtrArr) [2][2] = &ptrArr;
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 2; j++)
{
printf("%d\n", **((**doublePtrArr)[i][j]) );
//printf("%d\n", **(**doublePtrArr)[i][j] );
}
}
}
포인터의 배열과의 차이점
char * p : 포인터 변수인데, 그 값을 찾아가면 char이 나옵니다.
char (*p)[10] : 포인터 변수인데, 그 값을 찾아가면 char[10]이 나옵니다
char * p[10] : 배열 변수인데, 각 요소 타입이 char * 인 길이가 10인 배열입니다.
배열과 포인터
배열과 포인터는 비슷하지만
배열에 &를 취하면 배열의 포인터가 나오고,
포인터에 &를 취하면 포인터의 포인터가 나옵니다.
배열의 포인터에 &를 취하면 배열의 이중포인터가 나옵니다.
즉 int * ptr 에서
&ptr -> int **
(포인터에 주소연산자)
int ** ptr
&ptr -> int ***
(포인터에 주소연산자)
int arr[10]
&arr -> int(*)[10]
(배열에 주소연산자)
int arr[20][10]
&arr -> int(*)[20][10]
(배열에 주소연산자)
int (*aPtr)[10]
&aPtr -> int (**)[10]
(포인터에 주소연산자)
int *(*aPtr)[10]
&aPtr -> int *(**)[10]
배열이 단 한개이며 배열의 포인터가 아닌 경우, 이는1차원 포인터와 치환 가능합니다.
배열이 한개라는 의미는 int ****arr[A] 의 의미이며
배열의 포인터가 아니라는 의미는 int (*arr)[A]의 형태가 아니라는 뜻입니다.
int **** arr의 경우int *** arr[A]로 치환 가능합니다.
int ***(*arr)[A] 혹은 int **(*arr)[A]은
int **** arr로 치환 불가능합니다. (원인 : 배열의 포인터)
int **arr[A][B]도
int ***arr[B]로 치환 불가능합니다. (원인 : 1차원 포인터가 아님)
2차원 배열 이상인 경우 배열의 포인터로 단 하나 치환될 수 있습니다.
(단 하나의 의미는 배열의 포인터는 배열의 이중포인터로 변환할 수 없다는 의미입니다.)
단 이때 자료형은 동일해야 합니다.
즉 다음은 변환이 불가능합니다.
int ** (*arr)[A][B][C] -> int ** (**arr)[B][C]
int ** (**arr)[A][B][C] -> int ** (***arr)[B][C]
int (*arr)[A][B][C] -> int (**arr)[B][C]
int ** arr[A][B][C][D]에서 [A]부분만이 int ** (*arr)[B][C][D]으로 대체될 수 있습니다.
또한 이때 [A]는 비어있을 수 있으며, [B][C][D]는 비어있을 수 없고 똑같은 수여야 합니다
int ** arr[A][B][C][D]는
int ** (*arr)[B][C][D] 로는 치환될 수 있으나
다음으로는 치환될 수 없습니다.
int ** (**arr)[C][D]
int * (*arr)[A][B][C][D]
int * arr[E][A][B][C][D]
char * arr = char arr[N]
포인터 변수는 주소 == 배열 변수도 주소
char ** arr == char *arr [N]
이중 포인터는 주소의 주소, == 주소의 배열(주소)
char (* arr)[N] = = char arr[][N]
char[N]에 대한 주소 == char[N]의 배열(주소)
char (* arr)[N][M] = = char arr[][N][M]
char [N][M]에 대한 주소 == char[N][M]에 대한 배열(주소)
char * (*arr) [N] = char * arr [][N]
char * [N]에 대한 주소 == char *의 배열(주소)
포인터 변수인데 char*의 배열을 가짐
배열 변수인데 타입이 char*
'c언어' 카테고리의 다른 글
[C언어] - 구조체 (Struct) (0) | 2022.06.12 |
---|---|
[C언어] - 동적 메모리 할당 (malloc) (0) | 2022.06.12 |
[C언어] - 다차원 배열 (0) | 2022.05.06 |
[C언어] - Const (0) | 2022.04.20 |
[C언어] - Call By Value (0) | 2022.04.20 |