이중배열, N중배열
*(*(arr+i)+j)의 의미
이중배열 int arr[3][9]{}이 있을 때
arr은 3*9*4bytes 크기의 배열이자 const lvalue이다. 배열의 이름은 첫 번째 원소의 주소다. 이 주소를 타고 들어간 sizeof(*arr) 또는 sizeof(arr[0])은 36이다. 즉 arr[3]은 원소가 arr[0], arr[1], arr[2]인데 이들 각각이 하나의 [9]*4 배열이며 첫 번째 원소를 가르키는 const lvalue이다. arr[0]의 값은 &arr[0][0], arr[1]의 값은 &arr[1][0], arr[2]의 값은 &arr[2][0]이다. arr + 1은 &arr[0]에서 36바이트 이동한 &arr[1]과 같다. (arr은 arr[0]의 주소인 &arr[0]을 담고있는 포인터기 때문에 arr+1은 arr[0]의 크기인 36씩 이동한 arr[1]의 주소 &arr[1]을 반환한다.
arr + i == &arr[i]
그렇다면 arr을 *를 통해 추적한 *arr에서의 포인터 연산, *(arr + 1)과 *arr + 1는 뭐가 다를까?
전자 *(arr + 1)는 위에서 말한 것처럼 &arr[1]에 가해진 *연산이므로, 즉 *&arr[1]이므로 arr[1]과 같다. arr[1]의 값은 &arr[1][0]이다. (만약 삼중 배열이였다면 다음 배열의 주소다.) 이 때 **(arr+1), 즉 *arr[1]을 하면 *&arr[1][0]이고, 이는 곧 arr[1][0]이라 원소의 값을 얻을 수 있다.
*(arr + i) == arr[i]
2018-02-21
이동하는 메모리 양은 arr이 담고있는 주소를 참조한 결과의 크기 * i ,즉 sizeof(*arr) * i
후자 *arr + 1는 먼저 *연산을 한 상태인 arr[0] + 1이므로, arr[0]이 &arr[0][0]을 가르키는 포인터기에 &arr[0][1]과 같은 의미다.
*arr + i == arr[0] + i == &arr[0][1]
전자와 후자를 종합해보면 *(arr + 1) + 1 이 &arr[1][1]이 된다는 걸 알 수 있다.
당연하게도 &arr[1][1]의 값을 얻으려면 *&arr[1][1]을 하면 된다. 즉 arr[1][1] == *(arr[1] + 1) == *(*(arr+1) +1)이 되는 것이다.
arr[i][j] == *(arr[i] + j) == *(*(arr+i) +j) == (*(arr+i))[j]
이중배열을 함수의 인자로 넘길 때
void foo(int n[][7]){}와 같이 이중배열을 인자로 넘길 때 한 열의 길이를 알려야 하는 이유는 선언할 때와 마찬가지다.
int n[][]만으로는 n[2][3]이 몇 번째 요소인지 절대로 알 수 없기 때문이다.
반대로 행의 길이는 int n[200][7]같이 맘대로 적어넣어도 컴파일러는 상관을 안한다.(이는 c의 전신인 B의 유산이라고 한다) 이미 이전의 함수에서 스택에 등록된, 컴파일러가 알 필요 없는 정보이기 때문이다. (관리는 프로그래머의 몫)
2018-02-20
다만 템플릿으로 컴파일시간에 완전한 이중배열을 넘길 수 있는 방법이 있다.
https://stackoverflow.com/questions/8767166/passing-a-2d-array-to-a-c-function/17569578#17569578
N중배열
arr[i][j][k]의 첫 번째 요소 값인 arr[0][0][0] = ***arr 이다. 이중배열을 탐구했을 때와 같은 패턴이다.
arr 최종 원소의 타입 크기를 T라고 할 때
arr + n은 arr에서 T * n * j * k 만큼
*(arr + n) + m은 arr에서 T( n * j * k + m * k) 만큼
*(*(*(arr + n) + m) + l) 은 arr에서 T(n * j * k + m * k + l) 만큼 이동할 것이다.
sizeof(arr)은 배열 전체 크기를, sizeof(*arr)은 j * k * 타입크기, sizeof(**arr)은 k * 타입크기를 나타낼 것이다.
위에서 알 수 있는게 i정보가 필요없다! 마치 함수에 N중 배열을 건낼 때 처럼..
배열의 이름을 가지고 &arr + 1을 할 때는 (컴파일러가 길이를 알고있을 때만) 배열의 크기만큼의 뒤를 가르킨다는 걸 잊지말자.
2018-02-21
넓이가 4인 배열의 주소를 담는 포인터는 int(*arr)[4]; 다. 이렇게 선언된 arr은 크기가 4인 배열의 주소를 담는 포인터다. 즉 arr은 1차 배열의 주소를 담은 포인터, 2차원 배열과 성질이 같다. (2차원 배열의 이름은 첫 원소(1차배열)의 주소이므로.)
int fooarr3[3]{};
라고할 때
int(*arr)[3] = &fooarr3; //그대로 대입가능
arr[0][0] = 4; //fooarr3[0]의 값 변경, 단 조심해서 다뤄야 함!
*(*arr + 1) = 4; //fooarr3[1]의 값 변경
마찬가지로
int fooarr2x3[2][3]{};
라고할 때
int(*arr)[3] = fooarr2x3; //그대로 대입가능,
arr[0][0] = 4; //fooarr[0]의 값 변경
처럼 사용할 수 있다. fooarr2x3은 곧 가로가3인 배열의 주소기 때문이다!
다차원 배열의 typedef
( int(*arrNx4)[4]는 가로가 4인 2차배열 즉, 가로가 4인 배열의 주소지 가로가 4인 배열이 아니다!)
typedef int(*arrNx4)[4];
arrNx4 someFunc(arrNx4 arr)
{
cout << **(arr) << endl; // = 1
arrNx4 a4 = arr;
cout << **(a4 + 1) << endl; // = 5
return a4;
}
...
void main()
{
int arr[2][4] = {1,2,3,4,5,6,7,8};
arrNx4 arr2 = someFunc(arr);
}
이처럼 typedef를 하면 다차원 배열을 함수의 리턴값으로 사용할 수 있다...
'프로그래밍 > C, C++ 공부' 카테고리의 다른 글
scanf와 cin 의 비교 (0) | 2018.02.17 |
---|---|
문자열로 입력된 연산문의 분석 (0) | 2018.02.17 |
배열의 이름 (0) | 2018.02.16 |
C/C++의 컴파일러는 왜 배열의 크기를 알아야 할까? (0) | 2018.02.16 |
정수의 자릿수 구하기, 한 자리씩 판별하기 (0) | 2018.02.16 |