컴퓨터 공부/© C

[C언어] 11. 다차원 배열과 포인터 배열

letzgorats 2021. 2. 14. 03:30

11. 다차원 배열과 포인터 배열

 

<2차원 배열의 필요성>

 

- 2차원 배열은 굉장히 많은 목적으로 사용된다

- 행렬 데이터를 표현할 때, 그래프 알고리즘을 처리할 때, 다수의 실생활 데이터를 처리 할 때 등

이름

영어성적

수학성적

국어성적

홍길동

85

97

79

유관순

100

89

98

이순신

99

77

99

장보고

89

70

78

신립

95

98

98

 ▶ 흔히 우리가 보는 표 구조“2차원 배열과 흡사하다.

 

 

<2차원 배열의 초기화>

 

- 2차원 배열은 1차원 배열이 중첩되었다는 의미로 [대괄호]를 두 번 연속하여 쓴다. [ ][ ]

「자료형 배열이름 [][]={{,,,…},{,,,…},{,,,…}∙∙∙}

ex) int a[10][10];  (10x10행렬)

 

- 2차원 배열 또한 0인덱스부터 시작한다.

ex) a[0][2] : 배열 a02/ a[0][0] : 배열 a00

 

<2차원 배열의 사용>

- 2차원 배열2for문과 함께 많이 사용된다.

 

(예제1) 2차원 배열-2중 for문

# include <stdio.h>

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

int main(void) {

        int i, j;

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

        return 0;

}

 (해설) 기본적으로 C언어에서의 배열은 행 우선으로 처리되기 때문에, a[3][3]은 첫 행에 {1,2,3}, 두번째 행에 {4,5,6}이 세번째 행에 {7,8,9} 가 들어간다고 보면 된다. 표로 나타내 보자면

1

2

3

4

5

6

7

8

9

 이처럼 그려진다. 그럼 a[0][2]는 무엇일까? 02열이므로 3이되는 것이다. , 각 중괄호에 있는 값들은 그 행에 있는 각각의 열로 들어간다고 보면 된다. 그래서 2차원 배열에서 결과적으로 int iint j ,2개의 변수를 만들어서 각각의 원소에 접근하는 방식이 많이 사용된다.

 i0부터 13까지 증가시키고, j0부터 3까지 1씩 증가시킴으로써 각각의 원소에 접근해서 값을 출력할 수 있는 것이다. 하나의 행에 있는 열을 전부 출력시킨 다음에는 printf(“\n”); 로 줄바꿈을 해줌으로써 총 3x3의 전체 행렬을 출력할 수 있다.

 

<다차원 배열>

- 2차원배열 이상의 다차원 배열도 출력 할 수 있다.

- 우리 컴퓨터기본적으로 화면에 2차원 형태만 출력할 수 있다

(아무리 다차원 배열이라 하더라도 그 형태 자체를 컴퓨터로 출력하기는 쉽지 않다. 따라서, “2차원 배열 형태로 출력하되 컴퓨터 내부적으로는 다차원 구조로 동작하는구나라고 이해하면 된다.)

 

(예제2) 3차원 배열-3중으로 괄호를 만들면 된다.

# 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) {

        int i, j, k;

        for (i = 0;i<2; i++) {
                for (j = 0; j < 3; j++) {
                        for (k = 0; k < 3; k++) {
                                printf("%d", a[i][j][k]);
                        }
        printf("\n");
                }      
        printf("\n");
        }
        system("pause");

        return 0;
}

(해설) 예제1과 마찬가지이지만, 배열의 형태를 3중으로 해준다. (for문을 안에 하나 더 넣어주면 된다.) 마찬가지로 줄바꿈을 해줘야하니까 printf\n); 을 가장 안쪽의for문 바로 바깥에 넣어주면 된다. 출력은 예제 1에서 나왔던 행렬이 2개 나온다. 비록, 2차원 배열처럼 출력이 되었더라도 내부적으로는 다차원으로 동작한것이다. 만약 어떤 문제를 풀다가 예제 1에서 나온 행렬을 2번 출력하게 만들라고 하면 이런식으로도 코드를 짤 수 있을 것이다.

 그리고 원소의 개수는 어떻게 딱 알 수 있을까? 이건 행렬에서도 배웠겠지만 그냥 행x열 하면 원소의 개수다. 3x3행렬의 원소의 개수는 9개이고, 2x3x3행렬의 원소의 개수는 18개라는 것을 바로 알 수 있다는 뜻이다.

 

 

<포인터 배열의 구조 분석>

 

- 배열은 포인터와 동일한 방식으로 동작한다.

- 배열의 이름배열의 첫 번째 원소의 주소가 된다.

- 유일한 차이점이라고 하면, 포인터는 변수이며 배열의 이름은 상수이다.

(포인터는 변수라서 값이 바뀔 수가 있는데, 배열의 이름은 상수라서 다른 주소로 바뀔 수 가 없다.)

 

(예제3) 배열 이름이 변수인지 상수인지 파악해보자

#include <stdio.h>

int main(void) {

        int a = 10;

        int b[10];

        b = &a;

        system("pause");

        return 0;
}

(해설) 오류가 뜬다. 왜일까? 배열 b를 만들어서 배열ba라는 변수의 주소값으로 넣어주도록 만들었는데 식이 수정할 수 있는 lvalue(left value)여야 한다고 하는 오류메시지가 발생한다. 이것은 상수에 데이터를 넣고자 할 때 흔히 발생할 수 있는 오류문구인데, 배열 이름자체가 상수이기 때문에 다른 주소값으로 넣어줄 수 없다는 것을 확인할 수 있다.

하지만 반대로 포인터를 배열처럼 사용 할 수는 있다.

 

(예제4) 포인터를 배열처럼 사용

#include <stdio.h>

int main(void) {

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

        int *b=&a;

        printf("%d\n",b[2]);

        system("pause");

        return 0;
}

(해설) 배열을 이것저것 주소값을 바꾸어서 처리할 일이 생긴다면 포인터를 사용하는 것이 합당하다. &a라고 쓰는게 원칙인데 a라고 써줘도 출력값은 알맞게 나왔다. 다만 포인터는 무조건 사용해야 된다는 점 잊지 마라!

 

- 배열의 이름은 배열의 첫 번째 원소의 주소라는 것을 기억해라. 즉 뭔말이냐! 예제4에서 *b=a*b=a[0] 이나 동일하게 동작을 한다!

 이유는 배열의 이름은 그 배열의 첫 번째 원소의 주소와 동일하니까 a라고 써도 되고 a[0]라고 써도 된다. a의 주소값이 a[0]의 주소값과 같기 때문에 ,다시 말하면 a의 주소값이 a[0]의 주소값이기 때문에 포인터를 사용해서 a의 값을 알고 싶을 땐 a,a[0] 둘다 써도 무방하다는 것이다.

 

 

- 포인터는 연산이 가능한데, 연산을 통해 자료형의 크기만큼 이동한다는 특징이 있다.

ex) 정수(int)형 포인터는 4바이트씩 이동한다.

▶ 7.포인터 파트에서 int형은 정수마다 4바이트의 메모리를 차지하기 때문에 정수가 1씩 증가하면 주소값은 4씩 증가한다고 배웠었다. , 여기서 말하고 싶은 것은 포인터는 연산이 가능하되, 자료형이 어떤 것인지에 따라 그 특징에 맞게 연산이 된다는 것!

 

(예제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\n",a+i);
        }

        system("pause");

        return 0;
}

(해설) a+i 라고 하면 a의 첫번째 원소의 주소값i를 더한 값(a라는 배열의 이름은 a의 첫번째 원소의 주소값과 같다고 했다!!)이고 이를 차례로 출력하면 배열 a의 원소의 주소값이 차례로 출력되는데 각 주소값이 4씩 증가하는 걸 볼 수 있다. (int형이니까)

 포인터를 이용해 a+i 부분을 *(a+i)라고 하면 각각의 값이 출력되는 것을 확인 할 수 있다. , *(a+i)는 결국 a[i]랑 같은 말이 되는거다!!!

 

(문제1) (따지자면 예제3 관련)

- 크기가 10double형 배열을 선언 했을 때 배열의 시작 주소가 X라고 할 때, 배열의 마지막 원소의 주소는 몇일까?

#include <stdio.h>

int main(void) {

        double b[10];

        printf("%d\n%d\n",b,b+9);

        system("pause");

        return 0;
}

(틀린 풀이) 크기가 10인 배열에서 배열의 시작 주소 즉, 첫 번째 원소의 주소가 X라는 뜻이니까, 인덱스가 0인 첫 번째 원소의 주소 값이 X라는 것이다. 크기가 10인 배열에서 맨 마지막 원소는 인덱스가 9인 자리이므로 마지막 원소의 주소값은 맨 첫번째 원소 X9를 더해준 X+9가 된다. 직관적으로도 계산이 가능하다. 첫번째 원소 주소값이 X니까 당연히 마지막 10번째 원소의 주소값은 X+9이다.

 

(해설) 결론적으로는 잘못 풀었다. 우선 너무 안일했다. int형이라고 생각해서 풀어도 x+9가 아니라 x+36(4bytesx9)이다. 우선 double형은 int형과는 달리 각각의 원소의 크기가 8bytes이다. , 첫 번째 주소가 X이므로 마지막 주소는 (8bytes x 9)를 해서 72크기만큼 떨어져 있는 것이다. 그럼 당연히 첫번째 주소에서 72만큼 이동한 X+72가 마지막원소의 주소이다.

 

(예제6) 배열을 포인터처럼 사용해 각 원소에 접근할 수도 있다.

#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));
                }

        system("pause");

        return 0;
}

(해설) 아까 예제 5 에서처럼 a의 원소값을 쫘르륵 출력하고 싶으면 주소값을 표현한 a+i 에 포인터(*)만 붙여주면 원소값이 나온다. a[i] 이다 결국.

 

(문제2)(따지자면 예제 4,5,6 관련)

- 다음 결과는 어떻게 될까?

 #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));

        system("pause");

        return 0;
}

(내풀이) 우선 *p=a a배열의 첫 번째 주소값을 포인터를 사용해 표현한 말 그대로 첫번째 원소 값이다. p1이므로 첫번째 printf문에서는 1이 그대로 출력된다. 왜냐하면 p++1씩 증가하되 출력은 이전의 값으로 하는거니까 1이 출력되고 p는 현재 2가 된 것이다. 두 번째 printf문에서는 3이 출력된다. p2의값을 가지고 있는데 ++p이므로 그대로 1을 더해주면서 출력해준다. p는 현재 3의 값을 가진다. 마지막 printf문에서는 2를 더해주므로 5가 출력된다.

 

(해설) 포인터를 이용해서 a배열의 주소값을 넣도록 만들었다. 현재 p a의 첫번째 원소의 주소값을 가리키고 있고 맨 첫 printf문을 보면 *(p++)은 첫번째 원소 1을 가리키고 있는 주소값을 포인터를 이용해 1이라는 값을 그대로 출력한 것이고 p++는 주소값 위치를 1번째 원소에서 2번째 원소로 1 증가시킨 것이므로 현재 pa의 두번째 원소의 주소값을 가리키고 있다. 다음 printf문에서는 p가 가리키고 있는 원소가 1 증가한 3으로 이동하였고 그 3을 가리키고 있는 주소값을 포인터를 통해 3이라는값으로 출력하게 된것이다. 현재 p3이라는 원소를 가리키고 있고 마지막 printf문에서는 2칸 이동한 5를 가리키게 되므로 마지막으로는 5가 출력 되는 것이다. (위치가 이동한 것이다!)

▶ 이렇게 증감연산자와 함께 포인터의 연산에 대해서 물어보는 문제가 대학시험이나 공무원 시험 문제에서도 기본적으로 많이 출제가 되고있으므로 바르게 이해하고 있자!

 

- 2차원 배열 또한 포인터로 처리할 수 있다.

(당연히 포인터는 포인터의 포인터를 가질 수 있는 거라고 배웠다. 2차원 배열 또한 주소값을 내부적으로 다 가지고 있는 거니까 포인터의 포인터 즉, 이중포인터로 처리할 수 있는 것이다.)

 

(예제 7)

#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];

        int i;

        for (i = 0; i < 5; i++) {
                printf("%d",p[0][i]);
        }
        system("pause");

        return 0;
}

(해설) 행렬 하나 만들어주고 하나의 포인터 변수인 p를 만들고(배열을 가리키는 p를 만들고, 즉 배열의 크기가 5인 배열 p인데 *(포인터)를 사용해 배열 a의 두번째 행=1(0,1행순이니까)을 가리키게 선언을 해주는 것이다.)

 특정한 행을 가리키는 포인터니까(*p[5]=a[1]) 이중 포인터라고 얘기를 할 수 있는 것이다. 하나에 5개의 열을 가지는 행을 의미하는 포인터(*p)를 만들어주었고 for문을 통해 두번째 행의 원소가 출력되게 할건데 이중 포인터이므로 p[0][i]처럼 [대괄호]를 두개 넣어서 0번째 행에 i번째 열을 출력하라고 말하는 것이다. 기본적으로 *p{6,7,8,9,10}을 가리키고 있으니까 순서대로 6,7,8,9,10 이 출력된다. 

 

(정리)

1) 컴퓨터에서 2차원 배열 이상을 표현 할 수 있다.

2) C언어의 배열은 내부적으로 포인터와 동일하므로 포인터 연산으로 배열을 대체할 수 있다.

반응형

'컴퓨터 공부 > © C' 카테고리의 다른 글

[C언어] 13. 함수 포인터  (0) 2021.02.14
[C언어] 12. 동적 메모리 할당  (0) 2021.02.14
[C언어] 10. 컴퓨터가 변수를 처리하는 방법  (0) 2021.02.14
[C언어] 9. 문자열  (0) 2021.02.13
[C언어] 8. 문자  (0) 2021.02.13