본문 바로가기
휴지통/C 언어

다차원 배열과 포인터, 다중 포인터

by 신재권 2021. 1. 30.

다차원 배열은 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 = &num; 		//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