2026-02-27 TIL (5일차)
Unreal 강의
- 상태 머신 설계하는 방법 ● 오브젝트가 가질 수 있는 모든 상태 정리 (대기,걷기,달리기 등등) ● 상태1 -> 상태2 처럼 현재 상태에서 다른 상태로 변경 될 때마다 모든 조건 정리
C++ 공부
- 함수 포인터
● 변수 포인터는 메모리 절약과 효율인데 함수 포인터는 결정의 유연성(코드의 설계) 을 위한 것입니다.
구현되는 원리는 포인터랑 같다!
포인터라는 상자 안에 ‘주소’를 담는다는 점은 변하지 않아요. 다만, 그 주소가 데이터를 가리키느냐, 함수를 가리키느냐의 차이일 뿐 차이점: 가리키는 공간이 다름.
( 변수 포인터 - 스택,힙,데이터 , 함수 포인터 - 코드[ Code / Text ] 영역)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
int foo() { return 5; } int goo() { return 6; } int main() { int (*fcnPtr)() = foo; // fcnPtr points to function foo fcnPtr = goo; // fcnPtr now points to function goo return 0; }
C++는 기본 자료형과 달리 필요할 경우 함수를 함수 포인터로 암묵적으로 변환하므로 주소 연산자 &를 사용할 필요가 없다.
이유: 배열이랑 비슷한 개념이다.
●함수는 코드 영역에 저장된 명령어들의 집합 이기 때문에 함수이름이 시작 주소(진입점 주소)이다.
★함수는 시작점부터 실행해야 하는 존재다. 그래서 이름 자체가 곧 시작 주소로 통하도록 설계되었다!활용 예시
● 함수 누구를 부를지를 실행 중에 결정하기 위해서 (런타임 결정)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
void Attack() { std::cout << "기본 공격" << std::endl; } void Defense() { std::cout << "기본 방어" << std::endl; } int main() { void (*ptr)() = nullptr; int input; std::cin >> input; if (input == 1) { ptr = Attack; } else if (input == 2) { ptr = Defense; } if (ptr != nullptr) //만약 이 조건문이 없으면 input값이 1,2가 아니면 nullptr에 있는 함수를 실행을 시켜서 오류가 생김 { ptr(); } }
● 함수를 부품처럼 갈아 끼우기 (전략패턴)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
bool Better(int a, int b) { return a > b; } bool Worese(int a, int b) { return a < b; } void Sort(int* arr, int size, bool (*compare)(int, int)) //배열을 포인터로 받고 , 0부터 size-1만큼 정렬한다, 무슨 함수를 포인터로 받고 넘길꺼냐? 이러면 bool 함수+매개변수 2개인 함수는 다 가능 { for (int i = 0; i < size - 1; i++) { for (int j = i + 1; j < size; j++) { if (compare(arr[i], arr[j]))//저장되어있는 함수의 주소로가서 실행함 { std::swap(arr[i], arr[j]); } } } }
● 콜백, Callback
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
void OnCompleteNotify() { std::cout << "팝업" << std::endl; } void OnCompleteLogin() { std::cout << "로그인" << std::endl; } void DownloadFile(void (*callback)()) { std::cout << "다운로드 중..." << std::endl; callback(); //"아까 할당 받은 주소로 다시 가 std::cout << "다운로드 중2..." << std::endl; } int main() { DownloadFile(OnCompleteNotify); //OnCompleteNotify라는 이름(주소)을 복사해서 callback이라는 매개변수에 저장 //DownloadFile을 실행 시키는데 callback 실행 -> 안에는 OnCompleteNotify저장되어있음 // OnCompleteNotify가 끝나면 **다시 DownloadFile** 로 돌아온다. (당연히 자신위치 뒤 부터 실행시킨다.) }
- Dangling Pointer
★ 하기전에 알면 좋은 지식 동적할당
● int *pi = new int; ->여기서 포인터는 주소를 받아야하는데 new int 어디에 주소가 있는거야?
new라는 연산자 자체가 메모리를 빌린 뒤, 그 시작 주소를 결과값으로 돌려주는 기능
(쉽게 설명하면 함수 포인터, 배열 처럼 이름 자체로만 쓰면 시작 주소를 준다는 의미, 즉 할당받을 시작주소를 이미 들고있다는거임)
● 배열을 동적할당 받으면 delete[]로 빌린 size만큼 삭제시키던데 2차원배열도 그냥 delete[]] 이렇게 하면 안돼?
한 줄이 몇 칸인지 몰라서 한 줄을 뛰어넘을 때 얼마나 지워야하는지 몰라서 안된다.
그래서 2차원 배열 기준은 한줄 자체를 지우고 또 다시 한줄 지우는 형태로 해야한다.정의: 이미 해제된 메모리 주소를 여전히 가리키고 있는 포인터를 말합니다. 간단하게 설명하면 값이 존재하지 않는 주소에 가겠다는 뜻입니다.
- 메모리 해제 후 포인터를 초기화하지 않았을 때. 특히 동적 할당한 경우
- 지역 변수의 주소를 반환했을 때
클래스
●클래스와 구조체의 차이 정보은닉인 캡슐화의 차이
(클래스는 접근 제어 지시자에 따라 자기 내부 변수를 못건들게 한다. 대표적으로 hp를 외부에서 직접 못건들게하고 함수를 통해서만 접근해 상호작용하게 한다.)
(private를 외부에서 접근하는 방법: 생성자로 접근, public에 Set 함수를 구현해서 값을 넣는다, friend 키워드 사용)● 정보은닉, 캘슐화 하는 이유
잘못된 값을 저장되지 않도록 하기 위해서 사용합니다.● 클래스를 동적할당을 받는 이유
- 생명주기가 길어 할 때 사용 -> 일반적인 변수로 선언하면 함수 구문이 끝나면 변수가 사라지기 때문입니다. (캐릭터,몬스터 등등)
- 다형성을 구현 할 때 (부모인 몬스터 클래스 하나로 여러 몬스터들을 할당받아 관리도 가능하기 때문에)
● const 함수의 위치에 따른 역할차이
위치 명칭 사용 가능 범위 주요 목적 앞 (const int* F()) 반환 타입 상수화 모든 함수 돌려받은 값을 수정하지 못하게 함 끝 (void F() const) 멤버 함수 상수화 클래스 멤버 함수만 함수 안에서 멤버 변수를 수정하지 못하게 함 ● 생성자를 구현할 때 이니셜라이저 vs 대입의 차이
이니셜라이저: 객체가 메모리에 생성되는 순간에 값을 채워 넣습니다.
대입: 객체가 메모리에 이미 생성된 후(기본값으로), 그 안에 있던 값을 지우고 새 값으로 덮어씁니다.
★ 이니셜라이저가 성능에 약간의 이점이 있다.- inline 함수
inline 함수는 배열이나 함수 포인터를 타고 멀리 점프하지 말고, 그 자리에 코드를 복사해서 붙여넣어라고 컴파일러에게 부탁하는 명령어
추천: 1~3줄 정도의 아주 짧은 함수 (예: Getter/Setter, 단순 계산 함수).
C/C++ 강의 (02-27일 강의 전방선언부터 중요한 내용)
- 전방선언
일단 이 함수 있으니까 여기서 없다고 징징거리지마라 라고 미리 알려주는 느낌
원형을 그대로 적어준다 - 타입 이름 (매개변수)