07.10 - 포인터 산술과 배열 인덱싱 (pointer arithmetic and array indexing)
포인터 산술 (pointer arithmetic)
C++ 언어를 사용하면 포인터에서 정수 추가 또는 빼기 작업을 수행할 수 있다. 만약 ptr
이 정수를 가리키면 ptr+1
은 ptr
다음 메모리의 정수 주소가 된다. ptr-1
은 ptr
이전 정수의 주소다.
ptr+1
은 ptr
뒤 메모리 주소를 반환하지 않고 ptr
이 가리키는 객체의 다음 메모리 주소를 반환한다. ptr
이 정수 (4바이트라고 가정)를 가리키면, ptr+3
은 ptr
이후에 3개의 정수 (12바이트)를 의미한다. ptr
이 항상 1바이트인 char을 가리키면, ptr+3
은 ptr
이후에 3개의 문자(3바이트)를 의미한다.
포인터 산술 표현식을 계산할 때 컴파일러는 항상 피연산자에 가리키고 있는 객체의 크기를 곱한다. 이를 스케일링(scaling)이라고 한다.
#include <iostream>
int main()
{
int value = 7;
int *ptr = &value;
std::cout << ptr << '\n';
std::cout << ptr+1 << '\n';
std::cout << ptr+2 << '\n';
std::cout << ptr+3 << '\n';
return 0;
}
0012FF7C
0012FF80
0012FF84
0012FF88
위 예제의 출력 결과를 보면, 출력된 주소는 각각 4바이트씩 차이가 난다. (7C+4 = 80) (테스트한 컴퓨터는 정수가 4바이트인 환경이다.)
int 대신 short를 사용하는 같은 프로그램 :
#include <iostream>
int main()
{
short value = 7;
short *ptr = &value;
std::cout << ptr << '\n';
std::cout << ptr+1 << '\n';
std::cout << ptr+2 << '\n';
std::cout << ptr+3 << '\n';
return 0;
}
0012FF7C
0012FF7E
0012FF80
0012FF82
short
는 2바이트이므로 각 주소는 2바이트씩 차이가 난다.
배열은 메모리에 순차적으로 배치된다. (Arrays are laid out sequentially in memory)
주소 연산자(&
)를 사용해서 배열이 메모리에 순차적으로 배치되었는지 확인할 수 있다. 즉 요소 0, 1, 2, ...은 모두 순서대로 인접해 있다.
#include <iostream>
int main()
{
int array[] = { 9, 7, 5, 3, 1 };
std::cout << "Element 0 is at address: " << &array[0] << '\n';
std::cout << "Element 1 is at address: " << &array[1] << '\n';
std::cout << "Element 2 is at address: " << &array[2] << '\n';
std::cout << "Element 3 is at address: " << &array[3] << '\n';
return 0;
}
Element 0 is at address: 0041FE9C
Element 1 is at address: 0041FEA0
Element 2 is at address: 0041FEA4
Element 3 is at address: 0041FEA8
이 메모리 주소는 각각 4바이트씩 떨어져 있으며, 테스트한 컴퓨터의 정수 크기다.
Pointer arithmetic, arrays, and the magic behind indexing
- 위에서 배열은 메모리에 순차적으로 배치된다는 것을 배웠다.
- '07.09 - 포인터와 배열' 포스트에서 고정 배열이 배열의 첫 번째 요소(인덱스 0)를 가리키는 포인터로 변환될 수 있다는 것도 배웠다.
- 포인터에 1을 추가하면 메모리가 가리키는 다음 객체의 메모리 주소가 반환된다는 것을 배웠다.
따라서 배열에 1을 추가하면 배열의 두 번째 요소(인덱스 1)를 가리킨다는 걸 알 수 있다.
이것을 다음 예제로 증명할 수 있다.
#include <iostream>
int main()
{
int array [5] = { 9, 7, 5, 3, 1 };
std::cout << &array[1] << '\n'; // print memory address of array element 1
std::cout << array+1 << '\n'; // print memory address of array pointer + 1
std::cout << array[1] << '\n'; // prints 7
std::cout << *(array+1) << '\n'; // prints 7 (note the parenthesis required here)
return 0;
}
연산자 *
가 연산자 +
보다 우선순위가 높으므로, 포인터 산술 결과를 역참조하려면 연산자 우선순위를 올바르게 하려면 괄호가 필요하다.
0017FB80
0017FB80
7
7
일반적으로 array[n]
은 *(array + n)
과 같다.
포인터를 사용해서 배열 반복 (Using a pointer to iterate through an array)
포인터와 포인터 산술을 통해 배열을 반복할 수 있다. 첨자([]) 사용이 일반적으로 더 읽기 쉬우므로 이 방법은 잘 쓰이지 않지만, 다음 예제는 이것이 가능하다는 것을 보여준다.
const int arrayLength = 7;
char name[arrayLength] = "Mollie";
int numVowels(0); // 모음의 갯수
for (char* ptr = name; ptr < name + arrayLength; ++ptr)
{
switch (*ptr)
{
case 'A':
case 'a':
case 'E':
case 'e':
case 'I':
case 'i':
case 'O':
case 'o':
case 'U':
case 'u':
++numVowels;
}
}
cout << name << " has " << numVowels << " vowels.\n"; // Mollie has 3 vowels
어떻게 작동할까? 위 프로그램은 포인터를 사용하여 배열의 각 요소에 접근한다. 고정 배열은 배열의 첫 번째 요소를 가리키는 포인터 변환될 수 있다는 걸 기억하자. 따라서 ptr
은 배열의 첫 번째 요소를 가리키게 됩니다. 각 요소의 값은 스위치 식에 의해 구분되며, 모음일 경우 numVowels
가 증가한다. 그런 다음 루프의 ++연산자를 사용하여 포인터를 배열의 다음 문자로 전진시킨다. 모든 문자를 검사하면 for 루프가 종료된다.
번역: 이 포스트의 원문은 http://www.learncpp.com/cpp-tutorial/6-8-pointers-and-arrays/ 입니다.