C++를 공부하다 보면 아마 대부분 포인터 개념부터 이해하기 어려워지기 시작한다. 전공자인 나 또한 C++로 개발을 할 때 이 개념에 대해 정확하게 이해하지 못하고 개발을 했던 것 같다. 그래서 이번 포스팅을 통해 다시 한 번 그 개념을 정리해 보려 한다.
다음 코드를 보자.
int *p = &r;
위 코드가 포인터의 가장 기본적인 형태이다.
변수 앞에 "*"를 붙여 선언함으로써 해당 변수(p)가 포인터 변수임을 나타낸다.
- 포인터 변수
변수의 "주소값을 저장"하는 변수라 한다.
쉽게 말해 변수를 "가리키는" 변수라 한다.
위 개념을 토대로 포인터 변수가 어떻게 초기화가 되는지 보자.
변수(r)에 "&(Reference)"를 붙인 형태로 변수(r)의 메모리 상의 주소값으로 초기화를 한다.
이를 바탕으로 코드를 해석해보면
- 포인터 변수 p는 변수 r의 메모리 상의 주소값을 저장한다.
- 포인터 변수 p는 변수 r을 가리킨다.
여기서, 주의할 점은 p와 *p의 차이이다.
이 차이점을 기억해두자.
이를 토대로 위의 코드를 다시 작성하면
int *p;
p = &r;
위와 같이 나타낼 수 있다.
이 코드는 위의 설명을 보다 더 정확하게 표현하는 것으로 첫번 째 코드는 현재 코드를 축약한 형태로 나타낸 것이다.
다음 그림을 보자.
위에서 설명한 내용을 그림으로 나타낸 것으로 코드에서 포인터를 보게 되거나 혹은 내가 포인터를 사용하게 된다면 항상 이 그림을 떠올리며 코드를 이해하려 하면 된다.
이 개념만 놓고 보면 포인터를 이해하는데 크게 어렵지 않지만 코드가 길어지고 프로그램의 구조가 복잡해지면 이해하기 어려울 것이다.(나만 그랬을 수도...)
그래서 포인터를 보다 복잡한 구조에서 이해해 보기 위해 다음의 상황을 살펴 보려 한다.
- 배열을 동적할당 받는 경우
- 포인터를 인자로 받는 함수를 정의하는 경우
- 포인터를 반환하는 함수를 정의하는 경우
먼저, 이번 포스팅에서는 배열을 동적할당 받는 경우를 보자.
int **A = new int*[5];
위 코드는 int* 형(int형 포인터)의 원소들을 5개 저장할 수 있는 배열을 의미한다.
여기서, int형 포인터의 원소는 다음과 같이 정의 할 수 있다.
int *x = &y;
int *x = new int[5];
즉, 5개의 int형 포인터들을 원소로 갖는 배열은 1개 이상의 값들을 저장하는 배열을 원소로 갖는다는 것을 의미하므로 위 코드는 행의 크기가 5이고 열의 크기는 가변적인 "2차원 배열"을 나타낸다고 해석할 수 있다.
아직 위 코드는 열의 크기를 지정하지 않았기 때문에 2차원 배열을 정의하였다고 볼 수 없지만 2차원 배열을 나타낸다는 것은 파악할 수 있다.
2차원 배열의 정의는 다음 코드를 통해 할 수 있다.
for (int i = 0; i < 5; i++) {
A[i] = new int[5];
}
위 코드는 A의 각 원소들을 크기가 5인 1차원 배열로 고정함으로써 2차원 배열의 열의 크기를 5로 고정하지만, 이 크기를 A의 원소 별로 다르게 지정한다면 열의 크기가 가변적인 2차원 배열을 정의할 수 있다.
그림을 보자.(column 할당은 부분 생략하였습니다.)
위 코드가 메모리 상에서 어떤 구조를 이루고 있는 나타낸 것으로
빨간색 박스는 2차원 배열의 행을 나타내고 초록색 박스는 각각 열을 나타낸다.
참고로, 위와 같이 2차원 이상의 배열을 동적할당 받은 경우 메모리를 반납하기 위해서 가장 마지막에 할당 받은 메모리부터 반납하는 식으로 진행한다.
2차원 배열의 경우는 column 반납 -> row 반납의 과정으로 진행하면 된다.
코드는 다음과 같다.for (int i = 0; i < 5; i++) { delete[] A[i]; // column 반납 } delete[] A; // row 반납
.
.
.
다음 포스팅 "C++ 포인터(2)"에서 계속됩니다.