[UE5 TIL] Day 10 - C++ 학습/과제 작성

JungHoon Eum·2026년 3월 10일

오늘 배운 내용 요약

  • 포인터와 레퍼런스, nullptr
  • 클래스, 접근 제어자, 생성자/소멸자
  • 초기화 리스트와 기본 생성자 개념
  • 상속, 순수 가상 함수, 가상 소멸자
  • 함수 오버로딩과 템플릿
  • 전직 시스템 과제로 다형성 구조 연습

키워드

포인터 레퍼런스 nullptr 클래스 생성자 소멸자 초기화 리스트``가상 소멸자 함수 오버로딩 템플릿 자손 클래스 업캐스팅

기억해 둘 포인트

  • 스택, 힙 등 우리가 배우는 메모리는 우리가 다루는 메모리. 변수명, 자료형 등 컴파일러가 관리하는 메모리는 할당되는 메모리들과 별도
  • C++은 nulltpr
  • 레퍼런스는 선언과 동시에 초기화.
  • new - delete 는 같이.
  • 객체지향프로그래밍 은 태도, 목적, 패러다임 같은 것
  • private은 객체 기준이 아니라 클래스 기준이다.
  • 메모리 릭 검사: _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
  • public - protected - private
  • 생성자, 소멸자 안만들어도 default 가 있긴 하다.
  • 함수 오버로딩: 여러 동명의 함수 중에 변수들이 지들이 젤 맞는 곳에 알아서 들어간다
  • 초기화 리스트: Human():Height(0.0f), Age(0){} 식으로 생성자 만들자
  • template <typename T>
  • 부모 포인터로 자식 객체를 지울 때는 가상 소멸자가 필요하다.
  • 클래스 private 변수용 getter 함수 setter 함수 만들어 두기
  • 헤더, 소스코드 같이 수정하기
  • 자식 생성자에서 부모 생성자는 직접 넘겨줘야 한다.
    • Warrior::Warrior(std::string inputName) : Player(inputName)

전직 시스템 과제에서 기억할 것

  • Player* myPlayer = nullptr; 로 시작
  • while + switch 로 직업 선택 구현 가능
  • 잘못된 입력은 다시 입력받도록 처리
  • 자식 객체는 부모 포인터로 업캐스팅해서 저장
  • 사용 후에는 delete 하고 nullptr 처리
  • 공통 초기화는 생성자와 부모 클래스 쪽으로 묶는 게 깔끔하다

C++ 라이브 세션

포인터

  • 메모리 주소값 저장: 주소값만큼의 크기를 가진다.
    • Script* InPtrToScript 는 원본 자료형 크기와 관계없이 8바이트
  • 메모리와 연산의 관계에 대한 궁금증: 주소는 8바이트 - 선언할 때 스택메모리든 힙메모리든 거기에 저장. 크기는 char, int 등 선언 할 때 자료형으로 -> 이 자료형을 "기억" 하나? 데이터 관리가 어케 되는 걸까
    • 추가적인 질문으로 연관되어 있을 느낌이 드는데, 각 변수들이나 함수들의 "이름" 은 어케 저장함? 이것도 메모리 크기가 할당이 되나?
    • 튜터님 답변: 컴파일러가 관리하는 별도의 매핑 테이블이 있음. 여기서 하드디스크->RAM에 캐싱해서 저장. 우리가 배우는 스택 메모리-힙 메모리는 사용자들이 조작해야 할 메모리 범위에 대해서만 배우고 있는 거기 때문에 이에 관한 내용이 없던 것
  • 참조 int& RefToA = A; -> 선언과 동시에 초기화 필수
    • 주소가 아니라 "별명" 같은 거라 Ptr+1로 주소가 옮겨지는 참사같은게 일어나지 않는다: 안전
    • NULL 문제:
// Human.h
bool IsFriend(const char* InName);
bool IsFriend(int InID);

// Main.cpp
bool IsFriend = Human01->IsFriend(NULL); 
	//여기서 int InID가 호출
    //why? NULL은 정수0을 반환
  • nullptr: C++에서 쓰는 정수로 변환되지 않는 포인터 상수

생성자와 소멸자

객체 지향 프로그래밍

  • 지향점, 목적, 태도, 사고방식...
  • 그냥 이런 Frame으로 문제를 풀어나가자 라고 생각하면 된다.
  • 객체지향"언어" 에도 절차지향적인 면모가 있고, C언어도 그런 방식으로 구현이 어느 정도는 가능.

클래스

// Main.cpp

class 클래스명
{
public:
	멤버변수자료형 멤버변수명;
	//이 객체, 물체의 "속성"
	반환자료형 멤버함수명(매개변수자료형 매개변수명) {}
    //이 객체, 물체가 해야 하는 "행동"
};

int main(void)
{
	클래스명 객체이름;
	return 0;
}
  • Human Kim;: 스택 메모리에 생성
  • Human* Kim = new Human() : 동적 할당. 힙 메모리
    • delete Kim; 필수!! =nullptr; 까지
  • 메모리 릭 검사
    • _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

접근 제어자

  • public: 어디서나 접근 가능
  • protected: 멤버 함수, 자식 클래스에서 접근 가능
  • private: 멤버 함수에서만 접근 가능
  • C++ 기준 구조체 기본 접근 제어자는 public
    클래스 기본 접근 제어자는 private
  • 클래스 기준 임에 주의!!
    • 같은 클래스의 다른 객체더라도, private 멤버 변수에 접근 가능

생성자

  • 객체가 만들어질 때 자동으로 호출되는 함수
  • 직접 호출하면 명시적 호출, 컴파일러가 호출하면 암시적 호출
class 클래스명
{
public:
	클래스명(){생성하면서할내용}; // 이게 생성자
    ~클래스명(){}; //이건 소멸자
}
int main()
{
	Human Human01 = Human();      
	// 생성자를 명시적으로 호출해서 객체를 만들고, 그걸 다시 Human01에 대입.
	Human* Human02 = new Human();
	// 생성자의 명시적 호출.

	Human Human03;
	// 생성자의 암시적 호출.
	Human* Human04;
	// 포인터라 선언은 해드림. dangling pointer

return 0;
}
  • 클래스 생성할 때 멤버변수로 다른 클래스 객체 만들면 그 클래스도 생성됨

Initializer List

  • 생성자에 들어가는 초기화 내용들
    Human() : Height(0.0f), Weight(0.0f){} 식임
  • 이건 객체 생성과 동시에 초기화 해주는 목록들
  • 멤버변수 쪽에 선언-초기화 식 쓰면 (float Height = 0.0f;) 생성 이후 초기화
    -const 나 레퍼런스 등 선언과 초기화가 동시에 이뤄줘야 하는 변수들의 경우 이니셜라이저 필수

기본 생성자, 생성자 오버로딩

  • 기본 생성자 - 클래스 MC 만들고 main 함수에 MC MyClass; 하면 왜 생성됨? 생성자 클래스 내에 안만들어놨는데?
    • 기본 생성자를 컴파일러가 알아서 만들어 주기 떄문. class 안에 Human(){}; 생성자가 들어가 있는 꼴

C++ 온라인 강의

1주차 과제

  • 자손 클래스 생성: class Samsung: public Phone{};
  • 가상 소멸자는 virtual ~phone(){}: 포인터로 자손 클래스 받아올 때도 제대로 소멸자 발동하려면 이렇게 쓰는 게 맞다.
  • 배열-포인터-자손 클래스 통합 버젼
    Phone* arrayPhones [3] = { new Samsung, new Apple, new Xiaomi };
    • 암시적 호출을 썼음에 유의. new Samsung()이 아닌.
  • 다시 복습: 순수가상함수는 virtual void displayBrand() = 0;

템플릿

  • 다양한 자료형에 유연하게 대응하는 일반화된 코드 생성 가능
    • 정수를 더한다int/ 실수를 더한다 float 라는 개념을 하나로 합쳐서 관리할 수 있게 됨

함수 오버로딩 개념

  • C언어는 함수 이름으로 함수 구분, C++은 "반환타입, 이름 인자"를 사용해 구분
    • 정확히는 고유한 이름을 생성해 주는 것: 네임 맹글링
    • C++ 에선 동일한 이름의 함수 여러개 구현 가능 = 함수 오버로딩
  • print(int a){} 함수와 print(float a){} 함수 동시 구현
    • 들어가는 값에 따라 지가 알아서 계산해 줌
  • add(int a, int b){} 와
    add(int a, int b, int c){}
    • 인자 갯수 따라서 알아서 더해줌

오버로딩이 되지 않는 경우

  1. 타입 변환이 가능한 매개변수로 인해 두 개 이상의 오버로딩된 함수가 호출 후보가 되는 경우
    • print(10) 한다 쳤을 때 함수 매개변수들이 float long 이다? int는 둘 다 변환 가능
  2. 디폴트 매개변수로 인해 함수 호출 형태가 중복되는 경우
    • void display(int a, int b=5): b 는 디폴트 매개변수. b 지정해서 안 넘겨주면 인자 하나짜리 함수랑 똑같이 작동함. 그래서 display(int a)랑 중복됨.
  3. 매개변수의 타입만 포인터와 배열로 다른 경우
    • void print(int* arr), void print(int arr[])
      배열 이름은 첫 주소값을 가지고 있음 -> 포인터와 같은 타입으로 취급되어 애매모호
  4. 함수의 반환 타입만 다른 경우
    • int getValue(), double getValue() 이런 경우에도 안됨

함수 오버로딩의 순서

  1. 정확한 매개변수 타입 일치
    • print(10) -> void print(int a) over void print(double a)
  2. 타입 승격 변화
    • 값이 손실되지 않는 방향으로 변환
    • char, short -> int
    • float -> double
    • bool -> int
  3. 표준 타입 변환
    • 값 손실 있더라도 변환 가능한 경우들
    • int -> double
    • double -> int
    • double -> float
      -print(double a) 는 뭘 선택할까?
      void print int >>> void print string
  4. 사용자 타입 변환
    • 알필요없다.

템플릿

  • template <typename T> 맨 위에 추가: 어떤 타입이 올 지 모르겠으나, 그 자료형을 T 라고 부르겠다.
    함수 내부에서 자료형이 올 공간에 T 사용하면 됨.
  • 오버로딩할 함수를 일일히 구현하는 것을 대체.
template <typename T>
T add(T a, T b) {
    return a + b;
}
  • T getMax() 함수로 (a > b)? a : b; 만들어서 string 비교도 가능. 사전순으로 더 뒤쪽의 값을 반환
template <typename T>
void printArray(T arr[], int size) {
    for (int i = 0; i < size; i++)
        cout << arr[i] << " ";
    cout << endl;
}
  • size는 무조건 int니까 템플릿으로 넣을 이유가 없다.
  • printArray(intArr, 3); 식으로 프린트 가능
template <typename T>
void swapValues(T &a, T &b) {
    T temp = a;
    a = b;
    b = temp;
}
  • 이런 식으로 레퍼런스도 받을 수 있다

템플릿 클래스

  • 배열에 원소 추가/삭제/출력하는 클래스 만든다 치자.
  • 해당 배열이 어떤 원소도 받을 수 있도록 템플릿 설정 가능

과제 2: 전직 시스템

  • 추상 클래스 Player 와 직업 4개 Warrior, Magician, Thief, Archer 구현하고
    Player* myPlayer = nullptr; 로 업캐스팅.
  • while-switch로 무한루프 구현
while (selectPending)
{
	switch (selectMenu)
	{
	case 1:
		myPlayer = new Warrior(nickname);
		selectPending = false;
		break;
......
	default:
		std::cout << "Invalid input. Pick a number from 1 to 4." << std::endl;
		std::cin >> selectMenu;
		break;
	}
}
  • 포인터 삭제는
if (myPlayer != nullptr)
{
	delete myPlayer;
	myPlayer = nullptr;
}
  • 게임개발이라서 그런 거일수도 있지만, 복붙이 엄청 많은 느낌...과정을 단순하게 처리할 수 있었으면
  • Player 스탯 같은 초기화는 엔간하면 생성자 내부 {} 안이 아니라 이니셜라이저로
    • Player::Player(std::string inputName): nickname(inputName), level(1), HP(100),MP(100),power(10),defense(10),accuracy(10),speed(10)
  • getter 함수와 setter 함수는 기능적으로 있으면 나쁠 게 없는 함수들이 만들어 놓긴 했다
반환자료형 get변수명(){
return 변수명;
}
void set변수명(매개변수자료형 a){
변수명=a;
return;
}
  • 새로운 함수 추가는 헤더 .h 와 소스코드 .cpp 둘 다 해야 하는 걸 잊지 말자.
    • 헤더에는 void printPlayerStatus();
      소스코드에는 void printPlayerStatus(){}
  • 자손 클래스에서도 input으로 받는 건 표시를 해줘야 한다. 부모랑 똑같이 std::string inputname을 받더라도
    Warrior::Warrior(std::string inputName) : Player(inputName) 식으로 넘겨줘야 한다
  • jobName 플레이어스테이터스에 표시해 주는 함수도 멤버 함수로 만들어서 생성자에 포함시켜 player.jobName 초기화 시켜줌.
profile
개발지망생

0개의 댓글