다차원 배열은 2차원 이상의 배열을 의미한다.
배열의 선언 방법
int arr[10]; // 길이가 10인 1차원 int형 배열
int arr2[5][5]; //행의 길이 5, 열의 길이 5인 2차원 int형 2차원 배열
int arr3[3][3][3]; //높이길이 3, 행의 길이 3, 열의 길이 3인 int형 3차원 배열
다차원 배열중 2차원 배열이 주로 쓰인다.
int arr[3][4];
위배열을 선언시 메모리 공간에는 다음과 같이 나타난다.
[0][0] | [0][1] | [0][2] | [0][3] |
[1][0] | [1][1] | [1][2] | [1][3] |
[2][0] | [2][1] | [2][2] | [2][3] |
위 그림은 각각 요소에 접근할수 있도록하는 인덱스 값 을 나타낸 것이다.
즉
TYPE 배열이름[행][열]; 이렇게 2차원 배열을 선언 할 수 있다.
2차원 배열 대상으로 sizeof 연산시 행 x 열 x type의 바이트 로 결과가 나온다.
따라서 sizeof연산으로 정확히 크기가 계산 가능하다.
각 인덱스 값으로 배열의 요소에 접근할 수 있는데
arr[0][0] = 1;
이런식으로 값을 변경하거나, 삽입할 수 있다.
2차원 배열의 각 값을 출력할때 주로 이중 중첩문을 사용하여 접근해 출력한다 .
int arr[3][3];
위와 같이 배열 선언시 주소값을 확인해보면
arr[0][0]~ arr[0][2]
arr[1][0]~
이 순서로 주소값이 할당된다 .
즉 열을 채우고 행으로 넘어간다.
만약 [0][0]의 주소값이 0x1000이면 [0][1]은 0x1004 , [0][2]는 0x1008 이런식으로 증가한다.
2차원 배열은 1차원 배열과 마찬가지로 선언과 동시에 초기화를 할 수 있다.
int arr[3][3] = {
{1, 2, 3}.
{4, 5, 6},
{7, 8, 9}
};
위 그림처럼 행단위로 초기화 할 값을 별도의 중괄호로 명시할수 있다.
모든 배열요소를 초기화를 안하고 생략도 가능한데 생략한 값은 1차원 배열의 경우와 같게 0으로 초기화된다.
물론 배열을 한줄로 초기화도 가능하다.
int arr[3][3] = {1,2,3,4,5,6,7,8,9};
이 배열의 초기화는 위의 배열의 초기화와 완전히 동일하다.
1차원 배열에서는 배열의 길이 정보를 알려주지 않고 초기화를 해도 컴파일러가 알아서 길이 정보를 채워준다 .
하지만 2차원 배열에서는 열의 길이는 입력을 해야된다 .
즉
int arr1[][4]= {...}; //정상동작
int arr2[][]= {...}; //에러 발생
3차원 배열은 2차원 배열과 유사한데
이해하기 쉬운 방법은
3차원 배열은 여러개의 2차원 배열이 모여있다고 생각하면 된다.
즉 행과 열길이로 이뤄진 2차원 배열이 높이길이 = 갯수 만큼 있는 것이다.
int arr[3][2][4];
행의 길이 2, 열의 길이 4인 2차원 배열이 3개 있다. 라고 이해하면 쉽다.
이중포인터는 포인터의 포인터 , 즉 포인터 변수를 가리키는 또 다른 포인터 변수를 뜻하는 것이다.
int **dptr; //int형 이중 포인터
포인터 변수는 종류에 상관없이 주소값을 저장하는 변수이다.
int main(){
double num= 3.14;
double *ptr = # //변수 num의 주소 값 저장
}
num과 ptr의 공통점과 차이점은
공통점 : 둘다 변수이고, 값의 저장이 가능하다
차이점 : 저장하는 값의 종류가 다르다.
ptr도 메모리 공간에 할당되는 변수이다. 따라서 ptr을 대상으로도 &연산이 가능하다.
반환 되는 주소 값은 double형 더블 포인터 변수에 저장이 가능하다.
doube ** dptr= &ptr;
위의 선언으로
dptr은 ptr을 가리키고 ,ptr은 num을 가리킨다.
*dptr= ...; //*dptr은 포인터 변수 ptr을 의미
*(*dptr)= ....;// **dptr은 변수 num을 의미, 괄호는 생략이 가능하다.
#include <stdio.h>
int main(){
int num = 5;
int *ptr = # //ptr은 num을 가리킨다.
int **dptr= &ptr; //dptr은 ptr을 가리킨다.
int *ptr2;
printf("%9p, %9p \n", ptr, *dptr); //*dptr은 ptr을 의미하므로 동일한 출력
printf("%9d, %9d \n", num, **dptr); //**dptr은 num을 의미하므로 동일한 출력
ptr2= *dptr; //ptr2= ptr과 같은 문장
*ptr2= 10; //ptr2은 ptr의 주소를 가지고 있고 ptr은 num의 주소를 가지고있기 때문에 즉 num에 대입이다.
printf("%9d, %9d \n", num, **dptr); //동일한 출력
return 0;
}
위의 예제에서 num에 접근하는 방법은 여러가지 있다.
1. **dptr = 1; //num에 1저장
2. *ptr = 1; //num에 1저장
3. *ptr2= 1; //num에 1저장
4. num = 1; //num에 1저장
위의 방법중 어떠한 방법이라도 동일한 결과이다.
배열이 포인터의 역할도 한다고 하였는데
int *arr1[20]; //길이가 20인 int형 포인터 배열 arr1 또는 arr1은 int형 더블 포인터
double *arr2[20]; //길이가 20인 double형 포인터 배열 arr2, 또는 arr2는 double형 더블 포인터
즉 가리키는 첫번째요소의 자료형을 따지면 된다.
arr1은 1차원 배열이므로 , 즉 자료형은 int * 이된다 .
int* (*arr1)[20];즉 다시 int*형으로 int*형을 가리킨다고 생각하면 된다.
#include <stdio.h>
int main(){
int num1= 10, num2= 20, num3= 30;
int *ptr1 = &num1;
int *ptr2= &num2;
int *ptr3= &num3;
int *ptrArr[] = {ptr1, ptr2, ptr3}; //포인터 배열 ptrarr선언 포인터를 담을 수 있음
int **dptr= ptrarr; //포인터배열을 가리키는 dptr 이중포인터
//위의 대입연산이 가능하다는 것은 둘의 포인터 형이 일치한다는 뜻
printf("%d %d %d \n", *(ptrarr[0]), *(ptrarr[1]), *(ptrarr[2]) );
printf("%d %d %d \n", *(dptr[0]), *(dptr[1]), *(dptr[2]) );
return 0;
포인터 변수도 배열의 이름처럼 사용이 가능하다.
이중포인터가 있듯이 삼중포인터도 존재한다.
int ***tptr ; //tptr은 삼중 포인터 변수
이중포인터가 싱글포인터를 가리키듯이, 삼중포인터는 이중포인터를 가리키는 용도로 , 이중 포인터의 주소 값을 저장하는 용도로 사용이 된다.
int arr[10]; //arr는 int형 포인터
arr는 int 형 포인터 이므로 함수의 인자로 전달되기 위해서는
Func(arr);
함수의 매개변수가 다음과 같이 int형 포인터로 선언이 되어야 한다.
void Func(int *ptr);
다음과 같이 2차원 배열이 선언이 되면
int arr[3][3];
배열 이름 arr가 가리키는 것은 [0][0]에 위치한 배열의 첫번째 요소이다.
그리고 2차원 배열의 경우
arr[0]는 1행의 첫번째 요소, arr[1]은 2행의 첫번째요소, arr[2]는 3행의 첫번째 요소를 가리킨다.
2차원 배열 이름 arr를 대 상으로 sizeof 연산을 하는 경우 배열의 전체 크기를 반환한다.
arr[0], arr[1], arr[2]를 대상으로 sizeof연산을 하는 경우 각 행의 크기를 반환한다.
즉 arr는 첫번째 요소를 가리키면서 배열 전체를 의미한다. arr[0]은 첫번째 요소를 가리키지만 1행의 첫번째요소를 가리킨다.
arr와 arr[0]이 같다고 생각하면 안된다 . 하지만 결과적으로 주소값은 같다.
포인터 대상으로 증가 및 감소연산도 가능하다.
arr+0은 arr[0]과 같고
arr+1은 arr[1]과 같다.
2차원 배열 이름을 대상으로 증가 및 감소 연산을 할 시 , 연산결과는 각 행의 첫번째 요소의 주소값이 된다.
주소값의 차이는 똑같이 sizeof(자료형)를 나타내는데 서로 다른 배열의 자료형이 같아도 열의 길이에 따라 증가 및 감소연산의 주소값이 다르게 나타난다.
왜냐하면 2차원 배열의 열에 모두 주소값이 있는데 열의 길이가 더 길면 그만큼 주소값이 차이나게 되는것이다 .
2차원 배열이름의 포인터형을 선언할 때 고려해야되는 점은
가리키는 대상, 배열이름(포인터)를 대상으로 값을 증가, 감소시 얼마가 증가, 감소가 되는가?
즉 열의 길이정보가 포인터에 들어가야 되는 것이다.
int형 변수이면서 포인터 연산시 sizeof(int) x 4 의 크기 단위로 증가및 감소하는 포인터 변수 ptr은 다음과 같이 선언한다.
int (*ptr) [4];
int형은 변수를 가리키는 포인터
(*ptr)은 포인터
[4] 포인터 연산시 4칸씩 건너뛰는 포인터 ( 즉 첫번째 요소에만 접근)
위와 같은 포인터 변수는 2차원 배열을 가리키는 용도로만 사용되기 때문에 이러한 유형의 포인터를 가리켜 배열 포인터 변수라 한다.
#include <stdio.h>
int main(){
int arr1[2][2] = { {1, 2}, {3, 4}};
int arr2[3][2] = { {1, 2}, {3, 4}, {5, 6}};
int (*ptr) [2]; //배열 포인터 선언
int i;
for(i=0; i<2; i++){
printf("%d %d \n" ptr[i][0], ptr[i][1]);
}
ptr=arr2;
for(i=0; i<3; i++){
printf("%d %d \n" ptr[i][0], ptr[i][1]);
}
return 0;
}
================================출력결과=========================================
1 2
3 4
1 2
3 4
5 6
배열 포인터로 첫번째 요소에 접근할 수 있다 .
int *A[2] ; //포인터 배열 : 포인터를 담을 수 있는 배열선언 -int형 포인터 변수로 이루어진 배열
int (*B)[2]; //배열 포인터 : 열의 길이를 2씩 건너 뛸수있는 포인터 변수 선언 - 2차원 배열에 사용
두 선언을 혼동하면 안된다.
2차원 배열을 함수의 인자로 전달할때는
int arr1[2][4];
double arr2[4][5];
위와 같이 선언되었다 했을때 둘의 각각 배열 포인터는
int (*parr1)[4];
doube (*parr2)[5];
이다.
함수 Func에 인자로 전달한다 가정하면
void Func(int (*parr1)[4], double (*parr2)[5]);
위와 같이 정의해야 한다 .
또한
int parr1[][4], double parr2[][5] 으로 전달도 가능하다 .
즉 위의 선언과 함수에 선언된 매개변수의 선언은 완전히 동일한 의미이다.
하지만 이 둘은 매개변수의 선언에서만 같은 의미를 지닌다.
int (*parr1)[4] == int parr1[][4]
2차원 배열의 행길이 계산하기
sizeof(arr1)/ sizeof(arr1[0]) = 배열의 행의 길이가 나온다 .
위 공식은 배열의 행의 길이를 연산할 때 주로 사용된다.
2차원 배열에서도 arr[i]와 *(arr+i)는 같다.
int arr[3][3]= {1,2,3,4,5,6,7,8,9};
위 와같이 2차원 배열을 초기화 하였다.
인덱스 [2][1]의 값을 출력한다 가정을 하면
printf("%d", arr[2][1] );
printf("%d", *(arr+2)[1] );
printf("%d", *(arr[2]+1) );
printf("%d", *(*(arr+2)+1) );
위의 값은 모두 같다 .
즉 arr[i]와 *(arr+i)는 같다.
*(arr[i]+j) == (*(arr+i)[j]) == *(*(arr+i)+j) == arr[i][j] 이다.
**arr는 arr[0][0]과 같은 표현이다.
*(*(arr+0)+0);
이는 3차원 이상의 배열에서도 적용이 가능하다.
'휴지통 > C 언어' 카테고리의 다른 글
구조체 (0) | 2021.02.04 |
---|---|
문자열 입력 함수 (0) | 2021.02.02 |
문자와 문자열 (0) | 2021.02.01 |
함수 포인터, void 포인터 (0) | 2021.01.31 |
1차원 배열, 포인터 (0) | 2021.01.23 |