본문 바로가기
C++

C++ 배열 & 포인터

by godfeeling 2020. 7. 5.

배열(array)이란?

배열(array)은 같은 타입의 변수들로 이루어진 유한 집합으로 정의할 수 있습니다.

 

배열을 구성하는 각각의 값을 배열 요소(element)라고 하며, 배열에서의 위치를 가리키는 숫자를 인덱스(index)라고 합니다.

 

C++에서 인덱스는 언제나 0부터 시작하며, 0을 포함한 양의 정수만을 가질 수 있습니다.

 

 

 

배열은 같은 종류의 데이터를 많이 다뤄야 하는 경우에 사용할 수 있는 가장 기본적인 자료 구조입니다.

 

 

 

배열은 선언되는 형식에 따라 1차원 배열, 2차원 배열뿐만 아니라 그 이상의 다차원 배열로도 선언할 수 있습니다.

 

하지만 현실적으로 이해하기가 쉬운 2차원 배열까지가 많이 사용됩니다.

 

1차원 배열

1차원 배열은 가장 기본적인 배열로 다음과 같은 문법에 따라 선언합니다.

 

문법

타입 배열이름[배열길이];

 

 

 

타입은 배열 요소로 들어가는 변수의 타입을 명시합니다.

 

배열 이름은 배열이 선언된 후에 배열에 접근하기 위해 사용됩니다.

 

배열의 길이는 해당 배열이 몇 개의 배열 요소를 가지게 되는지 명시합니다.

 

 

 

열은 선언만 하고 초기화하지 않으면, 모든 배열 요소가 쓰레깃값으로 채워집니다.

 

 

 

예제

int sum = 0;

 

int grade[3]; // 길이가 3int형 배열 선언

 

 

 

// 인덱스를 이용한 배열의 초기화

 

grade[0] = 85; // 국어 점수

 

grade[1] = 65; // 영어 점수

 

grade[2] = 90; // 수학 점수

 

 

 

for (int i = 0; i < 3; i++)

 

{

 

sum += grade[i]; // 인덱스를 이용한 배열로의 접근

 

}

 

 

 

cout << "국영수 과목 총 점수 합계는 " << sum << "점이고, 평균 점수는 " << (double)sum/3 <<"점입니다.";

 

위의 예제는 int형 데이터를 3개 저장할 수 있는 배열을 선언하고 있습니다.

 

또한, 0부터 시작하는 인덱스(index)를 이용하면 각각의 배열 요소에 따로 접근할 수 있습니다.

 

배열의 선언과 동시에 초기화하는 방법

C++에서는 변수와 마찬가지로 배열도 선언과 동시에 초기화할 수 있습니다.

 

다음과 같이 괄호({})를 사용하여 초깃값을 나열한 것을 초기화 리스트라고 합니다.

 

문법

타입 배열이름[배열길이] = {배열요소1, 배열요소2, ...};

 

 

 

, 초기화 리스트의 타입과 배열의 타입은 반드시 일치해야 합니다.

 

만약 초기화 리스트의 개수가 배열의 총 길이보다 적으면, 배열의 앞에서부터 차례대로 초기화될 것입니다.

 

이때 초기화되지 못한 나머지 배열 요소는 모두 0으로 초기화됩니다.

 

초기화 리스트의 개수가 배열의 길이보다 많을 경우에는 아래 배열의 특징에서 따로 다루도록 하겠습니다.

 

 

 

하지만 초기화 리스트를 이용한 초기화 방식은 반드시 배열의 선언과 함께 정의되어야 합니다.

 

배열이 먼저 선언된 후에는 이 방식으로 배열의 요소를 초기화할 수 없습니다.

 

예제

int arr1[3] = {0, 1, 2}; // 배열의 선언과 동시에 초기화는 가능함.

 

int arr2[3]; // 배열의 선언

 

arr2[3] = {0, 1, 2}; // 배열이 먼저 선언된 후에는 이 방식으로 초기화될 수 없음. 오류가 발생함.

 

arr2 = arr1; // 길이가 같더라도 하나의 배열을 다른 배열에 통째로 대입할 수는 없음. 오류가 발생함.

 

 

 

다음 예제는 앞선 예제와 같은 배열을 선언과 동시에 초기화 리스트로 초기화하는 예제입니다.

 

예제

int sum = 0;

 

int grade[3] = {85, 65, 90}; // 길이가 3int형 배열의 선언과 동시에 초기화

 

 

 

for (int i = 0; i < 3; i++)

 

{

 

sum += grade[i]; // 인덱스를 이용한 배열의 접근

 

}

 

 

 

cout << "국영수 과목 총 점수 합계는 " << sum << "점이고, 평균 점수는 " << (double)sum/3 <<"점입니다.";

 

배열의 길이 자동 설정

C++에서는 초기화 리스트에 맞춰 자동으로 배열의 길이를 설정할 수도 있습니다.

 

문법

타입 배열이름[] = {배열요소1, 배열요소2, ...};

 

 

 

배열의 길이를 따로 입력하지 않은 배열은 초기화 리스트의 배열 요소 개수에 맞춰 자동으로 배열의 길이가 설정됩니다.

 

 

 

예제

int arr[] = {1, 2, 3};

 

 

 

위의 예제에서 int형 배열 arr의 길이는 자동으로 3으로 설정됨과 동시에 초기화 리스트에 의해 초기화됩니다.

 

배열의 특징

C++에서 배열은 다음과 같은 특징을 가집니다.

 

 

 

1. 배열의 길이를 선언할 때에는 반드시 상수를 사용해야 합니다.

 

2. 배열 요소의 인덱스는 언제나 0부터 시작합니다.

 

3. C++ 컴파일러는 배열의 길이를 전혀 신경 쓰지 않습니다.

 

 

 

예제

int sum = 0;

 

int grade[3] = {85, 65, 90}; // grade[0], grade[1], grade[2]만 선언 및 초기화

 

grade[3] = 100; // grade[3]를 선언하지 않고 초기화 진행

 

 

 

for (int i = 0; i < 4; i++) // grade[3]도 수식에 포함

 

{

 

sum += grade[i]; // 인덱스를 이용한 배열의 접근

 

}

 

 

 

cout << "국영수 과목 총 점수 합계는 " << sum << "점이고, 평균 점수는 " << (double)sum/3 <<"점입니다.";

 

위의 예제에서는 길이가 3int형 배열 grade를 선언하고 있습니다.

 

, 배열 grade의 배열 요소는 grade[0], grade[1], grade[2]만이 존재합니다.

 

하지만 존재하지도 않는 grade[3]이라는 배열 요소의 초기화를 진행하고, 반복문을 통해 수식에서도 이용합니다.

 

 

 

이때 C++ 컴파일러는 오류는 커녕 수식에서까지 이 배열 요소를 이용하여 결과까지 출력해 줍니다.

 

하지만 이 결과는 개발자가 전혀 의도하지 않은 결과물이며, 이러한 프로그램은 종종 예상치 못한 결과를 내주기도 합니다.

 

위처럼 C++에서는 컴파일러가 일일이 배열의 길이 등을 검사하여 오류를 출력해 주지 않습니다.

 

따라서 C++로 프로그래밍할 때에는 언제나 이런 계산을 개발자가 직접 해주어야 합니다.

 

배열이 차지하는 메모리의 크기

C++에서 배열을 복사하거나 배열 요소에 특정 작업을 하고 싶을 때는 해당 배열이 차지하는 메모리의 크기를 정확히 알고 있는 것이 좋습니다.

 

 

 

배열이 차지하는 총 메모리의 크기는 다음 수식을 사용하여 구할 수 있습니다.

 

수식

배열이 차지하는 메모리의 크기 = 배열의 길이 X sizeof(타입)

 

 

 

그리고 배열의 길이를 알고 싶을 때에는 다음 수식을 사용하여 구할 수 있습니다.

 

수식

배열의 길이 = sizeof(배열 이름) / sizeof(배열 이름[0])

 

 

 

위의 수식에서 배열 이름[0]은 해당 배열의 타입을 나타내기 위해서 사용되었습니다.

 

 

 

예제

int grade[] = {85, 65, 90}; // 배열의 길이를 명시하지 않음

 

int len = sizeof(grade) / sizeof(grade[0]); // 배열의 길이를 구하는 공식

 

 

 

cout << "배열 grade의 길이는 " << len << "입니다.";

 

C++11에서의 배열 초기화

C++11에서는 배열의 초기화에 관해 다음과 같은 사항들이 변경되었습니다.

 

 

 

1. 배열을 초기화할 때에 대입 연산자(=)를 사용하지 않아도 됩니다.

 

2. 값을 명시하지 않고 괄호({})만을 사용하여 초기화하면, 모든 배열 요소를 0으로 초기화할 수 있습니다.

 

3. 초기화 리스트를 사용하여 배열을 초기화할 경우에는 narrowing cast를 할 수 없습니다.

 

4. array 템플릿 클래스가 추가되었습니다.

 

narrowing cast

이전 버전의 C++에서 발생하던 수많은 호환성 문제를 C++11에서는 초기화 리스트를 사용하여 방지할 수 있습니다.

 

C++11에서는 초기화 리스트를 사용하여 변수나 배열을 초기화할 경우에 narrowing cast를 허용하지 않습니다.

 

따라서 초기화 리스트를 이용한 초기화에서 narrowing cast가 발생하면, 경고(warning)를 발생시킵니다.

 

 

 

narrowing cast란 다음 예제와 같이 초기화를 통해 발생하는 암시적인 데이터의 손실을 의미합니다.

 

예제

int var = 3.14; // narrowing cast

 

 

 

위의 예제는 int형 변수를 실숫값으로 초기화함으로써 데이터의 손실이 발생합니다.

 

이렇게 데이터의 손실이 발생하는 암시적인 타입 변환을 narrowing cast라고 합니다.

 

 

 

C++11에서는 초기화 리스트를 이용하여 이러한 narrowing cast로 인한 데이터의 손실 및 호환성 문제를 미리 방지할 수 있습니다.

 

예제

int var = {3.14}; // 초기화 리스트를 통한 narrowing cast는 허용하지 않으므로, 경고를 발생시킴.

 

다차원 배열(multi-dimensional array)

다차원 배열이란 2차원 이상의 배열을 의미하며, 배열 요소로 또 다른 배열을 가지는 배열을 의미합니다.

 

 

 

, 2차원 배열은 배열 요소로 1차원 배열을 가지는 배열이며,

 

3차원 배열은 배열 요소로 2차원 배열을 가지는 배열이고,

 

4차원 배열은 배열 요소로 3차원 배열을 가지는 배열인 것입니다.

 

2차원 배열(two dimensional array)

2차원 배열이란 배열의 요소로 1차원 배열을 가지는 배열입니다.

 

C++에서는 2차원 배열을 나타내는 타입을 따로 제공하지 않습니다.

 

대신에 1차원 배열의 배열 요소로 또 다른 1차원 배열을 사용하여 2차원 배열을 나타낼 수 있습니다.

 

 

 

2차원 배열은 다음과 같은 문법에 따라 선언할 수 있습니다.

 

문법

타입 배열이름[행의길이][열의길이];

 

 

 

타입은 배열 요소로 저장되는 변수의 타입을 설정합니다.

 

배열 이름은 배열이 선언된 후에 배열에 접근하기 위해 사용됩니다.

 

다음 예제는 앞선 그림을 C++ 프로그램으로 작성한 예제입니다.

 

예제

 

 

int arr1[6] = {10, 20, 30, 40, 50, 60};

 

int arr2[2][3] = {10, 20, 30, 40, 50, 60};

 

 

배열의 선언과 동시에 초기화하는 방법

1차원 배열과 마찬가지로 2차원 배열도 선언과 동시에 초기화할 수 있습니다.

 

2차원 배열은 1차원 배열과는 달리 다음과 같이 여러 방식으로 초기화할 수 있습니다.

 

 

 

1. 1차원 배열의 초기화 형태를 따르는 방식

 

2. 배열의 모든 요소를 초기화하는 방식

 

3. 배열의 일부 요소만을 초기화하는 방식

 

1차원 배열의 초기화 형태를 따르는 방식

C++에서는 2차원 배열을 1차원 배열의 초기화 형태로도 초기화할 수 있습니다.

 

문법

타입 배열이름[행의길이][열의길이] = {배열요소[0][0], 배열요소[0][1], ..., 배열요소[1][0], 배열요소[1][1], ..., 배열요소[2][0], 배열요소[2][1], ...};

 

 

 

이 방식으로는 2차원 배열의 배열 요소[0][0]부터 차례대로 초기화됩니다.

 

만약에 초기화하는 배열 요소의 개수가 배열의 총 길이보다 적으면, 나머지 배열 요소는 모두 0으로 초기화됩니다.

 

배열의 모든 요소를 초기화하는 방식

C++에서는 2차원 배열의 모든 요소를 좀 더 직관적으로 초기화할 수도 있습니다.

 

문법

타입 배열이름[행의길이][열의길이] =

 

{

 

{배열요소[0][0], 배열요소[0][1], ...},

 

{배열요소[1][0], 배열요소[1][1], ...},

 

{배열요소[2][0], 배열요소[2][1], ...},

 

...

 

};

 

 

 

이 방식은 앞서 살펴본 1차원 배열의 초기화 형태를 따르는 방식과 결과는 같습니다.

 

하지만 좀 더 직관적으로 2차원 배열의 모습을 알 수 있으므로, 보통 이 방식을 많이 사용합니다.

 

예제

int arr1[2][3] = {10, 20, 30, 40};

 

int arr2[2][3] = {

 

{10, 20, 30},

 

{40, 50, 60}

 

};

 

 

배열의 일부 요소만을 초기화하는 방식

C++에서는 2차원 배열의 일부 요소만을 초기화할 수도 있습니다.

 

 

 

이 방식으로는 다음 예제처럼 2차원 배열의 원하는 배열 요소만을 초기화할 수 있습니다.

 

이때 초기화하지 않은 배열 요소는 모두 0으로 자동 초기화됩니다.

 

예제

int arr_col_len, arr_row_len;

 

int arr[3][4] = {

 

{10, 20},

 

{30, 40, 50, 60},

 

{0, 0, 70, 80}

 

};

 

 

 

arr_col_len = sizeof(arr[0]) / sizeof(arr[0][0]); // 2차원 배열의 열의 길이를 계산함

 

arr_row_len = (sizeof(arr) / arr_col_len) / sizeof(arr[0][0]); // 2차원 배열의 행의 길이를 계산함

 

 

 

cout << "arr의 배열 요소의 값" << endl;

 

for (int i = 0; i < arr_row_len; i++)

 

{

 

for (int j = 0; j < arr_col_len; j++)

 

{

 

cout << setw(4) << arr[i][j];

 

}

 

cout << endl;

 

}

 

위의 예제에서 2차원 배열 열의 길이를 구할 때 사용하는 수식은 다음과 같습니다.

 

수식

arr_col_len = sizeof(arr[0]) / sizeof(arr[0][0]);

 

 

 

열의 길이는 sizeof(arr[0])으로 2차원 배열 한 행의 길이를 먼저 구한 후에, 그 값을 배열 타입의 크기로 나누어서 구합니다.

 

열의 길이를 이용하면 2차원 배열 행의 길이도 구할 수 있습니다.

 

수식

arr_row_len = (sizeof(arr) / arr_col_len) / sizeof(arr[0][0]);

 

 

 

행의 길이는 (sizeof(arr) / arr_col_len)으로 2차원 배열 한 열의 길이를 먼저 구한 후에, 그 값을 배열 타입의 크기로 나누어서 구할 수 있습니다.

 

배열의 길이 자동 설정

1차원 배열과 마찬가지로 2차원 배열도 배열의 길이를 명시하지 않고, 자동으로 배열의 길이를 설정할 수 있습니다.

 

, 행의 길이는 생략할 수 있지만, 열의 길이는 반드시 명시해야 합니다.

 

 

 

다음 예제는 앞선 예제에서 행의 길이를 생략한 예제로, 같은 결과를 출력합니다.

 

이 예제에서 행의 길이를 명시하고, 열의 길이를 생략하면 컴파일할 때 오류가 발생하는 것을 확인할 수 있습니다.

 

예제

int arr_col_len, arr_row_len;

 

//int arr[3][] = {

 

int arr[][4] = {

 

{10, 20},

 

{30, 40, 50, 60},

 

{0, 0, 70, 80}

 

};

 

 

 

arr_col_len = sizeof(arr[0]) / sizeof(arr[0][0]); // 2차원 배열의 열의 길이를 계산함

 

arr_row_len = (sizeof(arr) / arr_col_len) / sizeof(arr[0][0]); // 2차원 배열의 행의 길이를 계산함

 

 

 

cout << "arr의 배열 요소의 값" << endl;

 

for (int i = 0; i < arr_row_len; i++)

 

{

 

for (int j = 0; j < arr_col_len; j++)

 

{

 

cout << setw(4) << arr[i][j];

 

}

 

cout << endl;

 

}

 

주소값의 이해

데이터의 주소값이란 해당 데이터가 저장된 메모리의 시작 주소를 의미합니다.

 

C++에서는 이러한 주소값을 1바이트 크기의 메모리 공간으로 나누어 이해할 수 있습니다.

 

예를 들어, int형 데이터는 4바이트의 크기를 가지지만, int형 데이터의 주소값은 시작 주소 1바이트만을 가리키게 됩니다.

 

포인터란?

C++에서 포인터(pointer)란 메모리의 주소값을 저장하는 변수이며, 포인터 변수라고도 부릅니다.

 

char형 변수가 문자를 저장하고, int형 변수가 정수를 저장하는 것처럼 포인터는 주소값을 저장하는 데 사용됩니다.

 

예제

int n = 100; // 변수의 선언

 

int *ptr = &n; // 포인터의 선언

 

포인터 연산자

C++에서 포인터와 연관되어 사용되는 연산자는 다음과 같습니다.

 

 

 

1. 주소 연산자(&)

 

2. 참조 연산자(*)

 

주소 연산자(&)

주소 연산자는 변수의 이름 앞에 사용하여, 해당 변수의 주소값을 반환합니다.

 

'&'기호는 앰퍼샌드(ampersand)라고 읽으며, 번지 연산자라고도 불립니다.

 

참조 연산자(*)

참조 연산자는 포인터의 이름이나 주소 앞에 사용하여, 포인터에 저장된 주소에 저장되어 있는 값을 반환합니다.

 

'*'기호는 역참조 연산자로 에스크리터(asterisk operator)라고도 불립니다.

 

 

 

C++에서 '*'기호는 사용하는 위치에 따라 다양한 용도로 사용됩니다.

 

이항 연산자로 사용하면 곱셈 연산으로, 포인터의 선언 시에도, 메모리에 접근할 때도 사용됩니다.

 

포인터의 선언

C++에서 포인터는 다음 문법에 따라 선언할 수 있습니다.

 

문법

타입* 포인터이름;

 

 

 

타입이란 포인터가 가리키고자 하는 변수의 타입을 명시합니다.

 

포인터 이름은 포인터가 선언된 후에 포인터에 접근하기 위해 사용됩니다.

 

포인터를 선언할 때 참조 연산자(*)의 앞과 뒤에 존재하는 공백은 무시됩니다.

 

포인터의 동시 선언

C++에서는 여러 개의 포인터를 동시에 선언할 수 있습니다.

 

하지만 여러 개의 포인터를 동시에 선언할 때에는 다음과 같은 점에 주의해야 합니다.

 

 

 

다음 예는 두 개의 int형 포인터를 동시에 선언하려고 하는 예제입니다.

 

하지만 다음 예제에서 ptr1int형 포인터로, ptr2는 그냥 int형 변수로 선언될 것입니다.

 

잘못된 예제

int* ptr1, ptr2;

 

 

 

따라서 두 개의 int형 포인터를 선언하고 싶을 때에는 다음과 같이 각각의 포인터 변수 이름 앞에 참조 연산자(*)를 따로 사용하여 선언해야 합니다.

 

맞는 예제

int *ptr1, *ptr2;

 

포인터의 선언과 초기화

포인터를 선언한 후 참조 연산자(*)를 사용하기 전에 포인터는 반드시 초기화되어야 합니다.

 

초기화하지 않은 채로 참조 연산자를 사용하게 되면, 어딘지 알 수 없는 메모리 장소에 값을 저장하는 것이 됩니다.

 

이러한 동작은 매우 위험한 결과를 초래할 수도 있으며, 이렇게 발생한 오류는 디버깅하기도 매우 힘듭니다.

 

 

 

따라서 다음과 같이 포인터의 선언과 동시에 초기화를 함께 하는 것이 좋습니다.

 

문법

타입* 포인터이름 = &변수이름;

 

또는

 

타입* 포인터이름 = &주소값;

 

포인터의 참조

C++에서 선언된 포인터는 참조 연산자(*)를 사용하여 참조할 수 있습니다.

 

 

 

다음 예제는 포인터의 주소값과 함께 포인터가 가리키고 있는 주소값의 데이터를 참조하는 예제입니다.

 

예제

int x = 7; // 변수의 선언

 

int *ptr = &x; // 포인터의 선언

 

int **pptr = &ptr; // 포인터의 참조

 

예제

int num1 = 1234;

 

double num2 = 3.14;

 

 

 

int* ptr_num1 = &num1;

 

double* ptr_num2 = &num2;

 

 

 

cout << "포인터의 크기는 " << sizeof(ptr_num1) << "입니다." << endl;

 

cout << "포인터 ptr_num1가 가리키고 있는 주소값은 " << ptr_num1 << "입니다." << endl;

 

cout << "포인터 ptr_num1가 가리키고 있는 주소에 저장된 값은 " << *ptr_num1 << "입니다." << endl;

 

cout << "포인터 ptr_num2가 가리키고 있는 주소값은 " << ptr_num2 << "입니다." << endl;

 

cout << "포인터 ptr_num2가 가리키고 있는 주소에 저장된 값은 " << *ptr_num2 << "입니다.";

 

위 예제의 번 라인에서는 sizeof 연산자를 사용하여 포인터 변수의 크기를 구하고 있습니다.

 

 

 

포인터 변수는 메모리에서 변수의 위치를 나타내는 주소를 다루는 변수이므로, 그 크기는 일반적으로 CPU에 따라 결정됩니다.

 

따라서 32비트 CPU에서는 1워드(word)의 크기가 4바이트이므로, 포인터 변수의 크기 또한 4바이트가 될 것입니다.

 

 

 

하지만 이러한 포인터 변수의 크기는 컴파일러로 컴파일할 때 그 크기까지 직접 명시할 수 있습니다.

 

따라서 포인터 변수의 크기는 CPU의 종류와 컴파일할 때 사용된 컴파일러의 정책에 따라서 달라질 수 있습니다.

 

또한, 번과 번 라인에서처럼 포인터가 가리키는 변수의 타입에 따라 포인터의 타입도 같이 바꿔주고 있습니다.

 

포인터의 타입은 참조 연산자를 통해 값을 참조할 때, 참조할 메모리의 크기를 알려주는 역할을 하기 때문입니다.

 

워드(word)CPU가 한 번에 처리할 수 있는 데이터의 크기입니다.

1바이트는 8비트이므로 32비트 시스템에서는 32비트 / 8비트 = 4, 4바이트가 1워드(word)로 처리됩니다.

 

64비트 시스템에서는 64비트 / 8비트 = 8, 8바이트가 1워드(word)로 처리됩니다.

 

포인터 연산

포인터는 값을 증가시키거나 감소시키는 등의 제한된 연산만을 할 수 있습니다.

 

 

 

C++의 포인터 연산에는 다음과 같은 규칙이 있습니다.

 

 

 

1. 포인터끼리의 덧셈, 곱셈, 나눗셈은 아무런 의미가 없습니다.

 

2. 포인터끼리의 뺄셈은 두 포인터 사이의 상대적 거리를 나타냅니다.

 

3. 포인터에 정수를 더하거나 뺄 수는 있지만, 실수와의 연산은 허용하지 않습니다.

 

4. 포인터끼리 대입하거나 비교할 수 있습니다.

 

타입별 포인터 연산

C++의 포인터 연산에서 포인터 연산 후 각각의 포인터가 가리키고 있는 주소는 포인터의 타입에 따라 달라집니다.

 

그 증가 폭은 포인터가 가리키는 변수의 타입의 크기와 같습니다.

 

예를 들어, int형 포인터의 증가폭은 int형 타입의 크기인 4바이트만큼 증가하게 됩니다.

 

이 법칙은 포인터의 뺄셈에서도 똑같이 적용됩니다.

 

포인터와 배열의 관계

포인터와 배열은 매우 긴밀한 관계를 맺고 있으며, 어떤 부분에서는 서로를 대체할 수도 있습니다.

 

배열의 이름은 그 값을 변경할 수 없는 상수라는 점을 제외하면 포인터와 같습니다.

 

C++에서는 배열의 이름을 포인터처럼 사용할 수 있을 뿐만 아니라, 포인터를 배열의 이름처럼 사용할 수도 있습니다.

 

, C++에서는 배열의 이름이 주소로 해석되며, 해당 배열의 첫 번째 요소의 주소와 같게 됩니다.

 

예제

int arr[3] = {10, 20, 30}; // 배열 선언

 

int* ptr_arr = arr; // 포인터에 배열의 이름을 대입함.

 

 

 

cout << "배열의 이름을 이용하여 배열 요소에 접근 : " << arr[0] << ", " << arr[1] << ", " << arr[2] << endl;

 

cout << " 포인터를 이용하여 배열 요소에 접근 : "

 

<< ptr_arr[0] << ", " << ptr_arr[1] << ", " << ptr_arr[2] << endl;

 

cout << "배열의 이름을 이용한 배열의 크기 계산 : " << sizeof(arr) << endl;

 

cout << " 포인터를 이용한 배열의 크기 계산 : " << sizeof(ptr_arr);

 

위의 예제에서는 포인터에 배열의 이름을 대입한 후, 해당 포인터를 배열의 이름처럼 사용합니다.

 

 

 

하지만 배열의 크기를 계산할 때에는 배열의 이름과 포인터 사이에 큰 차이가 발생합니다.

 

배열의 이름을 이용한 크기 계산에서는 배열의 크기가 int형 배열 요소 3개의 크기인 12바이트로 제대로 출력됩니다.

 

하지만 포인터를 이용한 크기 계산에서는 배열의 크기가 아닌 포인터 변수 자체의 크기가 출력되는 차이가 생깁니다.

 

배열의 포인터 연산

다음 예제는 앞선 예제와는 반대로 배열의 이름을 포인터처럼 사용하는 예제입니다.

 

이 예제에서는 배열의 이름으로 포인터 연산을 진행하여 배열의 요소에 접근합니다.

 

예제

int arr[3] = {10, 20, 30}; // 배열 선언

 

 

 

cout << " 배열의 이름을 이용하여 배열 요소에 접근 : " << arr[0] << ", " << arr[1] << ", " << arr[2] << endl;

 

cout << "배열의 이름으로 포인터 연산을 해 배열 요소에 접근 : "

 

<< *(arr+0) << ", " << *(arr+1) << ", " << *(arr+2);

 

배열의 이름과 포인터 사이에는 다음과 같은 공식이 성립함을 알 수 있습니다.

 

공식

arr이 배열의 이름이거나 포인터이고 n이 정수일 때,

 

arr[n] == *(arr + n)

 

 

 

위의 공식은 1차원 배열뿐만 아니라 다차원 배열에서도 언제나 성립합니다.

 

배열에 관계된 연산을 할 때는 언제나 배열의 크기를 넘어서는 접근을 하지 않도록 주의해야 합니다.

 

포인터 연산을 이용하여 계산하다가 배열의 크기를 넘어서는 접근을 하는 경우, C++ 컴파일러는 어떠한 오류도 발생시키지 않습니다.

 

다만 잘못된 결과만을 반환하므로 C++로 프로그래밍할 때에는 언제나 배열의 크기에 주의해야 합니다.

 

메모리의 동적 할당(dynamic allocation)

데이터 영역과 스택 영역에 할당되는 메모리의 크기는 컴파일 타임(compile time)에 미리 결정됩니다.

 

하지만 힙 영역의 크기는 프로그램이 실행되는 도중인 런 타임(run time)에 사용자가 직접 결정하게 됩니다.

 

이렇게 런 타임에 메모리를 할당받는 것을 메모리의 동적 할당(dynamic allocation)이라고 합니다.

 

 

 

포인터의 가장 큰 목적은 런 타임에 이름 없는 메모리를 할당받아 포인터에 할당하여, 할당받은 메모리에 접근하는 것입니다.

 

C언어에서는 malloc() 함수 등의 라이브러리 함수를 제공하여 이러한 작업을 수행할 수 있게 해줍니다.

 

 

 

C++에서도 C언어의 라이브러리 함수를 사용하여 메모리의 동적 할당 및 해제를 할 수 있습니다.

 

하지만 C++에서는 메모리의 동적 할당 및 해제를 위한 더욱 효과적인 방법을 new 연산자와 delete 연산자를 통해 제공합니다.

 

new 연산자

C언어에서는 malloc()이나 calloc() 함수 등을 이용하여 메모리의 동적 할당을 수행합니다.

 

C++에서도 위의 함수를 사용할 수 있지만, 더 나은 방법인 new 연산자를 이용한 방법을 제공하고 있습니다.

 

 

 

C++에서 new 연산자는 다음과 같은 문법으로 사용합니다.

 

문법

타입* 포인터이름 = new 타입;

 

 

 

첫 번째 타입은 데이터에 맞는 포인터를 선언하기 위해, 두 번째 타입은 메모리의 종류를 지정하기 위해 사용됩니다.

 

만약 사용할 수 있는 메모리가 부족하여 새로운 메모리를 만들지 못했다면, new 연산자는 널 포인터를 반환합니다.

 

 

 

new 연산자는 자유 기억 공간(free store)이라고 불리는 메모리 공간(memory pool)에 객체를 위한 메모리를 할당받습니다.

 

또한, new 연산자를 통해 할당받은 메모리는 따로 이름이 없으므로 해당 포인터로만 접근할 수 있게 됩니다.

 

delete 연산자

C언어에서는 free() 함수를 이용하여 동적으로 할당받은 메모리를 다시 운영체제로 반환합니다.

 

이와 마찬가지로 C++에서는 delete 연산자를 사용하여, 더는 사용하지 않는 메모리를 다시 메모리 공간에 돌려줄 수 있습니다.

 

 

 

C++에서 delete 연산자는 다음과 같은 문법으로 사용합니다.

 

문법

delete 포인터이름;

 

 

 

다음 예제는 런 타임에 int형과 double형 데이터를 위한 메모리를 할당받고, delete 연산자를 사용하여 더 이상 사용하지 않는 메모리를 반환하는 예제입니다.

 

예제

int* ptr_int = new int;

 

*ptr_int = 100;

 

 

 

double* ptr_double = new double;

 

*ptr_double = 100.123;

 

 

 

cout << "int형 숫자의 값은 " << *ptr_int << "입니다." << endl;

 

cout << "int형 숫자의 메모리 주소는 " << ptr_int << "입니다." << endl;

 

 

 

cout << "double형 숫자의 값은 " << *ptr_double << "입니다." << endl;

 

cout << "double형 숫자의 메모리 주소는 " << ptr_double << "입니다." << endl;

 

 

 

delete ptr_int;

 

delete ptr_double;

 

이때 new 연산자를 통해 생성한 메모리가 아닌 변수를 선언하여 생성한 메모리는 delete 연산자로 해제할 수 없습니다.

 

delete 연산자는 반드시 new 연산자를 통해 할당된 메모리를 해제할 때에만 사용해야 합니다.

 

또한, 한 번 해제한 메모리를 다시 해제하려고 시도하면 오류가 발생합니다.

'C++' 카테고리의 다른 글

C++ 구조체  (0) 2020.07.05
C++ 문자열  (0) 2020.07.05
C++ 제어문  (0) 2020.07.05
C++ 연산자  (0) 2020.07.05
C++ 타입  (0) 2020.07.05

댓글