#2 C++ 포인터 개념 및 실습
1. 포인터
1.1 포인터 기본 개념
포인터 변수는 주소값을 저장하는 변수인다.
1.1.1 포인터 변수 예시
p는 n의 주소를 저장하는 변수이다.
int n=10;
int* p =&n; //p는 n의 주소를 저장하는 변수이다.
*p =25; //n에 25가 저장됨.
cout<< n <<' '<< *p; // 둘다 25가 출력된다.
1.1.2 포인터를 사용한 원소의 탐색
기존의 일반적인 원소탐색 방법
int nArray[10];
for(int i=0; i<10; ++i)
nArray[i] = i; //*(nArray+i)=i
//nArray[i]==*(nArray+i) 와 같기 때문이다.
포인터를 사용한 원소의 탐색
int nArray[10];
int*p = &nArray[0]; //int*p = nArray; 해도 같은 의미!
for(int i = 0; i < 10; i++){
*(p+i) = i;
}
- 포인터 이용한 다양한 표현
int arr[6] = {1,2,3,4,5,6};
int *chr_ptr; // 1) int에 대한 포인터 변수 chr_ptr을 선언하는 문장 작성
int chr_ptr=arr; // chr_ptr이 arr 배열이 저장되어 있는 메모리 주소값을 갖도록 초기화.
cout << chr_ptr << endl; // 2) 배열의 시작주소 : 0x61fef4
chr_ptr++; // chr_ptr의 값을 하나 증가
cout << chr_ptr << endl; // 3) 배열의 시작주소+4 : 0x61fef8
cout << *chr_ptr << endl; // 4) 2
cout << arr << endl; // 5) ~~1~~ 0x61fef4
cout << arr+4 << endl; // 6) 5번쨰 원소의 주소 0x61fef4 + 16이므로, 0x61ff04
cout << &arr[3] << endl; // 7) 옆 문장이 실행되었을 때의 결과는? 4번째 원소의 주소 : 0x61ff00
cout << arr[4] <<endl; // 5
// arr[3]의 값을 chr_ptr을 이용하여 프린트
cout <<*(chr_ptr+2)<< endl; //아까 chr_ptr하나 증겨시켰으므로 +2만해도 된다.
- 배열과 포인터는 밀접한 연관이 있다.
int p[] = {6, 5, 4};
cout << p[0] << " " << p[1] << " " << p[2] << endl;
// 6 5 4
cout << *p << " " << *(p + 1) << " " << *(p + 2) << endl; // 주소를 증가시킨다음 그 내용
// 6 5 4
cout << p << " " << p + 1 << " " << p + 2 << endl; // 그다음 주소로 이동하고 싶을때는 +1을 해주면 된다.
//0x61fed8 0x61fedc 0x61fee0
cout <<&p[0] << " " << &p[1] << " " << &p[2] << endl; // 그다음 주소로 이동하고 싶을때는 +1을 해주면 된다.
//0x61fed8 0x61fedc 0x61fee0
=> int 정수형은 4바이트이다. 따라서 주소가 4씩 늘어난다.
1.2 16진수 개념
-
16진수로 표현된 값
:
-
0x61fef4는 16진수로 표현된 숫자이다. -
여기서
0x는 숫자가 16진수라는 의미를 나타낸다.
-
-
예를들어 16을 더한다.
:
- 이 값에 16을 더한다는 것은 10진수로 16을 더하는 것과 동일한 연산이다.
- 컴퓨터에서는 모든 숫자를 2진수(바이너리)로 처리하므로, 어떤 진수든 더하기 연산 자체는 동일하다. 하지만 숫자를 표현하는 방식만 다를 뿐이다.
-
계산 방법
:
-
0x61fef4는 10진수로 변환하면6422260이다. -
여기에 16을 더하면
6422276이 된다. -
다시 16진수로 변환하면
0x61ff04가 된다.
-
1.2.1 10진수에서 16진수로 변환:
- 예를 들어, 10진수 6422260을 16진수로 변환하면 0x61fef4가 된다.
- 계산 방식은 6422260 ÷ 16을 반복하여 나머지를 구하는 방식으로 진행된다.
<10진수 6422260을 16진수로 변환하기 >
#1. 16으로 나누기
10진수에서 16진수로 변환하는 방법은, 숫자를 계속 16으로 나누면서 나머지를 기록하고, 몫이 0이 될 때까지 이를 반복하는 것이다. 나머지들은 16진수 자릿수에 해당하며, 가장 먼저 나온 나머지가 16진수에서 가장 낮은 자리에 오고, 마지막 몫은 가장 높은 자리에 해당한다.
10진수 6422260을 예로 들어보겠다.
-
1단계: 6422260 ÷ 16 = 401391, 나머지 4
(여기서 4는 16진수의 가장 낮은 자리에 해당한다. 즉, 16진수로 변환된 수의 마지막 자리가 된다.)
-
2단계: 401391 ÷ 16 = 25086, 나머지 15
(나머지가 15이므로 16진수에서는 F로 표시된다. 따라서 F가 16진수에서 두 번째 자리에 온다.)
-
3단계: 25086 ÷ 16 = 1567, 나머지 14
(나머지 14는 16진수에서 E에 해당하므로, E가 세 번째 자리에 온다.)
-
4단계: 1567 ÷ 16 = 97, 나머지 15
(나머지 15는 16진수로 F이므로 네 번째 자리에 F가 온다.)
-
5단계: 97 ÷ 16 = 6, 나머지 1
(나머지 1이 다섯 번째 자리에 해당한다.)
-
6단계: 6 ÷ 16 = 0, 나머지 6
(마지막 몫이 0이므로 변환이 끝나고, 나머지 6이 가장 높은 자리에 해당한다.)
#2. 나머지들을 역순으로 나열
나눈 결과에서 나온 나머지들을 역순으로 나열하면, 16진수로 변환된 숫자가 나온다.
- 나머지들: 6, 1, F, E, F, 4
따라서 10진수 6422260을 16진수로 변환하면 0x61FEF4가 된다.
1.2.2 16진수에서 10진수로 변환:
- 16진수 0x61fef4를 10진수로 변환하려면 각 자릿수에 해당하는 값을 계산한다.
- 0x61fef4는 6 * 16^5 + 1 * 16^4 + 15 * 16^3 + 14 * 16^2 + 15 * 16^1 + 4 * 16
^0로 계산된다.
2. 실습
2.1 포인터 변수
int main(void)
{
int K[3] = {7, 8, 9};
cout << K << " " << &K[0] << " " << &K[1] << " " << &K[2] << endl;
// 0x61ff04 0x61ff04 0x61ff08 0x61ff0c
// 배열의 이름은 배열의 시작주소를 의미함. 따라서 K는 K[0]의 주소와 같다.
// K[1]은 K[0]의 주소에서 4만큼 떨어진곳에 있다. K[2]는 K[0]의 주소에서 8만큼 떨어진곳에 있다.
/* C++에서는 문자열로 취급되는 배열은 주소를 통해 출력할 수 있으며,
배열의 첫 번째 요소에서 시작해 **널 문자(\\0)**를 만날 때까지 문자열을 출력한다.*/
char C[4] = {'7', '8', '9','\\0'};
cout << C << " " << &C[0] << " " << &C[1] << " " << &C[2] << endl;
// 789 789 89 9
//&C[1]은 배열의 두 번째 요소 '8'의 주소를 가리킨다.
//이때 주소는 '8'의 위치부터 출력이 시작되며, 배열의 끝인 널 문자 '\\0'를 만날 때까지 출력된다.
int M[3] = {6, 5, 4};
int *p = M; // 주소를 저장하는 변수라는 뜻***
cout << *p << endl; // 변수의 내용이라는 뜻***
// 6
cout << p << " " << p + 1 << " " << p + 2 << endl; // 그다음 주소로 이동하고 싶을때는 +1을 해주면 된다.
// 0x61fee0 0x61fee4 0x61fee8
// 다음 둘은 같은 의미이다.
cout << p[0] << " " << p[1] << " " << p[2] << endl;
// 6 5 4
cout << *p << " " << *(p + 1) << " " << *(p + 2) << endl; // 주소를 증가시킨다음 그 내용
// 6 5 4
p = &K[2]; // p에 K[2]의 주소를 저장
cout << *p << " " << *(p - 1) << " " << *(p - 2) << endl; // 내용을 출력
// 9 8 7
cout << p[0] << " " << p[-1] << " " << p[-2] << endl;
// 9 8 7
return 0;
2.2 실습2-1
int main(void)
{
int K[3] = {7, 8, 9}; // K[0], K[1], K[2]
int M[4] = {6, 5, 4, 0}; // M[0], M[1], M[2], M[3]
int N[2] = {1, 2}; // N[0], N[1]
int *q[3]; // q[0], q[1], q[2]<---- 포인터 배열
q[0] = K; // q[0]에 K의 주소를 저장
q[1] = M; // q[1]에 M의 주소를 저장
q[2] = N; // q[2]에 N의 주소를 저장
cout << q[0] <<endl;
//q[0] : K의 주소
cout<< *(q[0]) << " " << *(q[0] + 1) << " " << *(q[0] + 2) << endl; // 7 8 9
cout <<q[0][0] << " " << q[0][1] << " " << q[0][2] << endl; // 7 8 9
//q[1] : M의 주소
cout<< *(q[1]) << " " << *(q[1] + 1) << " " << *(q[1] + 2) <<" "<<*(q[1]+3) <<endl; // 6 5 4 0
cout <<q[1][0] << " " << q[1][1] << " " << q[1][2] <<" "<<q[1][3] <<endl; // 6 5 4 0
//q[2] : N의 주소
cout<< *(q[2]) << " " << *(q[2] + 1) <<endl; // 1 2
cout <<q[2][0] << " " << q[2][1] <<endl; // 1 2
cout<< *(*(q+2)+0) << " " << *(*(q+2) + 1) <<endl; // 1 2
// q[2] : *(q+2)와 같다.
// q[2]의 주소에 0을 더하면 N[0]의 주소가 되고, 1을 더하면 N[1]의 주소가 된다.
return 0;
}
(\*(q+2) + 0):- (q+2)는 배열 N의 시작 주소(N[0]의 주소)이며, 여기에 0을 더하면 배열 N의 첫 번째 요소를 가리킨다.
- 따라서 (*(q+2) + 0)는 N[0]의 값인 1을 출력한다.
(\*(q+2) + 1):- (q + 2)는 여전히 배열 N의 시작 주소이다.
- 여기에 1을 더하면 배열 N의 두 번째 요소 N[1]의 주소가 된다.
- 이를 역참조하면 N[1]의 값인 2가 출력된다.
2.3 배열 호출 전달의 두가지 방법**
2.3.1 기존의 배열의 함수호출,전달
(이전에 실습1-3에서 했던 코드)
함수 호출 시 배열의 이름은 달라도 상관없다.중요한 것은 배열의 첫 번째 요소의 주소가 함수로 전달된다는 사실이다. 따라서 배열의 이름은 호출할 때와 함수 내에서 다르게 사용해도 문제되지 않는다.배열이 아닌 기본 자료형을 함수에 전달하면값이 복사되어 전달된다. 하지만!배열을 함수에 전달할 때배열의 값이 복사되지 않는다**.→ 배열의 **주소가 전달되기 때문에, 함수에서 배열의 요소를 변경하면 원본 배열도 함께 변경된다.
// 함수가 호출될때 값을 복사해서 가져온다. 따라서 이름이 달라도 상관없다.
double biggest(double b[], int n) // 배열의 이름과 배열의 크기를 받아야함
// 위의 코드는 이함수내에서 선언하는것과 같은 효과를 가진다.
{
double a_max = b[0];
for (int i = 1; i < n; i++)
{
if (a_max < b[i])
{
a_max = b[i];
}
}
return a_max;
}
int ex0909_6(void)
{
/*배열에 입력받기*/
double a[5]; // a[0] ~ a[4]
cout << a; // 배열의 이름은 배열의 시작주소를 의미함. 0x61fed0
cout << "type 5 floating point5 numbers>>";
for (int n = 0; n < 5; n++)
{
cin >> a[n];
}
for (int n = 0; n < 5; n++)
{
cout << a[n] << " ";
}
/*큰수 찾기*/
int N = 5;
double a_max = biggest(a, N); // 호출할때는 타입이 필요없고, 배열의 이름만 적어주면 됨***
cout << "max number is " << a_max << endl;
}
2.3.2 포인터를 이용한 배열 호출 전달
배열을 이용해서 다시 짜 보았다.
// double biggest(double *b, int n) //변수의 주소와 변수의 크기를 받아야함
double biggest1(double* b, int n)
{
double a_max = *(b+0); //b[0]
for (int i = 1; i < n; i++)
{
if (a_max < *(b+i))
{
a_max =*(b+i);
}
}
return a_max;
}
int main(void)
{
/*배열에 입력받기*/
double a[5]; // a[0] ~ a[4]
cout << a; // 배열의 이름은 배열의 시작주소를 의미함. 0x61fed0
cout << "type 5 floating point5 numbers>>";
for (int n = 0; n < 5; n++)
{
cin >> a[n];
}
for (int n = 0; n < 5; n++)
{
cout << a[n] << " ";
}
/*큰수 찾기*/
int N = 5;
double a_max = biggest1(a, N); // 호출할때는 타입이 필요없고, 배열의 이름만 적어주면 됨***
cout << "max number is " << a_max << endl;
return 0;
}
2.4 실습 2-2
메인함수의 출력 a와 b가 30,40이 되도록 수정해봐라
void pfunc_1_(int a, int b)
{
cout << a<<" "<<b <<endl;
a=30; b=40;
}
int main(void)
{
int a =10, b=20;
pfunc_1_(a,b);
cout <<a<<" "<<b<<endl;
return 0;
}
위의 코드를 실행하면, 호출한 함수에서 두값을 변경하였음에도, 다음과 같이 출력된다.

이를 통해, 호출된 함수의 변수와, 호출한 함수의 변수는 다른 변수임을 알수 있다.
| a | 10 |
|---|---|
| b | 20 |
| 함수가 호출되는 순간, 변수가 다른 주소에 복사된다. | |
| a | 10→ 30 |
| b | 20→ 40 |
즉, 호출된 함수에서 변수의 값을 바꾸어도
원래 함수에서는 바뀐 값이 적용되지 않는다.
이를 해결 ⇒ 포인터를 이용한 함수호출
//c는 주소이기 때문에 c에다가 넣으면 안된다.
void pfunc_1_(int* c, int* d)
{
cout <<*c<<" "<<*d <<endl;
*c=30; *d=40; //c는 주소이기 때문에 c에다가 넣으면 안된다.
//*C에 넣어야한다.
}
int main(void)
{
int a =10, b=20;
pfunc_1_(&a,&b);
cout << a<<" "<<b <<endl;
return 0;
}

댓글남기기