포인터의 개념에 대해서는 C언어 문법을 공부 할 때에 계속 반복 했던 내용입니다.
복습하는 느낌으로 한번 더 잡고 가겠습니다.
포인터를 사용하는데에는 여러가지 장점이 있기 때문입니다. 그중 가장 큰 장점은 메모리 절약 이라고 생각됩니다.
큰 구조체 같은 경우 직접 전달 하는것 보다 포인터를 사용하는 것이 메모리 사용량을 크게 줄일 수 있을 것이고요.
두번째로 동적으로 메모리를 할당할 수 있는 점입니다.
동적으로 메모리 할당이 가능하다는 말이 어떤 것을 의미하는지 알아보죠.
포인터의 장점은 유연하다는 것입니다. 대상이 어떤 녀석이건 간에 원형(prototype)과 주소만 알고 있다면 그 역할을 수행하도록 할 수 있다는 점이 포인터의 정체성입니다.
이것을 활용해서 런타임중에 메모리를 할당할 수가 있는 것이고
함수포인터 에서는 분기문을 사용하게 되면 실행중에도 어떤 함수(알고리즘)을 선택할지 결정할 수가 있습니다.
콜백함수를 사용할 수도 있고요
함수를 인자로 전달할 수도 있게 됩니다.
객체에서 사용되는 함수 포인터는 어떤 형태를 가지고 있는지 보겠습니다.
헤더파일과 구현파일은 접어두기로 만들고
메인파일만 살펴 보겠습니다.
헤더파일 Point.h
#ifndef POINT_H
#define POINT_H
class Point
{
public:
//맴버 함수
void Print() const;
//생성자들
Point();
Point(int initialX, int initialY);
Point(const Point& pt);
//접근자
void SetX(int value);
void SetY(int value);
int GetX() const { return x; };
int GetY() const { return y; };
private:
//맴버 변수
int x, y;
};
inline void Point::SetX(int value)
{
if (value < 0)
x = 0;
else if (value > 100)
x = 100;
else
x = value;
}
inline void Point::SetY(int value)
{
if (value < 0)
y = 0;
else if (value > 100)
y = 100;
else
y = value;
}
#endif
구현 파일 Point.cpp
#include "Point.h"
#include<iostream>
using namespace std;
Point::Point(const Point& pt)
{
x = pt.x;
y = pt.y;
}
Point::Point(int initialX, int initialY)
{
SetX(initialX);
SetY(initialY);
}
Point::Point()
{
x = 0;
y = 0;
}
void Point::Print() const
{
cout << "( " << x << ", " << y << " )\n";
}
Main.cpp 파일
#include "Point.h"
//맴버함수가 아닌 경우 아래와 같이 함수 포인터를 선언하게 됩니다.
typedef void(*FP1)(int);
//맴버 함수의 경우 아래와 같이 함수 포인터를 선언합니다.
typedef void(Point::* FP2)(int);
int main()
{
//객체를 생성한다.
Point pt(50, 50);
//FP1, FP2를 사용해서 SetX() 함수를 가리킨다.
//FP1 fp1 = &Point::SetX;
//위와 같이 함수를 가리키도록 하면 에러가 발생한다.
FP2 fp2 = &Point::SetX;
//함수 포인터를 사용해서 함수 호출
(pt.*fp2)(100);
pt.Print(); //(100, 50)
//함수 포인터 사용하지 않고 SetX 호출
pt.SetX(70);
pt.Print();//(70, 50)
return 0;
}
C++11 부터 typedef 대신 using 키워드를 사용해서 함수 포인터에 using 선언을 할 수 있습니다.
using FP1 = void (*)(int);
using FP2 = void (Point::*)(int);
typedef 문을위와 같이 대체 할 수가 있습니다.
함수 포인터를 선언할 때 함수의 원형을 써주는데요
typedef void (*FP1)(int)
typedef void(Point::*FP2)(int);
선언부는 이렇습니다.
void(Point::*식별자)(int)
맴버 함수 포인터를 선언 하는 방법이 이렇습니다.
FP2 fp2 = &Point::SetX
(pt.*fp2)(100)
사용하는 문장이 이렇습니다.
C언어에서 함수 포인터 사용과 비교 해보겠습니다.
#include<cstdio>
int sum(int, int); // 함수 선언
int main()
{
int n1 = 1, n2 = 2;
int* ptr;
ptr = &n1;
*ptr = 50;
printf("n1 : %d\n", *ptr);
int (*fp)(int, int); //함수 포인터 선언
int res; //반환값을 저장할 변수
fp = sum; //함수명을 함수 포인터에 저장
res = fp(n1, n2); //함수 포인터로 함수 호출
printf("result : %d\n", res); //반환값 출력
return 0;
}
int sum(int a, int b) // 함수 정의
{
return (a + b);
}
예제 코드 입니다.
포인터 변수를 사용 할 때는 ptr=&n1과 같이 선언함으로서
*ptr이 n1의 역할을 하게 됩니다.
포인터 함수를 사용 할 때는
fp = sum;과 같이 선언하고
fp가 sum을 대체하게 됩니다.
함수의 이름 자체가 포인터이기 때문에 가능한거죠
예제 코드에서 FP2 fp2 = &Point::SetX 와 같이 쓰였으니 fp2 자체는 포인터 변수 입니다.
pt.fp2(100) 과 같은 형태로 사용하려면
그러니까 포인터 함수로 쓰려면 fp2 = Point::SetX 와 같이 선언되었어야만 합니다.
그런데 주소값이 들어가 있죠?
위C언어 예제에서 볼 수 있듯이
*ptr = 50;
포인터 변수에 ' * ' 를 붙임으로서 그 변수가 참조하고 있는 값에 접근할 수 있게 됩니다. 이를 역참조라고 하죠.
다르게 말하면 ptr 은 주소값인데 *ptr 이라고 쓰면 그 주소에 있는 본체에 접근할 수 있게 되는거죠?
함수 포인터도 같습니다.
fp2는 주소값이고, *fp2라고 쓰면 fp가 가리키는 본체에 접근하는 거죠.
함수 포인터에서는 그 본체가 곧 함수이며 함수 이름이고, 곧 포인터 이기도 합니다. (더럽게 헷갈립니다.)
(pt.*fp2)(100)
이것이 위 처럼 함수포인터 사용이 표현된 첫번 이유 입니다.
두번째 이유는 아래 사진에 나와 있습니다.
주소값을 사용하지 않고 함수 포인터 초기화 처럼
FP2 fp2 = Point::SetX;
와 같이 초기화를 했습니다.
그러면 fp2가 그자체로 Point::SetX 라는 함수 이름이 된거죠?
(pt.fp2)(100)
이렇게 함수를 실행했습니다만 컴파일러가 에러를 던집니다.
반드시 그 맴버일 때만 저런식으로 표기할 수 있다는 것이죠
객체 맴버가 아닌 함수 포인터다 라는걸 표현하기 위해 (pt.*fp2)(100) 와 같은 표현 방식을 택한 것입니다.
'C++문법 공부' 카테고리의 다른 글
객체 맴버로 열거체(enum)사용하기 (0) | 2023.06.24 |
---|---|
정적 맴버 함수를 사용해서 객체를 생성하기 (0) | 2023.06.22 |
생성자 맴버 초기화 리스트를 사용한 객체 맴버의 초기화 (0) | 2023.06.11 |
복사 생성자 (0) | 2023.06.09 |
인라인 함수( inline function) (0) | 2023.06.09 |