변수라는 것부터 설명해서 배열과 포인터에 대해서 이야기 한다.
변수
데이타를 담아 둘 수 있는 문자이다. 데이타가 저장되는 메모리의 주소는 ‘&’문자(&=주소연산자)를 변수명 앞에 붙여서 찾을 수 있다. 참고로 자료형선언없이 주소변수 앞에 붙는 “*”문자(*=역참조연산자)를 변수명 앞에 붙이면
자료형
데이타의 형식이다. int, float 형 및 int*, float* 형 등이 있다. 배열형은 없다.
int a; // a라는 변수에 저장되는 데이타의 형식인 int형임을 의미한다.
int* a; // a라는 포인터변수에 저장되는 데이타의 형식이 int형임을 의미한다. int *a와 같다.
그런데 int*을 자료형이라고 한 것은, a라는 문자에 저장되는 값이 int가 아니라 주소값이기 때문이다.
즉, int* a; 라는 의미는 int형 데이타를 가리키는 주소 a를 선언한 것이기 때문이다.
그래서 윗 식은 항상 int형 데이타를 저장하는 포인터 변수 a를 초기화한다라고 이해해야 한다.
변수선언 및 접근
- 일반변수
int a; // a라는 변수를 선언했는데 a에는 int형 자료를 넣을 수 있다.
a=1; // a에 1을 대입한다.
- 포인터변수
int a;
int* ptr = &a; // ptr라는 포인터 변수를 선언했는데 이 포인터 변수는 int형 데이타를 저장하는 주소를 담고 있다.
#include <stdio.h>
int main() {
int a=10;
int* ptr; ptr=&a;
// 윗 문장은 int* ptr = &a;와 같다.
printf("%d\n\n", *ptr); //*ptr에서 *이 역참조연산자이다.
return 0;
}
변수의 정의를 보면 데이타를 저장하는 문자라고 했으니까 ptr은 주소데이타를 저장하고 있는 문자라고 보면된다. 그래서*
int* p; or int *p:
p라는 포인터 자료형을 선언했는데, p는 주소를 저장하고 이 주소는 int 자료형을 저장하는 공간의 주소라는 의미이다. 다른 말로는 이러한 int 자료형 공간을 포인팅해주는 변수라는 의미이다.
int a=1;
int* p= &a;
라는 선언을 볼 수 있다. 이것은
int a=1;
int* p;
p=&a;
와 같다.
참조로,
p=&a의 양변에 역참조 연산자 *를 넣으면,
*p=*(&a);
즉, *p==a가 된다.
*배열설명 시작
이러한 int형 자료가 여러개 있는데 이것을 하나의 변수로 접근하려고 한다. 그러면 배열생성 연산자를 이용하면 된다.
int a[3];
이것은 int 자료형을 가지는 배열 3개를 만드는데 그 시작주소는 a라는 의미이다.
왜 배열을 먼저 만드느냐는 연산자 우선순위가 높기 때문이다.
C언어의 연산자 우선순위를 보면 (), [] 등이 최우선 순위를 가진다고 나온다. 그런데 ()는 이해가 간다. 괄호로 묶인 내용을 먼저 처리해야 한다는 의미이다. 그런데 []? 이것은 배열생성 연산자이면서 첨자 연산자이다.
그리고 매우중요한 이야기!
a가 시작주소를 가진다는 의미는 a는 포인터라는 의미이다.
데이타는 a[0], a[1]과 같이 배열이름에 첨자연산자(인덱스연산자)를 마지막에 넣으며 인덱스값을 인덱스연산자 안에 넣어주면 된다.
a[0]=1;
a[1]=2;
위에서는 인덱스연산자이므로
a+0의 주소는 a 시작주소 값이다.
a+1의 주소는 a 시작주소 + sizeof(int)의 값이다.
이것은 a가 포인터변수이기 때문이다.
즉,
int a[3];
int* ptr=a;
또는
int a[3];
int* ptr;
ptr=a;
**2차원 배열
2차원 배열은 행과 열을 가진다.
int a[2][3];
배열생성 연산자인 [2][3]을 이용해서 배열을 만들고 그 시작주소는 a가 가진다.
즉 a라는 포인터를 사용하겠다는 의미이다.
이런 관점에서 나온것이 배열 포인터이다. 즉, 배열을 가리키는 포인터를 이용하겠다는 의미이다.
int (*arrPointer)[3];
연산자 우선순위에 의해서
int 자료형 배열 3개를 만들고 이 배열을 arrPointer라는 포인터로 접근하겠다라는 의미이다.
이러면 각 행의 갯수는 마음대로 정할 수 있다.
그리고, 괄호를 묶은 이유는
int* arrPointer[3];
로 선언해 놓으면, 이것은 int 자료형을 가리키는 포인터를 담고 있는 배열 3개를 만들라는 의미로 해석되기 때문이다.
정리하면, 배열 포인터는 배열의 우선순위가 높기 때문에 int 자료형 값을 저장하는 배열부터 만들고, 이 배열의 시작주소를 포인터로 선언하는 것이다.
반면에, 포인터 배열은 int* 이라는 자료형을 가지는 배열을 만들고 이 배열에 주소를 넣겠다라는 의미이다.
아래는 배열포인터의 예와 포인터배열의 예를 설명해주는 코드이다. 모두 2차원 배열에 접근할 수 있다.
*배열포인터
#include <stdio.h>
int main() {
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
// 배열 포인터 선언
int (*arrPointer)[3] = matrix;
// 배열 포인터를 사용하여 값 출력
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 3; ++j) {
printf("%d ", arrPointer[i][j]);
}
printf("\n");
}
return 0;
}
위 코드를 보니 만들어진 배열에 접근하기 위해서는 배열과 포인터를 연결해주면 된다.
int (*arrPointer)[3] = matrix;
그러나 포인터 배열은 행 주소를 별도로 받아 들여야 한다.
*포인터배열
#include <stdio.h>
int main() {
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
// 포인터 배열 선언
int *ptrArray[2];
// 포인터 배열 초기화
for (int i = 0; i < 2; ++i) {
ptrArray[i] = matrix[i];
} // 행주소를 일치시켰다.
// 포인터 배열을 사용하여 값 출력
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 3; ++j) {
printf("%d ", ptrArray[i][j]);
}
printf("\n");
}
return 0;
}
윗 장점은 행 주소를 연결해주는 작업이 필요하나 열의 크기가 가변적일 때 사용할 수 있다.