C++
[강의] 12월 18일 수업정리
k-codestudy
2024. 12. 18. 17:40
오늘은 함수 포인터에 대한 수업을 들었다.
1. 함수 포인터
1.1 함수 포인터의 기본
C언어
#include <iostream>
void function();
int main()
{
void (*pFunc)() {};
pFunc = function;
pFunc();
}
void function()
{
printf("테스트\n");
}
C++
#include <iostream>
void function();
int main()
{
void (*pFunc)() {};
pFunc = &function;
(*pFunc)();
}
void function()
{
printf("테스트\n");
}
- void (*pFunc)() {}
- 함수 포인터 선언 시 함수 원형에 따라 작성해야 하며, ()를 포함해야 한다. - pFunc = &function / (*pFunc)()
- 함수의 주소를 할당할 때 &function을 사용하는 것이 FM이다.
- 클래스 함수 포인터에서는 이러한 FM방식을 사용하는 것이 중요하다.
1.2 클래스 함수 포인텅 ( 외부 )
// data.h
#pragma once
class C_DATA
{
private:
int m_nData;
public:
C_DATA() = default;
void setData(int nData);
int getData();
};
// data.cpp
#include "data.h"
void C_DATA::setData(int nData)
{
m_nData = nData;
}
int C_DATA::getData()
{
return m_nData;
}
#include <iostream>
#include "data.h"
int main()
{
C_DATA c1{};
C_DATA c2{};
void (C_DATA:: * pFunc)(int) {};
pFunc = &C_DATA::setData;
(c1.*pFunc)(100);
printf("%d\n", c1.getData());
}
- void (C_DATA::*pFunc)(int){}
- 클래스 멤버 함수 포인터 선언 시 C_DATA::를 명시해야 한다.
- 이를 통해 함수가 C_DATA 클래스 소속임을 알린다. - pFunc = &C_DATA::setData
- 클래스 맴버 함수의 주소를 저장하지만, 객체가 명시되지 않아 호출이 불가능하다. - (c1.*pFunc)(100);
- 객체 c1을 통해 호출해야 맴버 함수 실행이 가능하다.
- 여기서 C_DATA:: 객체 대신 객체(c1, c2)를 사용해 호출 대상이 명확해진다.
- 이런식의 방식떄문에 외부에서는 클래스 함수 포인터를 잘 사용하지 않지만 내부에서는 사용하는 경우가 존재한다.
2. 함수 포인터를 이용하여 계산기 프로그램 만들기
#pragma once
#include <stdio.h>
class C_CALCULATION
{
public:
enum class E_TYPE
{
E_ADD,
E_MUL,
E_DIV,
E_MAX
};
private:
int m_nData1;
int m_nData2;
private:
int add();
int mul();
int div();
public:
C_CALCULATION() = default;
void setData(int nData1, int nData2);
int result(E_TYPE eType);
};
- enum class
- 연산 종류를 E_TYPE 열거형으로 정의하여, 고유 ID를 부여.
- E_TYPE::E_ADD, E_TYPE::E_MUL, E_TYPE::E_DIV로 각 연산을 구분. - result 함수
- 멤버 함수 포인터 배열과 this 포인터를 사용해 동적으로 멤버 함수를 호출.
====
#include "calculation.h"
int C_CALCULATION::add()
{
return m_nData1 + m_nData2;
}
int C_CALCULATION::mul()
{
return m_nData1 * m_nData2;
}
int C_CALCULATION::div()
{
return m_nData1 / m_nData2;
}
void C_CALCULATION::setData(int nData1, int nData2)
{
m_nData1 = nData1;
m_nData2 = nData2;
}
int C_CALCULATION::result(E_TYPE eType)
{
int nResult{};
switch (eType)
{
case C_CALCULATION::E_TYPE::E_ADD:
nResult = m_nData1 + m_nData2;
break;
case C_CALCULATION::E_TYPE::E_MUL:
nResult = m_nData1 * m_nData2;
break;
case C_CALCULATION::E_TYPE::E_DIV:
nResult = m_nData1 / m_nData2;
break;
default:
break;
}
return nResult;
}
- add, mul, div
- 각 연산에 해당하는 멤버 함수. m_nData1과 m_nData2를 사용해 계산 후 결과를 반환. - setData
- 두 피연산자 값을 설정하는 함수.
- 이 값들은 이후 연산 함수에서 사용됨. - result
- 연산 타입(E_TYPE)에 따라 적절한 연산을 수행하고 결과를 반환.
- switch-case 구조를 사용하여 연산 타입에 맞는 멤버 함수를 호출. - 코드 개선 사항
- result 함수에서 switch-case 문에서 멤버 함수를 직접 호출(add(), mul(), div())하여 코드 중복을 제거.
- 각 연산은 개별 함수로 구현되어, 유지보수성과 가독성을 높임.
#include <iostream>
#include "calculation.h"
int main()
{
C_CALCULATION cCalculation{};
cCalculation.setData(20, 10);
printf("%d\n", cCalculation.result(C_CALCULATION::E_TYPE::E_ADD));
printf("%d\n", cCalculation.result(C_CALCULATION::E_TYPE::E_DIV));
printf("%d\n", cCalculation.result(C_CALCULATION::E_TYPE::E_MUL));
}
- 함수 포인터와 클래스 멤버 함수 포인터를 사용해 switch-case를 제거한 코드
// calculation.h
#pragma once
#include <stdio.h>
class C_CALCULATION
{
public:
enum class E_TYPE
{
E_ADD,
E_MUL,
E_DIV,
E_MAX
};
private:
int m_nData1;
int m_nData2;
int (C_CALCULATION::* m_arCalculation[(int)E_TYPE::E_MAX])();
private:
int add();
int mul();
int div();
public:
C_CALCULATION() = default;
void init();
void setData(int nData1, int nData2);
int result(E_TYPE eType);
};
// calculation.cpp
#include "calculation.h"
int C_CALCULATION::add()
{
return m_nData1 + m_nData2;
}
int C_CALCULATION::mul()
{
return m_nData1 * m_nData2;
}
int C_CALCULATION::div()
{
return m_nData1 / m_nData2;
}
void C_CALCULATION::init()
{
m_arCalculation[(int)E_TYPE::E_ADD] = &C_CALCULATION::add;
m_arCalculation[(int)E_TYPE::E_DIV] = &C_CALCULATION::div;
m_arCalculation[(int)E_TYPE::E_MUL] = &C_CALCULATION::mul;
}
void C_CALCULATION::setData(int nData1, int nData2)
{
m_nData1 = nData1;
m_nData2 = nData2;
}
int C_CALCULATION::result(E_TYPE eType)
{
// 정해져 있는 경우 이게 편함
return (this->*m_arCalculation[(int)eType])();
// 내꺼 부를때 문법, 네임스페이스를 쓰는게 아닌 this-> 참조를 하여 내꺼라고 알려줌 / -> * 이부분 이렇게 띄워쓰면 에러남 / 원래 this 사용하지 말라했는데 어쩔수없이 쓰는 부분
}
// main.cpp
#include <iostream>
#include "calculation.h"
int main()
{
C_CALCULATION cCalculation{};
cCalculation.init();
cCalculation.setData(20, 10);
printf("%d\n", cCalculation.result(C_CALCULATION::E_TYPE::E_ADD));
printf("%d\n", cCalculation.result(C_CALCULATION::E_TYPE::E_DIV));
printf("%d\n", cCalculation.result(C_CALCULATION::E_TYPE::E_MUL));
}
// Calculation.h
- 멤버 함수 포인터 배열
- int (C_CALCULATION::*m_arCalculation[(int)E_TYPE::E_MAX])()은 클래스 멤버 함수 포인터를 담는 배열.
- 각 포인터는 add, mul, div와 같은 연산 멤버 함수를 가리킴. - init 함수
- 함수 포인터 배열을 초기화하여 연산 종류와 멤버 함수를 연결.
// Calculation.cpp
- init
-연산 종류(E_TYPE)에 따라 해당 멤버 함수의 주소를 m_arCalculation 배열에 저장.
- E_TYPE::E_ADD → &C_CALCULATION::add와 같은 방식으로 초기화. - result
- this 포인터와 멤버 함수 포인터를 사용해 배열에 저장된 함수를 동적으로 호출.
- (this->*m_arCalculation[(int)eType])()는 멤버 함수 포인터를 호출하는 정확한 문법.
멤버 함수 포인터 호출 문법
return (this->*m_arCalculation[(int)eType])();
- this->*
- 멤버 함수 포인터 호출 시 반드시 필요.
- this는 현재 객체를 가리키며, this->*는 멤버 함수 포인터와 연결. - 멤버 함수 포인터 배열
- m_arCalculation[(int)eType]은 E_TYPE에 대응하는 멤버 함수의 포인터.
3. 야구 게임 (숫자 4개 , 랜덤 함수만)
#include <random>
#include <iostream>
void swap(int& nDst, int& nSrc);
int main()
{
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dist(0, 9);
int arData[10]{ 0,1,2,3,4,5,6,7,8,9 };
for (int i = 0; i < 20; ++i)
{
swap(arData[dist(gen)], arData[dist(gen)]);
}
for (int i = 0; i < 4; i++)
{
printf("%d ", arData[i]);
}
printf("\n");
}
void swap(int& nDst, int& nSrc)
{
int nTmp = nDst;
nDst = nSrc;
nSrc = nTmp;
}