Prosto

[C언어] 포인터(Pointer)의 이해와 예제, 문제 -1 본문

Programing/C Programing

[C언어] 포인터(Pointer)의 이해와 예제, 문제 -1

Prosto 2017. 5. 27. 00:44



'포인터(Pointer) - 특정 변수(의 주소)를 가리키는 역할을 하는 변수!'

 

프로그래밍을 하다보면 main에서 한번 만들어둔 변수 값을 다른 함수에도 그대로 사용하고, 또 변경하고싶은 경우가 있을 수 있습니다.

같은 지역(main 내)에 있는 변수라면 사용, 변경은 간단하지만,

같은 지역이 아닌 경우(main 외 호출된 함수)는 해당 값을 임시 변수로 받아 반환하는 식으로 처리하게 됩니다.

이런 때 좀 더 효율적으로 처리할 수 있게 해주는 게 포인터를 사용하는 큰 이유로 볼 수 있습니다.

(사실 더 큰 이유는 메모리를 할당받고 해당 공간을 기억하는데 사용되지만, 그건 포인터 공부가 어느정도 된 후에 보면 금방 이해될 것이라 생각됩니다!)

 

간단한 소스와 함께 위의 설명을 다시 확인해볼까요?

#include<stdio.h>

//Prosto

int ReturnPlusOne(int n) {

return n + 1;

}

int main(void) {

int number = 3;

printf("%d\n", number);


number = 5;

printf("%d\n", number);


number = ReturnPlusOne(number);

printf("%d\n", number);


return 0;

}

이미 배웠던 내용들로 간단하죠? 이렇게 된 소스를 실행시키면 어떤 결과가 나올까요?



이렇게 결과가 나오죠!?


int number = 3;

 이 부분에서 number에는 선언과 함께 초기화하여 3이 들어갔고,


number = 5;

 이 부분에서 number에 다시 숫자 5를 대입하여 5로 바뀌었고,


number ReturnPlusOne(number);

이 부분에서 ReturnPlusOne함수를 호출하여 함수 실행 결과로 숫자에 1을 더한 결과를 돌려받아 number에 대입하였습니다.(6)


여기서 잠깐, 위와 같이 함수를 호출하면 어떻게 처리되는지 아시나요?

ReturnPlusOne(number) 함수가 호출된 시점에서 위쪽에 정의된 함수로 진입하게 됩니다.

이때, 인자로 number ->즉 5를 전달해줬고요.

그렇다면 실제 함수의 모양은 아래와 같다고 봐야합니다.

int ReturnPlusOne(int n=5) {

return n(5) + 1;     //즉 5+1 = 6을 반환.

}

이렇게요. ReturnPlusOne 함수가 호출되며 새롭게 int n이라는 변수를 만들고 거기에 인자로 받아온 숫자 5를 대입한 후

아래에서 사용된 것입니다. (main에서 사용된 number와 ReturnPlusOne 함수에서 사용된 n은 다른 변수가 되는 겁니다!)


위와 같은 과정을 거쳐 실행 결과로 3, 5, 6이 나온 겁니다!



포인터를 이야기하기 전에 확실히 정리해야 할 부분이 있습니다.

위 소스의 함수(ReturnPlusOne)에서 인자로 받아 초기화해준 int n은 main에서 만든 int number와 다른 변수라는 점!



다시 한번 확인해보기 위해 아래와 같이 함수를 바꾼 소스를 실행시켜볼까요?


#include<stdio.h>

//Prosto

void ChangePlusOne(int n) {

n += 1;

}

int main(void) {

int number = 3;

printf("%d\n", number);


number = 5;

printf("%d\n", number);


ChangePlusOne(number);

printf("%d\n", number);


return 0;

}


아까와 달라진 부분이 함수가 바뀌었죠?


ChangePlusOne 함수는 반환타입은 void(없음)이고, 인자로 아까처럼 int형 인자를 하나 받네요.

그리고 n에 1을 더했습니다.

main에서 어떤 차이가 있을까요?


실행 결과

아까와 달리 실행 결과가 3, 5, 5가 나오죠?


ChangePlusOne 함수에서 number의 값을 인자로 받아왔고 n에 1을 더했지만, number에는 아무 영향이 없습니다. 

 (이 이유는 위 소스에서 설명한 것과 같습니다.)


그럼 이제 main에서의 number와 함수의 n은 확실히 다르다는 걸 확인하셨나요?


이런 상황에서 포인터를 사용하면 함수에서도 main에서 만든 변수를 그대로 사용할 수 있습니다.


사용 방법을 배우기 전에 예제로 어떻게 사용되고, 어떤 차이가 있는지 확인해볼까요?

 

 

예제1. 위 소스에서 ChangePlusOne 함수포인터를 사용하여 실제 main의 number가 바뀌도록 수정해봅시다.


이렇게 바꿔주면 우측 실행 결과에서 우리가 원하던 대로 실제 number가 바뀌는 것을 확인할 수 있죠?

딱 보니, 여기에 새롭게 등장한 *&가 포인터에서 중요한 역할을 하고있는 것 같죠?



간단한 예제도 확인했으니,

 포인터(Pointer)에 대한 설명, 선언과 사용 그리고 *&는 무엇을 나타내는지도 함께 알아볼까요?



설명

 특정 메모리의 위치를 가리키도록 하는 변수를 포인터(pointer)라고 부릅니다. 이 포인터는 가리키는 대상의 위치, 즉 주소를 알아야 사용할 수 있습니다.

주소를 알고있다면 해당 주소에 들어있는 이 어떤 것인지도 확인하거나 다른 값을 넣는 것도 가능하죠.



참고..


지금까지 우리는 변수를 만들면 해당 변수는 메모리에 기록이 된다는 사실은 신경쓰지 않고,

편하게 변수의 이름과 값만 생각하고 사용했습니다.

 하지만, 실제로는 우리가 int n;이라고 소스를 입력하고 실행하면,

우리는 '메모리 x12345678번지(어느 빈 공간)에 자료형이 int형이고, 변수 이름은 n, 값은 초기화하지 않은 상태로 사용할게!'

라고 컴퓨터에게 알려준 것과 같습니다. (우리가 사용하는 변수는 자료형, 이름, 값 외에 (메모리)주소도 있었다는 거죠!)


그리고 그 후 n = 12;라고 입력하고 실행하면

'이름이 n인 변수가 어디있었지? 아 x12345678번지에 있구나. 여기의 값을 12로 바꿔줘!'

라고 이해하고 컴퓨터는 처리를 해줍니다.


그러면 포인터를 사용한다는 건 우리가 사용했던 기존의 방법대로 '이름이 n인 변수'를 찾는 게 아니라,

(메모리)주소(x12345678번지)를 찾아가는 거라는 거겠죠? 당연히 해당 주소에 있는 값도 알 수 있겠고요!



선언

 아까 잠깐 봤지만, 포인터도 기존에 사용하던 변수와 유사하게 생겼습니다.

자료형도 기존에 사용했던 int, float, double, long, char 등 존재하고요.

가장 큰 차이점이라면 선언 시 *이 자료형과 이름 사이에 들어간다는 점입니다.


int형 포인터를 (선언)만들고 싶다면,

int* ip;

이렇게 선언하면 ip라는 이름의 int형 포인터를 사용할 수 있습니다.


마찬가지로 float형 포인터를 선언하고 싶다면,

float* fp;

이렇게 입력하면 fp라는 이름의 float형 포인터를 사용할 수 있겠죠?


그렇다면 double형 포인터를 사용하려면요?

double* dp;

이렇게 입력하면 되겠죠? (ip, fp, dp는 그냥 이름이니 원하는 이름을 입력하면 됩니다. intPointer라든지 ddd라든지 자유롭게요.)


마찬가지로 char 포인터나 특정 구조체 포인터도 만들 수 있겠죠?



선언과 초기화
 그렇다면, 선언과 함께 초기화는 어떻게 할까요?
어떻게 하면 주소를 가지고 올 수 있을까요?
주소를 일일이 입력해줘야 할까요?
 아까 잠깐 봤었지만 그때 사용되는 게 바로 & 기호입니다! 이 기호를 주소를 알고싶은 변수 앞에 붙여주면 주소를 알려줍니다!

다시 한번 간단한 예제와 함께 확인해볼까요?
자 이렇게 int형 변수로 num1, num2를 만든 후,
int* num1Pointer = &num1;
int* num2Pointer = &num2;
를 입력해주니, 각각 num1과 num2의 주소를 받는 것을 확인할 수 있습니다.
그 결과 num1의 값과 *num1Pointer(num1Pointer가 가리키는 주소에 있는 값)값이 똑같이 출력되는 것을 확인할 수 있습니다.

여기서 잠깐,
 필요할 것 같아 몇 가지만 제대로 정리하고 가자면,

처음 선언할 때
int* p;
 int형 포인터로 p라는 이름의 변수를 선언한다!
p = &num;
 int형 포인터인 변수 p의 값에 num 변수의 주소값 대입.
printf("%d", *p);와 같은 식으로 p에 *을 붙여서 사용하면 p가 가리키는 주소에 있는 값을 나타내고,
printf("%d", p);와 같이 p만 사용하면 p가 가리키고 있는 주소를 나타냅니다!

아래 소스와 실행 결과를 보시죠.
어떤가요?
*p는 num과 같고,
p는 &num과 같죠?
(포인터 변수의 그냥 이름은 주소, *을 붙이면 해당 주소에 있는 값)

이 부분은 포인터 사용에 있어 가장 중요합니다. 포인터 변수에 *이 있을 때와 없을 때의 차이점이요.
(가리키는 주소!와 가리키는 주소에 있는 값!이죠.)




자, 여기까지  봤으니 간단한 예제를 하나만 더 확인하고,
실제로 포인터에 관한 문제를 풀어보도록 합시다!


예제2. int형 변수를 선언하여 사용자에게 입력받은 후 해당 값을 2배로 바꿔주는 함수(반환 형태 : void)를 사용하여 입력받은 수의 3배를 출력해주시오. (포인터가 인자로 들어간 함수 사용)


(어떻게 만들었을 것이라 생각되나요? 가장 중요한 함수가 어떻게 생겼을까요? 잠깐만 생각해보고 아래 소스 확인하세요.)



생각한 대로 소스가 나왔나요?


ChangeDouble 함수에 인자로 int형 포인터를 받았죠?

main에서 사용될 때는 만들어 둔 num 변수의 주소를 전달해줬고요.


결과적으로 ChangeDouble 함수는 아래와 같은 의미가 됐겠죠?

#define _CRT_SECURE_NO_WARNINGS //scanf 경고 없애는.

#include<stdio.h>

//Prosto


void ChangeDouble(int* p) {  //이 인자 부분을 풀어서 보면, int* p = &num;이 되는 거죠.

*p = *p * 2;

}


int main(void) {

int num;


printf("숫자 입력 : ");

scanf("%d", &num);  //추가로 사실 예전부터 사용했던 scanf의 &num은 변수 num의 주소라는 의미가 있었던 거죠!


ChangeDouble(&num);

printf("%d\n", num);

return 0;

}




지금까지 포인터의 가장 중요한 기본적인 부분들을 살펴봤으니 실제로 문제도 풀어보도록 합시다.

(3개의 문제가 준비되어 있습니다. 실제로 풀어보시고, 완성 소스엔 필요한 부분은 설명도 있으니 함께 보세요.)

 


문제1. 숫자를 입력 받아 해당 숫자를 가리키는 포인터를 만들어 해당 숫자의 값과 포인터의 값을 출력하고, 두 주소도 출력하여 같은 값이 나오는지 확인해보세요.


 (출력 결과 예)

 





문제2. 숫자를 입력 받아 해당 숫자를 제곱으로 바꿔주는 함수를 만드세요. (반환 형태가 void이고, 포인터 사용)


 (출력 결과 예)







문제3. 숫자1, 숫자2 두 개의 숫자를 입력 받아 포인터 두 개의 인자를 받는 함수로 두 숫자를 바꾼 후 출력해주는 프로그램을 만드세요.

 (숫자1을 출력하면 숫자2의 숫자가 나오고, 숫자2를 출력하면 숫자1이 나오게 하세요. 함수에서 두 값을 서로 바꿈)


 (출력 결과 예)





지금까지 포인터에 대한 기초적인 내용은 쭉 살펴봤습니다.

기본적인 내용만 다루는데도 글이 꽤 길어졌네요.

포인터는 C언어에서 잘 공부해두면 아주 좋은 개념입니다. C언의 꽃이라고 부르기도 하고요.(수학의 꽃이 미적분이나 정수론이라는 것처럼...)

처음부터 모든 걸 알기보다는 여러 번 보며 하나씩 알아가시면 충분할 것 같습니다!


이번 글은 포인터에 대한 첫 번째 글이었습니다.

다음 글에서 포인터를 조금 더 확인해보고, Call By Value와 Call By Reference에 대하여 알아보겠습니다.



질문은 댓글이나 메일로 따로 연락주시면 시간되는 때 답변드리겠습니다. ( 연락 )


Comments