이 포스팅은 Object Oriented Programming 시리즈 23 편 중 11 번째 글 입니다.

  • Part 1 - 01: Introduction
  • Part 2 - 02: Identifier, Variable, constant, Std IO, Operator
  • Part 3 - 03: Functions #1 - Calling (호출)
  • Part 4 - 04: Functions #2 - Local, Global Variable
  • Part 5 - 05: Functions #3 - Recursion Function, Reference Variable (재귀함수)
  • Part 6 - 06: Functions #4 - Reference Variable vs. Pointer
  • Part 7 - 07: Functions #5 - CallbyValue, CallbyReference
  • Part 8 - 08: Selection and Repetition
  • Part 9 - 09: File Input & Output (파일입출력)
  • Part 10 - 10: String library, rand(), srand()
  • Part 11 - This Post
  • Part 12 - 12: Array, Vector (정적배열, 동적배열)
  • Part 13 - 13: class, object
  • Part 14 - 14: this, operator overloading
  • Part 15 - 15: friend, static, destructor
  • Part 16 - 16: Inherence (상속)
  • Part 17 - 17: Static Binding, Dynamic Binding, Header File
  • Part 18 - 18: Generic Programming, Template
  • Part 19 - 19: List Container
  • Part 20 - 20: Iterator (반복자)
  • Part 21 - 21: algorithm Library
  • Part 22 - 21: functional, lambda function
  • Part 23 - 22: Exception handling
▼ 목록 보기

What is pointer

포인터는 먼저 자료형으로 선언할 수 있다. 각각의 자료형에 대해 * 를 달게 되면 선언할 수 있다. 이 의미를 말로 파악하는 것이 중요한데, int* p 와 같은 경우, 특정 자료형이 int인 변수의 주소를 받을 수 있는 포인터 변수 p를 생성해라 로 생각 할 수 있다.

char* c 와 같은 경우, 특정 자료형이 char인 변수의 주소를 받을 수 있는 포인터 변수 c를 생성해라로 판단하면 된다. 그렇다면 어떤 식으로 메모리 공간에 할당이 될까?

#include <iostream>

using namespace std;

int main(){

    int x;
    x = 4;
    int* p;
    p = &x;

    cout << p << endl;

    cout << a << ", " << *p << endl;

    return 0;
}


스크린샷 2019-04-17 오후 12 53 09메모리와 할당

이렇게 메모리 공간에 자리잡게 된다. 이때 p의 주소의 있는 값으로가서, 그 값을 출력하게 하는 방법은 포인터 변수 p에 * 를 달면 가능하다. 해당 주소로 점프 를 한다고 이해하면 직관적이다.

Output
0x04
4, 4

이것을 그림으로 좀더 직관적으로 이해해보자.

스크린샷 2019-04-17 오후 12 53 52포인터 시각화

여기서 가장 핵심적으로 생각해야 하는 부분은, 포인터 변수를 선언한 뒤에 꼭 이 변수가 가리키는 주소를 할당해주어야 한다는 것이다.

PassingByAddress

이제까지 함수호출에 관해 Callbyvalue, Callbyreference와 같은 것을 배웠다. 그런데 이제 포인터를 배우게 되면, 함수의 인자를 포인터 변수로 선언하고, 값을 넘길 때 주소 를 넘겨줄 수 있다.

함수를 호출하고 나면, 메모리 공간에 저장된 두 변수의 값을 변경하는 Swap 함수를 만들어보자.

#include <iostream>

using namespace std;

void swap(int* var1, int* var2){
    int temp = *var1	// var1의 주소로 점프한 값을 temp에 저장해라
    *var1 = *var2;		// var1의 주소로 점프한 값에 var2의 주소로 점프한 값을 할당해라
    *var2 = temp;		// var2의 주소로 점프한 값에 temp의 값을 넣어라.
}

int main(){

    int a = 4, b = 5;
    cout << a << "," << b << endl;
    swap(&a,&b);
    cout << a << ", " << b << endl;

    return 0;
}
Output
4, 5
5, 4

Function Pointer

함수 포인터? 함수역시 포인터로 구성되어 있다는 사실을 모른다면 당황스러울 수 있다. 우리가 함수를 호출한다는 것은, 컴파일 이후 특정 메모리 공간에 정의되어 있는 행의 위치로 가서 읽어오라는 뜻이다.

그러기 위해서는 함수호출을 했을 때, 그 위치로 가야한다는 의미이고, 결국은 메모리의 주소를 알아야 한다는 결론을 얻을 수 있다. 따라서 우리가 함수의 이름을 설정하고 나면, 이 함수의 이름은 포인터이다.

즉, 컴파일 이후 메모리 공간에 생성된 일련의 작업 행위가 적힌 곳의 시작 위치 주소를 가지고 있다. 이 구조를 알고 있다면 우리는 특정 다른 함수를 짤때, 파라미터로 이 함수의 시작 주소를 넣어 더 고차원적인 프로그래밍을 가능하게 할 수 있다.

함수 포인터 사용법

함수가_반환하는_자료형 (*함수포인터로_사용할_변수_이름)(인풋파라미터의_자료형, 인풋파라미터의_자료형, ...)

Example

#include <iostream>

using namespace std;

int add(int a, int b){
    return x + y;
}

int multiply(int a, int b){
    return x*y;
}

int evaluate(int (*f)(int, int), int x, int y){
    return f(x, y);		// main 으로부터 함수의 시작주소를 포인터 변수 f로 받고,
    					// 그 함수의 인풋으로 x, y를 준 값을 반환해라.
}

int main(){
    cout << add(2, 3) << endl;
    cout << multiply(2, 3) <<  endl;
    cout << evaluate(&add, 2, 3) << endl;
    cout << evaluate(&multiply, 2, 3) << endl;
}
Output
5
6
5
6