프린세스 다이어리

[C] 다차원 배열과 포인터 배열 본문

C, C++

[C] 다차원 배열과 포인터 배열

개발공주 2021. 9. 29. 12:59
728x90

1. 2차원 배열

 

2차원 배열은 행렬 데이터를 표현할 때, 그래프 알고리즘을 처리할 때, 다수의 실생활 데이터를 처리할 때 등 사용된다. 흔히 우리가 보는 표 구조가 2차원 배열과 흡사하다. 2차원 배열은 1차원 배열이 중첩되었다는 의미로, [] 대괄호를 두 번 연속하여 쓴다.

 

자료형 배열 이름[행][열] = { {값, 값, 값,... }, {값, 값, 값,...},... }

자료형과 배열 이름을 정해주고, 행과 열이 어느 정도의 크기를 가지고 있는지 써 줘야 한다. 가령 가로 10, 세로 10의 크기를 가지고 있는 100개의 정수형 값이 들어가는 2차원 배열이면 int a[10][10]; 라고 정의해준다. 초기화하고 싶은 값이 있다면 {} 중괄호를 중첩하여 값을 넣어준다.

 

2차원 배열을 화면에 출력한 형태다.

2차원 배열 또한 인덱스는 0부터 시작한다. A[행][열] 형태이기 때문에 가로행이 내려갈수록 첫 번째 인덱스 값이 증가하고, 세로열이 오른쪽으로 갈수록 두 번째 인덱스 값이 증가한다.

 

2. 2차원 배열의 사용

 

2차원 배열은 행과 열을 표현한다는 특성 때문에 2중 for문과 함께 많이 사용된다. 

#include <stdio.h>

int a[3][3] = {{1,2,3}, {1,2,3}, {1,2,3}};

int main(void) {  
    for (int i = 0; i < 3; i++)
    {
       for (int j = 0; j < 3; j++)
        {
            printf("%d ", a[i][j]);
        }
        printf("\n");
    }
}

단순히 3행 3열 형태를 가진 2차원 배열의 기본 형태를 출력해보는 코드다. 

 

1 2 3 
1 2 3 
1 2 3

이와 같이 출력된다. 

 

3. 다차원 배열

 

2차원 배열처럼 다차원 배열도 사용할 수 있지만, 컴퓨터는 기본적으로 화면에 2차원 형태만 출력할 수 있다. 내부적으로 다차원 구조를 다루더라도 화면에 보이는 건 어렵다.

 

#include <stdio.h>

int a[2][3][3] = { { {1,2,3}, {4,5,6}, {7,8,9} }, 
                { {1,2,3}, {4,5,6}, {7,8,9} } };

int main(void) {  
    for (int i = 0; i < 2; i++)
    {
       for (int j = 0; j < 3; j++)
        {
            for (int k = 0; k < 3; k++)
            {
                printf("%d ", a[i][j][k]);
            }
            printf("\n");
        }
        printf("\n");
    }
}
1 2 3 
4 5 6 
7 8 9 

1 2 3 
4 5 6 
7 8 9

3중으로 괄호를 만들어서 3차원 배열을 초기화할 수 있다. 실행해 보면 전체 16개의 배열이 출력이 되는 것을 확인할 수 있다. 

 

4. 포인터 배열의 구조

 

배열은 내부적으로 포인터와 동일한 방식으로 동작한다. 배열의 이름은 배열의 원소의 첫 번째 주소가 된다. 포인터는 변수라 값이 바뀔 수 있으며, 배열의 이름은 상수라서 다른 주소로 바뀔 수가 없다는 점이 다르다. 

#include <stdio.h>

int main(void) {  
    int a = 10;
    int b[10];
    b = &a; 
}

a에는 정수를 할당했고, b에는 길이가 10인 배열을 할당했는데, 이 코드를 실행시켜보면 에러메시지가 출력된다. 

 

main.c:6:7: error: array type 'int [10]' is not assignable
    b = &a; 
    ~ ^
1 error generated.

흔히 상수에 값을 새로 대입하고자 할 때 등장하는 메시지다. 배열의 이름인 b는 상수이기 때문에 다른 주소 값을 넣어줄 수 없다.

 

#include <stdio.h>

int main(void) {  
    int a[5] = {1,2,3,4,5};
    int *b = a;
    printf("%d\n", b[2]);
}

배열의 주소값을 바꾸어서 처리를 해야 할 때는 포인터를 배열처럼 사용할 수 있다. 하나의 포인터 변수 *b를 만들어서 a를 가리키도록 하면 b라는 포인터 변수의 인덱스에 접근할 수도 있고 다양한 처리가 가능하다. int *b = &a가 아닌 int *b = a를 쓴 이유는, a는 배열이기 때문에 이미 일종의 포인터이기 때문이다. 

 

#include <stdio.h>

int main(void) {  
    int a[5] = {1,2,3,4,5};
    int *b = &a;
    printf("%d\n", b[2]);
}
main.c:5:10: warning: incompatible pointer types initializing 'int *' with an expression of
      type 'int (*)[5]' [-Wincompatible-pointer-types]
    int *b = &a;
         ^   ~~
1 warning generated.
3

 

 

*b의 인덱스에 접근하려면, 다음과 같은 방법으로도 접근할 수 있다.

#include <stdio.h>

int main(void) {  
    int a[5] = {1,2,3,4,5};
    int *b = &a[0];
    printf("%d\n", b[2]);
}

배열의 이름인 a는 배열의 첫 번째 원소의 주소이기도 하기 때문이다. a의 첫번째 원소의 주솟값을 넣어도 동일하게 동작한다.

 

참고

https://www.python2.net/questions-346068.htm

 

5. 포인터의 연산

 

#include <stdio.h>

int main(void) {  
    int a[5] = {1,2,3,4,5};
    int i;
    for (i = 0; i < 5; i++)
    {
        printf("%d ", a + i);
    }
    
}
main.c:8:23: warning: format specifies type 'int' but the argument has type 'int *' [-Wformat]
        printf("%d ", a + i);
                ~~    ^~~~~
1 warning generated.
-375417200 -375417196 -375417192 -375417188 -375417184 %

포인터는 연산을 통해 자료형의 크기만큼 이동한다. 즉 위에서는 정수형 배열이기 때문에 4바이트씩 이동한다. 일단 출력을 보았을 때 메모리 주소가 4바이트씩 이동한 것을 알 수 있다. 그런데 warning과 함께 출력이 되었다. 구글링을 열심히 했는데 원인은 아직도 찾지 못했다. 일단 포인터를 가지고 연산이 가능함을 출력 결과를 통해 알게 되었다.

 

printf("%d ", *(a + i));

위와 같이 *(a + i)처럼 포인터가 가리키고 있는 주소의 값을 표현하는 방식으로도 바꿀 수 있다. 출력을 해 보면 1부터 5까지 출력이 된다. 

 

printf("%d ", a[i]);

a의 i 인덱스로 바꾸어도 동일하게 1부터 5까지 출력이 된다. 

 

#include <stdio.h>

int main(void) {  
    double b[10];
    printf("%d %d\n", b, b + 9);
}
main.c:5:23: warning: format specifies type 'int' but the argument has type 'double *'
      [-Wformat]
    printf("%d %d\n", b, b + 9);
            ~~        ^
main.c:5:26: warning: format specifies type 'int' but the argument has type 'double *'
      [-Wformat]
    printf("%d %d\n", b, b + 9);
               ~~        ^~~~~
2 warnings generated.
-444565936 -444565864

예를 들어 크기가 10인 double형 배열을 선언했을 때 배열의 시작 주소가 x라고 한다면, 배열의 마지막 원소의 주소는 x+72 (8*9)가 된다. 

 

#include <stdio.h>

int main(void) {  
    int a[5] = {1,2,3,4,5};
    int *p = a;
    printf("%d \n", *(p++));
    printf("%d \n", *(++p));
    printf("%d \n", *(p + 2));
}
1
3
5

포인터를 이용해서 a 배열의 주소값을 넣어주었고, 첫 번째 printf()에서는 현재 p의 값을 가지고 있는 상태에서 출력이 이루어지므로, 1이 출력된다. 출력된 후 p는 1이 증가하였고, 두 번째 printf()에서 다시 한 칸 증가하는 연산이 이루어진 후 3이 출력된다. 마지막 printf()에서는 5가 출력된다. 

 

6. 2차원 배열을 포인터로 처리하기

 

#include <stdio.h>

int main(void) {  
    int a[2][5] = {{1,2,3,4,5}, {6,7,8,9,10}}; 
    int (*p)[5] = a[1];
    for (int i = 0; i < 5; i++)
    {
        printf("%d ", p[0][i]);
    }
}

2차원 배열은 포인터의 포인터, 즉 이중 포인터로 처리할 수 있다. 하나의 포인터 변수인 *p를 만들고, 배열을 가리키는 포인터로 만들어주었다. 특정한 행(배열, 즉 포인터)을 가리키는 포인터이기 때문에 이중 포인터인 것이다. 그러면 *p는 5개의 열을 가지는 행을 의미하게 된다. 여기에 a[1]를 넣어주는 것은, a의 두 번째 행 {6,7,8,9,10}에서 첫 번째 원소를 가리키고 있는 주소값을 넣어 준다는 의미다. 

 

main.c:5:11: warning: incompatible pointer types initializing 'int (*)[5]' with an expression
      of type 'int [5]'; take the address with & [-Wincompatible-pointer-types]
    int (*p)[5] = a[1];
          ^       ~~~~
                  &
1 warning generated.
6 7 8 9 10 %

실행해 보니 오류가 나서 아래와 같이 고쳐주었다.

int (*p)[5] = &a[1];

반복문을 이용해서 p[0][i] 값을 출력해 보니, 오류 없이 6에서 10까지 출력이 되었다. 

이중 포인터이기 때문에, p[0]은 p의 0번째 행, 즉 &a[1]을 넣어주었기 때문에 {6,7,8,9,10}을 나타내는 것이고, p[0][i]는 p의 0번째 행의 5개 원소를 하나씩 출력하게 되었다. 

728x90
Comments