일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- vue3
- 연결리스트
- 코딩테스트
- C
- 이진탐색
- 자바스크립트
- RT scheduling
- 브라우저
- 포인터
- alexnet
- pytorch
- cors
- 스택
- 타입스크립트
- 해시테이블
- 웹팩
- 프로그래머스
- 자료구조
- GraphQL
- 알고리즘
- Machine Learning
- 연결 리스트
- 큐
- 컨테이너
- 프로세스
- RxJS
- 릿코드
- 프론트엔드
- 배열
- APOLLO
- Today
- Total
프린세스 다이어리
[C] 다차원 배열과 포인터 배열 본문
1. 2차원 배열
2차원 배열은 행렬 데이터를 표현할 때, 그래프 알고리즘을 처리할 때, 다수의 실생활 데이터를 처리할 때 등 사용된다. 흔히 우리가 보는 표 구조가 2차원 배열과 흡사하다. 2차원 배열은 1차원 배열이 중첩되었다는 의미로, [] 대괄호를 두 번 연속하여 쓴다.
자료형 배열 이름[행][열] = { {값, 값, 값,... }, {값, 값, 값,...},... }
자료형과 배열 이름을 정해주고, 행과 열이 어느 정도의 크기를 가지고 있는지 써 줘야 한다. 가령 가로 10, 세로 10의 크기를 가지고 있는 100개의 정수형 값이 들어가는 2차원 배열이면 int a[10][10]; 라고 정의해준다. 초기화하고 싶은 값이 있다면 {} 중괄호를 중첩하여 값을 넣어준다.
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개 원소를 하나씩 출력하게 되었다.
'C, C++' 카테고리의 다른 글
[C] 함수 포인터 개념과 사용 방법 정리. (0) | 2021.09.30 |
---|---|
[C] 동적 메모리 할당이 무엇인가요? 개념, 함수, 활용방법 정리 (0) | 2021.09.30 |
[C] 컴퓨터가 변수를 처리하는 방식에 대하여 정리 (0) | 2021.09.29 |
[C] C언어에서 문자열을 다루는 방법, 문자열 관련 함수 정리 (0) | 2021.09.28 |
[C] C언어에서 문자를 처리하는 방법, 문자와 버퍼의 관계 (0) | 2021.09.27 |