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());

}
  1. void (C_DATA::*pFunc)(int){}
    - 클래스 멤버 함수 포인터 선언 시 C_DATA::를 명시해야 한다.
    - 이를 통해 함수가 C_DATA 클래스 소속임을 알린다.
  2. pFunc = &C_DATA::setData
    - 클래스 맴버 함수의 주소를 저장하지만, 객체가 명시되지 않아 호출이 불가능하다.

  3. (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;
}