STL의 공부하기 위헤서 필수적으로 알고 있어야한다.
함수 포인터
포인터는 주소를 담는 바구니 이다. int* pointer = &a; 포인터라는 것을 알려주기 위해 * 를 사용하고 변수 이름은 pointer 이다. 데이터 타입은 int인 것이다.
지금까지 우리가 사용한 데이터 타입은 기본 타입 들과 우리가 제공한 class 나 struct 등을 사용했지만 이것 이외에도
바로 함수 타입이다
#include <iostream>
using namespace std;
int Add(int a, int b)
{
return a + b;
}
int main()
{
// 함수 포인터
// int 2개를 받아 int 하나를 반환하는 시그니처 함수
typedef int(FUNC_TYPE)(int, int);
// 1) 포인터 *
// 2) 변수 이름 fn
// 3) 데이터 타입 함수 (인자는 int, int , 반환은 int)
FUNC_TYPE* fn;
// Add 함수를 호출해라 => 함수의 주소 위치로 jmp 해라!
// 즉 함수라는 것도 별거 없이 그냥 함수가 정의되어 있는 위치가 있고
// 그 위치로 코드를 이동시켜 실행해라 라는것.
// 함수의 이름은 함수의 시작 주소를 가짐 (배열과 유사)
int result = Add(1, 2);
// 시그니처가 동일한 함수를 받을 수 있다.
// 이게 함수 포인터이다.
fn = Add;
int result2 = fn(1, 2);
cout << result2 << endl;
// 함수 포인터는 * 붙여도 함수 주소!
int result3 = (*fn)(1, 2);
cout << result3 << endl;
return 0;
}
그렇다면 의문이 든다. 왜 굳이 한번 더 걸쳐서 호출을 해야할까??
만약 시그니처가 비슷한 함수 Sub이 있다고 해보자.
#include <iostream>
using namespace std;
int Add(int a, int b)
{
return a + b;
}
int Sub(int a, int b)
{
return a - b;
}
int main()
{
typedef int(FUNC_TYPE)(int, int);
FUNC_TYPE* fn;
// 시그니처가 동일한 함수를 받을 수 있다.
// Add를 사용하는 함수 전부 다 Sub로 한번에 바꿀 수 있다.
fn = Sub;
int result2 = fn(1, 2);
cout << result2 << endl;
// 함수 포인터는 * 붙여도 함수 주소!
int result3 = (*fn)(1, 2);
cout << result3 << endl;
return 0;
}
이렇게 Add를 사용하는 함수를 sub로 한번에 수정이 가능하다. 물론 이것만 보면 에이 그건 수정하면 되지. 그 전 노력이 더 귀찮게 하는거 같은데? 라는 생각이 든다.
더 직관적으로 사용하는 예시를 생각해보자.
Item 이라는 클래스가 있고 여러 데이터를 들고 있다고 해보자.
그리고 아이템을 찾는 함수가 있다고 해보자.
class Item
{
public:
Item() : _itemId(0), _rarity(0), _ownerId(0)
{
}
public:
int _itemId;
int _rarity;
int _ownerId;
};
// 아이템 찾기
Item* FindItem(Item items[], int itemCount)
{
for (int i = 0; i < itemCount; i++)
{
Item* item = &items[i];
// TODO 조건
//
}
}
우리가 회귀도에 따라서 아이템을 찾고 싶다면 저 조건에 희귀도를 체크할 것이다. 그런데 만약 id로 찾고 싶다면?? 주인의 id로 찾고 싶다면?? FindItemByItemId 함수를 하나 더 만들어야할 것이다. 계속 복붙 복붙을 하게 될것이다. 즉 코드가 되게 길어지고 공통적인 부분이 늘어나게 된다.
심지어 코드를 한군데에 추가하면 다른 함수도 추가해 줘야하는 귀찮은 작업을 해야한다.
그걸 해결하기 위해 매개변수로 어떤 동작을 넣어주면 되지 않을까 라는 생각이 된다.
#include <iostream>
using namespace std;
class Item
{
public:
Item() : _itemId(0), _rarity(0), _ownerId(0)
{
}
public:
int _itemId;
int _rarity;
int _ownerId;
};
// 아이템 찾기
Item* FindItem(Item items[], int itemCount, bool(*selector)(Item* item))
{
for (int i = 0; i < itemCount; i++)
{
Item* item = &items[i];
// TODO 조건
if (selector(item))
{
return item;
}
}
}
bool IsRareItem(Item* item)
{
return item->_rarity >= 2;
}
int main()
{
Item items[10] = {};
items[3]._rarity = 3;
Item* raraitem = FindItem(items, 10, IsRareItem);
return 0;
}
이런 식으로 Rare 뿐만 아니라 특정 함수(행동)를 매개변수로 넣어서 작동 할 수있다. 물론 이 문법 자체는 엄청 좋다고는 할 수 없다.
이것을 한방에 처리할 수 있는 문법을 나중에 배울 것이다.
함수 포인터 심화
우리가 위에서 다룬 함수 포인터는 반쪽짜리에 불과하다. 위 문법으로 [전역변수/ 정적함수] 만 담을 수 있다.(호출 규약이 동일한 애들)
#include <iostream>
using namespace std;
class Knight
{
public:
int GetHp()
{
cout << "GetHp()" << endl;
return _hp;
}
private:
int _hp = 100;
};
int main()
{
int(*fn)() = GetHp;
return 0;
}
아무리 해도 GetHp를 담는 곳에서 컴파일 에러가 나는 모습을 볼 수 있다. GetHp는 멤버함수이기 때문이다. GetHp는 멤버함수 이기 때문에 특정 객체를 넘겨주는 규약이 존재한다.
따라서 이렇게 구현이 가능하다.
int main()
{
Knight k1;
int(Knight::*fn)() = &Knight::GetHp;
(k1.*fn)();
Knight* k2 = new Knight();
(k2->*fn)();
return 0;
}
지금은 무슨 얘기인지 잘 몰라도 나중에 STL 등에서 나오기 때문에 알아만 두자.
나중 C++ 11 에서도 using으로 등장하니까 알아두자!
'C++ > 기초' 카테고리의 다른 글
[C++] STL - Vector (1) | 2023.12.04 |
---|---|
[C++] 함수 객체, 템플릿, 콜백 함수 (0) | 2023.12.01 |
[C++] C++의 위험성과 메모리 관리의 위험성 (3) | 2023.11.29 |
[C++] 전방 선언 (0) | 2023.11.29 |
[C++] 복사, cast (0) | 2023.11.28 |