관리자 글쓰기
C++ 배열2
2022. 4. 5. 12:22 - pingu-s

배열과 포인터의 관계

 

배열명의 구조

 

이전에서 학습한 포인터와 배열에는 밀접한 관계가 있다. 이 관계를 살펴보기 위해 배열의 각 요소의 주소를 조사하는 것으로부터 시작한다.

 

포인터 파트에서 학습한 주소 연산자(&)를 생각해보자. 변수와 똑같이 배열의 각 요소에 대해 & 연산자를 사용할 수 있다. 다음과 같이 배열의 요소에 & 연산자를 사용하면 그 요소가 저장되어 있는 주소를 알 수 있다.

&test[0]
&test[1]

위의 표에서 배열의 1번째 요소(test[0]), 2번째 요소(test[1])의 값이 저장되어 있는 주소를 알 수 있다.

더구나 배열에서는 특별한 사용법으로 배열 요소의 주소를 나타낼 수 있는 것으로 되어 있다. '배열명'만 기술함으로써 배열의 1번째 요소의 주소를 나타낼 수 있다.

test

[ ]를 붙이거나 첨자를 붙여서는 안 된다. & 연산자도 필요 없다. 이 표기의 구조를 알기 위해 다음의 코드를 입력해 보도록 한다.

 

예제) 배열의 시작 요소값 찾기

test[5] 배열에 초깃값을 선언하고, 배열로부터 1번째 요소의 값을 출력하는 코드를 작성해 보도록 한다.

#include <iostream>
using namespace std;

int main()
{
	int test[5] = { 90, 80, 50, 20, 75 };

	cout << "test[0]의 값은 " << test[0] << " 입니다.\n";
	cout << "test[1]의 값은 " << test[1] << " 입니다.\n";
	cout << "test[0]의 주소는 " << &test[0] << "입니다.\n"; //①...
	cout << "test의 값은 " << test << "입니다.\n"; //②...
	cout << "즉, *test의 값은 " << *test << "입니다.\n";

	return 0;
}

①, ② 실행 결과를 살펴보면 'test'의 값이 '&test[0]'와 똑같은 것을 알 수 있다.

이와 같이 'test'라는 배열명만으로 배열의 1번째 요소인 'test[0]'의 주소를 나타낼 수 있다. 배열명이란 배열의 선두 요소의 주소를 저장하고 있는 포인터와 똑같은 기능을 가진다.

포인터 파트에서도 학습한 바와 같이 포인터에 간접 참조 연산자(*)를 사용하면 그것이 가리키는 변수의 값을 알 수 있었다. 배열명 test에 *를 붙인 경우에도 똑같이 배열의 1번째 요소인 test[0]의 값을 나타낼 수 있도록 되어 있다.

이것을 다음의 표에 종합해 둔다.

test &test[0] → 배열의 1번째 요소의 주소
*test test[0] → 배열의 1번째 요소의 값

 

포인터 연산

 

배열명은 배열의 시작 요소로의 포인터로서 사용한다는 것을 알았다.

그런데 C++에서는 포인터가 배열에 밀접한 관계를 가질 때 포인터에 대해 다음과 같은 연산을 수행할 수 있다.

연산자 포인터 연산의 예(단, p, p1, p2는 포인터)
+ p + 1 p가 가리키는 요소의 다음 요소의 주소를 얻는다.
- p - 1 p가 가리키는 요소 앞의 요소의 주소를 얻는다.
p1 - p2 p1과 p2사이의 요소 수를 얻는다.
++ p++ p가 가리키는 다음 요소의 주소를 얻는다.
-- p-- p가 가리키는 앞 요소의 주소를 얻는다.

 

포인터 연산은 식과 연산자 파트에서 학습한 사칙 연산 등의 계산과는 다소 다르다. 예를 들면, + 연산자를 살펴보도록 한다. + 연산자를 사용해 'p+1'이라는 계산을 하면 p가 가리키는 1개 다음의 요소의 주소를 얻는 연산이 수행된다. 주소의 값에 1을 더하는 계산을 하는 것이 아니다.

그러면 다음의 예를 보면서 포인터의 연산을 수행하는 것으로 한다.

 

예제) 포인터 연산

선언된 배열에서 &연산자와 *연산자를 사용하여 주소와 값을 출력하는 코드를 작성해 보도록 한다.

#include <iostream>
using namespace std;

int main()
{
	int test[5] = { 90, 80, 50, 20, 75 };

	cout << "test[0]의 값은 " << test[0] << " 입니다.\n";
	cout << "test[0]의 주소는 " << &test[0] << "입니다.\n";
	cout << "test의 값은 " << test << "입니다.\n";
	cout << "test+1의 값은 " << test + 1 << "입니다.\n"; //①...
	cout << "*(test+1)의 값은 " << *(test + 1) << "입니다.\n"; //②...

	return 0;
}

① +연산자를 사용하여 포인터의 덧셈을 해 보았다. test에 1을 더한 test + 1을 출력한다. 그러면 그다음의 요소인 배열 2번째 요소의 주소가 출력된다.

② 더욱이 *연산자를 사용해서 *(test + 1)을 조사하면 배열 2번째 요소의 값을 알 수 있다. 즉, *(test + 1)은 배열의 2번째 요소인 test[1]과 똑같은 의미로 된다.

 

이들의 내용을 다음의 표에 종합한다. 아래와 같이 배열은 첨자를 사용한 표기이나, 포인터의 덧셈을 사용한 표기라는 2종류의 표기로 똑같은 요소를 나타낼 수 있도록 되어 있다.

*test test[0] → 배열의 1번째 요소의 값
*(test + 1) test[1] → 배열의 2번째 요소의 값
*(test + 2) test[2] → 배열의 3번째 요소의 값
... ... ...

 

포인터의 연산

 

배열명을 사용할 때의 주의

 

지금까지 설명한 것처럼 배열명은 배열의 1번째 요소의 주소를 저장하고 있는 포인터와 똑같다고 생각된다. 단, 이 포인터는 통상의 포인터와 다른 점이 있다. 그것은 배열명으로 나타내는 포인터에는 다른 주소를 대입할 수 없다는 것이다. 다음의 코드를 살펴보도록 한다.

#include <iostream>
using namespace std;

int main()
{
	int a = 5;
	int test[5] = { 90, 80, 50, 20, 75 };

	test = &a; //잘못된 문장이다.

	return 0;
}

포인터 파트에서 학습한 것처럼 통상의 포인터에는 다른 변수의 주소를 대입하는 것이 가능하였다. 그러나 배열명은 그 배열의 1번째 요소 이외의 다른 주소를 나타낼 수 없다. 즉, 변수 a의 주소를 test에 대입할 수 없기 때문에 주의해야 한다.

 

인수와 배열

 

배열을 인수로 사용

 

배열과 포인터에는 밀접한 관계가 있는 것을 알았다. 이 절에서는 이 관계를 이용한 여러 가지 코드를 살펴보기로 한다. 우선 배열을 함수의 인수로 사용하는 코드를 살펴보는 것으로 한다. 다음의 코드를 입력해서 살펴보도록 한다.

 

예제) 함수의 인수로 배열을 사용

5명의 시험 점수를 입력받아 배열에 저장하고, 이 배열을 함수 avg()의 인수로 사용하여 점수의 평균을 구하는 코드를 작성해 보도록 한다.

#include <iostream>
using namespace std;

//avg 함수의 선언
double avg(int t[]); //배열을 함수의 인수로 사용한다.

int main()
{
	int test[5];

	cout << "5명의 시험 점수를 입력하시오.\n";
	for (int i = 0; i < 5; i++) {
		cin >> test[i];
	}
	double ans = avg(test); //배열명을 실인수(배열의 1번째 요소의 주소)로 넘긴다.
	cout << "5명의 평균점은 " << ans << "점 입니다.\n";

	return 0;
}

//avg 함수의 정의
double avg(int t[])
{
	double sum = 0;
	for (int i = 0; i < 5; i++) {
		sum += t[i]; //배열을 이용하여 성적의 합계를 구한다.
	}
	return sum / 5;
}

이 5명의 시험 평가 점수를 반환하는 avg() 함수에서는 배열을 인수로서 사용한다. 이때, 가인수에는 [ ]로 기술하고, 실인수로서 배열명 test를 기술하여 전달하는 것에 주목하도록 한다. 이와 같이 인수로 배열을 사용할 때에는 실인수로 배열명을 전달한다.

배열명은 1번째 요소의 주소를 나타내므로 함수에는 배열의 각 요소의 값이 아니고 배열의 1번째 요소의 주소만 전달되는 것이다. 배열을 함수에 전달하는 경우는 이 1번째 요소의 주소를 전달하는 방법을 사용한다.

 

포인터를 인수로 사용

 

앞에서 살펴본 배열과 포인터의 밀접한 관계를 사용하면 같은 함수를 포인터와 같이 기술할 수 있다.

 

//avg 함수의 선언
double avg(int* pT); //가인수를 포인터로서 표기할 수도 있다.
...
//avg 함수의 정의
double avg(int *pT)
{
	double sum = 0;
	for (int i = 0; i < 5; i++) {
		sum += *(pT + i); //배열을 포인터의 연산에 의한 표기로서 이용
	}
	return sum / 5;
}

이 함수의 가인수는 포인터 pT이다. 이때 호출 장소에서 배열의 1번째 요소의 주소가 전달되면 포인터 pT가 그 주소로 초기화된다. 이 때문에 함수 내에서는 포인터 pT에 대해 포인터 연산을 수행함으로써 배열 요소를 처리할 수 있게 된다.

즉, 이 avg() 함수는 앞선 예제의 avg() 함수 대신으로 사용할 수 있다. 전체의 코드는 다음과 같다.

 

#include <iostream>
using namespace std;

//avg 함수의 선언
double avg(int* pT);

int main()
{
	int test[5];

	cout << "5명의 시험 점수를 입력하시오.\n";
	for (int i = 0; i < 5; i++) {
		cin >> test[i];
	}
	double ans = avg(test);
	cout << "5명의 평균점은 " << ans << "점 입니다.\n";

	return 0;
}

//avg 함수의 정의
double avg(int *pT) //가인수를 포인터로 표기한다.
{
	double sum = 0;
	for (int i = 0; i < 5; i++) {
		sum += *(pT + i); //포인터의 연산에 의해 처리한다.
	}
	return sum / 5;
}

avg() 함수를 이용하는 부분의 코드는 앞선 예제와 똑같다. 배열을 받는 함수는 이와 같이 포인터로 표기한 가인수를 이용하는 것이 좋다.

 

포인터에 [ ] 연산자를 사용

 

지금까지의 경우는 배열명을 1번째 요소의 포인터로 취급할 수 있는 것을 살펴보았다. 사실은 이것과는 역으로 포인터에 [ ]를 사용해서 배열과 같이 표기하는 것도 가능하다. 예를 들면, 앞의 avg() 함수 내의 포인터 pT를 살펴보도록 한다. 이 포인터의 표기에 [ ]를 사용해서 다음과 같은 표기를 할 수 있다.

pT[2] //포인터에 []를 사용할 수 있다.

이 [ ]를 첨자 연산자(subscript operator)라고 한다. 이와 같이 포인터에 [ ]를 사용한 표기는 포인터 pT가 가리키고 요소에서 2개 다음의 요소를 나타내는 것으로 되어 있다.(*(pT+2)) 포인터가 배열을 가리키고 있을 때에는 포인터에 [ ]를 붙여서 그것이 가리키는 요소를 나타낼 수 있도록 되어 있다. 배열과 똑같은 표기로 된다.

단, [ ]를 올바르게 사용할 경우는 포인터와 배열이 밀접한 관계를 가질 때뿐이므로 주의해야 한다. 이 함수에서는 실인수로서 배열의 1번째 요소의 주소가 전달되기 때문에 [ ]를 사용하는 표기가 가능하다. 다른 포인터에 [ ]를 사용하면 코드를 실행했을 때에 치명적인 오류가 발생해 버리는 경우가 있기 때문에 주의해서 사용해야 한다.

아래의 코드는 조금 전에 작성한 avg() 함수 내의 포인터에 [ ] 연산자를 적용해서 배열과 같이 나타낸 것이다.

 

//avg 함수의 정의
double avg(int *pT)
{
	double sum = 0;
	for (int i = 0; i < 5; i++) {
		sum += pT[i]; //포인터에 []를 사용한 표기가 가능하다.
	}
	return sum / 5;
}

반복문 내에 pT[i]라는 표기를 사용함으로써 포인터 pT가 가리키는 배열의 요소에 순서대로 액세스 한다. 즉, 이 함수는 앞의 avg() 함수와 똑같은 처리를 수행한다.

이와 같이 포인터와 배열에 관계가 있는 경우는 포인터와 배열을 똑같이 취급할 수 있다.

 

(참고) i[pT] 형태도 가능

 

 

출처: 박흥복서정희. 2015. C++ 프로그래밍 (초보자를 위한). 문운당

'개발 > C++' 카테고리의 다른 글

C++의 고급 기능  (0) 2022.04.08
C++ 배열3  (0) 2022.04.06
C++ 배열  (0) 2022.04.04
C++ 포인터3  (0) 2022.03.31
C++ 포인터2  (0) 2022.03.30