Unreal 개발 본 캠프 11일차

HappyCircle·2025년 12월 11일

Unreal 개발

목록 보기
28/163

오늘 학습 진행 내용
라이브 세션
알고리즘 강의

게임 개발자를 위한 C++ 문법
챕터2-1.자원 관리하기
스택 메모리

  • 일반 변수들은 대부분 스택 메모리 공간을 차지
  • 스택 메모리의 가장 큰 특징은 변수의 생존 주기가 끝나면 선언 시 할당되었던 메모리가 자동으로 히수
  • 변수의 생존 주기는 서언된 라인을 기준으로 가장 가까운 마침 괄호 "}"

스택 메모리 예시 코드

#include <iostream>
using namespace std;

void func2() {
    int b = 20;  // 지역 변수 'b', stack 메모리에서 관리됨
    cout << "func2: b = " << b << endl;
}
void func1() {
    int a = 10;  // 지역 변수 'a', stack 메모리에서 관리됨
    cout << "func1: a = " << a << endl;
    fun2(); //fun2() 호출
}  // func1()이 종료되면 'a'는 소멸, fun2() 종료 후 'b' 도 소멸

void func3() {
    for (int i = 0; i < 3; ++i) {  // 반복문 안에서 지역 변수 'i'가 매번 새로 생성됨
        int temp = i * 10;  // 반복문 안에서만 유효한 'temp'
        cout << "Iteration " << i << ": temp = " << temp << endl;
    }  // 반복문 끝날 때마다 'temp'는 소멸됨
}

int main() {
    func1();  // func1() 호출
    // 'a'는 func1() 호출 중에만 존재하고, 함수 종료 후 소멸됨
    fun3(); //fun3() 호출 
    return 0;
}

힙(동적) 메모리

  • 스택 메모리는 크기가 제한적이라는 단점 존재
  • 동적 할당 시 new 연산자를 사용, 해제 시 delete 연산자 사용
  • 스택과 달리 자동 해제 X, 메모리 누수 위험성 존재
  • 동적 할당된 객체의 생존 주기는 사용자가 delete로 해제할 때까지 유지

힙(동적) 메모리 예시 코드

#include <iostream>
using namespace std;
//변수 하나의 영역에 대한 동적 할당
void func1() {
    int* ptr = new int(10);  // 힙 메모리에 정수 10 할당
    cout << "Value: " << *ptr << endl;
    delete ptr;             // 메모리 해제
}
//배열의 동적 할당과 해제
void func2() {
    int* arr = new int[5];  // 힙 메모리에 정수 배열 5개 할당
    for (int i = 0; i < 5; ++i) {
        arr[i] = i * 10;
        cout << "arr[" << i << "] = " << arr[i] << endl;
    }
    delete[] arr;  // 배열 메모리 해제
}

//메모리 해지를 하지 않은 예시
void func3() {
    int* ptr = new int(20);  // 힙 메모리에 정수 20 할당
    cout << "Value: " << *ptr << endl;
    // 메모리 해제를 하지 않음
}

//사용자 입력 기반 배열 동적 할당
void createDynamicArray() {
    int size;
    cout << "Enter the size of the array: ";
    cin >> size;  // 배열 크기를 사용자로부터 입력받음

    if (size > 0) {
        int* arr = new int[size];  // 입력받은 크기만큼 동적 배열 생성
        for (int i = 0; i < size; ++i) {
            arr[i] = i * 2;  // 배열 초기화
            cout << "arr[" << i << "] = " << arr[i] << endl;
        }
        delete[] arr;  // 동적으로 할당한 배열 메모리 해제
    } else {
        cout << "Invalid size!" << endl;
    }
}

int main() {
    func1();
    fun2();
    func3();
    createDynamicArray();
    return 0;
}

Dangling Pointer
이미 해제된 메모리의 주소를 계속 가지고 있는 포인터를 지칭

Daling Pointer 예시 코드

#include <iostream>
using namespace std;

//이중 해제 코드 오류 발생
void func4() { 
    int* ptr = new int(30);  // 힙 메모리에 정수 30 할당
    cout << "Value: " << *ptr << endl;
    delete ptr;             // 첫 번째 해제
    // delete ptr;          // 두 번째 해제 (코드 활성화 시 문제 발생)
}

//Dangling Pointer 예시
void func5() {
    int* ptr = new int(40);  // 힙 메모리에 정수 40 할당
    int* ptr2 = ptr; //힙 메모리의 ptr 주소를 ptr2에 저장

    cout << "ptr adress = " << ptr << endl;
    cout << "ptr2 adress = " << ptr2 << endl;
    cout << *ptr << endl;

    delete ptr; //힙 메모리 ptr 해제

    cout << *ptr2 << endl; //이미 해제된 ptr의 주소를 할당하고 있는 ptr2 불러오면 쓰레기값 존재
}

int main() {
    fun4()
    func5();    
    return 0;
}

Memory Leak

  • 동적으로 할당한 메모리를 사용한 후 제대로 해제하지 않으면 계속해서 사용하지 않는 메모리가 쌓임
  • 이렇게 되어 메모리 점점 차지하다가 사용할 수 있는 메모리 부족해지는 현상

스마트 포인터

  • 스마트 포인터의 핵심 원리는 new/delete를 사용하지 않는 자동 메모리 관리

  • 각 스마트 포인터의 목적에 맞게 자동으로 메모리를 관리(Dangling Pointer, Memory Leak 해결)

    • unique_ptr
      객체에 대한 단일 소유권을 관리(단 하나의 포인터만 객체를 소유), 복사 혹은 대입 불가능
      객체의 소유권을 명확히 하고 소유권 이전(move)를 통해 효율적인 자원 관리

      unique_ptr 예시 코드

#include <iostream>
#include <memory> // unique_ptr 사용
using namespace std;
//Smart 포인터 사용 예제(unique_ptr)

class uniqueClass {//일반 클래스에서 unique_ptr 사용 예시
public:
    uniqueClass(int val) : value(val) { 
        cout << "uniqueClass 생성: " << value << endl;
    }
    ~uniqueClass() {
        cout << "uniqueClass 소멸: " << value << endl;
    }
    void display() const {
        cout << "값: " << value << endl;
    }
private:
    int value;
};

int main() {
    // unique_ptr 생성
    unique_ptr<int> ptr1 = make_unique<int>(10); //객체에 대한 단일 소유권을 관리

    // unique_ptr이 관리하는 값 출력
    cout << "ptr1의 값: " << *ptr1 << endl;

    // unique_ptr은 복사가 불가능
    // unique_ptr<int> ptr2 = ptr1; // 컴파일 에러 발생!
    // 소유권 이동 (move 사용) 복사 대신
    unique_ptr<int> ptr2 = move(ptr1);

    if (!ptr1) { //move로 ptr1에서 ptr2로 옮겨져서 ptr1이 비어지게 됨
        cout << "ptr1은 이제 비어 있습니다." << endl;
    }
    cout << "ptr2의 값: " << *ptr2 << endl;

    // unique_ptr로 MyClass 객체 관리
    unique_ptr<uniqueClass> uniqueObject = make_unique<uniqueClass>(42);

    // MyClass 멤버 함수 호출
    uniqueObject->display();

    // 소유권 이동
    unique_ptr<uniqueClass> newOwner = move(uniqueObject);

    if (!uniqueObject) {
        cout << "uniqueObject는 이제 비어 있습니다." << endl;
    }
    newOwner->display();

    // 범위를 벗어나면 메모리 자동 해제
    return 0;
}
  • shared_ptr
    레퍼런스 카운트를 관리, 현재 객체를 참조하는 포인터의 개수를 카운팅
    레퍼런스 카운트가 0이 되면 객체는 자동으로 메모리 해제
    하나의 객체를 여러 개의 포인터가 함께 참조할 수 있는 스마트 포인터(복사 및 대입 연산으로 여러개 포인터 하나의 객체 공유)
    use_count()를 활용하여 현재 객체를 참조하는 포인터 수 확인
    reset()으로 소유 중인 객체를 해제하거나 다른 객체로 변경 가능

    shared_ptr 예시 코드

  #include <iostream>
  #include <memory> // shared_ptr 사용
  using namespace std;

  //Smart 포인터 사용 예제(shared_ptr)

  class sharedClass {//일반 클래스에서 shared_ptr 사용 예시
  public:
      sharedClass(int val) : value(val) { //클래스 기본 생성자
          cout << "sharedClass 생성: " << value << endl; // 출력: sharedClass 생성: 42
      }
      ~sharedClass() { //클래스 소멸자
          cout << "sharedClass 소멸: " << value << endl; // 출력: sharedClass 소멸: 42
      }
      void display() const { //출력 매개 함수
          cout << "값: " << value << endl; // 출력: 값: 42
      }
  private:
      int value; 
  };

  class B; // Forward declaration

  class A {
  public:
      std::shared_ptr<B> b_ptr;
      ~A() { std::cout << "A destroyed\n"; }
  };

  class B {
  public:
      std::shared_ptr<A> a_ptr;
      ~B() { std::cout << "B destroyed\n"; }
  };

  int main(){
      // shared_ptr 생성
      shared_ptr<int> ptr3 = make_shared<int>(10);

      // ptr3의 참조 카운트 출력
      cout << "ptr3의 참조 카운트: " << ptr3.use_count() << endl; // 출력: 1

      // ptr4가 ptr3과 리소스를 공유
      shared_ptr<int> ptr4 = ptr3;
      cout << "ptr2 생성 후 참조 카운트: " << ptr3.use_count() << endl; // 출력: 2

      // ptr4가 범위를 벗어나면 참조 카운트 감소
      ptr4.reset();
      cout << "ptr2 해제 후 참조 카운트: " << ptr4.use_count() << endl; // 출력: 1

  // shared_ptr로 MyClass 객체 관리
      shared_ptr<sharedClass> obj1 = make_shared<sharedClass>(42);

      // 참조 공유
      shared_ptr<sharedClass> obj2 = obj1;

      cout << "obj1과 obj2의 참조 카운트: " << obj1.use_count() << endl; // 출력: 2

      obj2->display(); // 출력: 값: 42

      // obj2를 해제해도 obj1이 객체를 유지
      obj2.reset();
      cout << "obj2 해제 후 obj1의 참조 카운트: " << obj1.use_count() << endl; // 출력: 1

      //shared_ptr을 사용해서 순환 참조가 발생하는 예시
      auto a = std::make_shared<A>(); 
      auto b = std::make_shared<B>();

      a->b_ptr = b;
      b->a_ptr = a;

      // main 함수가 끝나도 A와 B는 서로 참조 중이라 메모리 해제가 안 됨
  }
  • weak_ptr
    객체의 소유권을 공유하지 않고 레퍼런스 카운트를 증가시키지 않는 약함 참조
    shared_ptr은 두 개 이상의 객체가 서로 shared_ptr 가리켜 순환 참조 발생(메모리 누수 유발)
    서로 순환하고 있는 shared_ptr 중 하나를 weak_ptr로 대체하면 순환고리 끊어지게 됨
    weak_ptr은 레퍼런스 카운트를 증가시키지 않는 약한 참조 방식 동작 스마트 포인터
    lock() 호출 후 반환된 shared_ptr이 유효한지 확인 후에 사용
    레퍼런스 카운트 영향 미치지 않으므로 반드시 lock 함수로 내부객체 유효성 확인 후 사용

    weak_ptr 예시 코드

#include <iostream>
#include <memory> // weak_ptr 사용
using namespace std;

class B; //Forward declaration

class A {
public:
    std::shared_ptr<B> b_ptr;
    ~A() { std::cout << "A destroyed\n"; }

    void say_hello() {
        std::cout << "Hello from A\n";
    }
};

class B {
public:
    std::weak_ptr<A> a_ptr; // weak_ptr로 변경
    ~B() { std::cout << "B destroyed\n"; }

    void useA() {
        if (auto a_shared = a_ptr.lock()) { // 유효한지 확인
            a_shared->say_hello();
        } else {
            std::cout << "A is no longer available.\n";
        }
    }
};  

int main(){
    std::shared_ptr<B> b = std::make_shared<B>();

    {
        std::shared_ptr<A> a = std::make_shared<A>();
        b->a_ptr = a;
        b->useA(); // A가 유효하므로 Hello 출력
    } // A는 scope을 벗어나며 소멸됨

    b->useA(); // A는 이미 소멸되었기 때문에 메시지 출력

    auto weak_a = std::make_shared<A>();
    auto weak_b = std::make_shared<B>();
    weak_a->b_ptr = weak_b;
    weak_b->a_ptr = weak_a; // weak_ptr로 참조
}

얕은 복사와 깊은 복사
일반적으로 포인터나 동적으로 할당된 자원을 관리하는 객체는 메모리 안정을 위해 깊은 복사 사용이 바람직

  • 얕은 복사
    클래스 내의 포인터 멤버를 복사할 때 포인터가 가리키는 데이터가 아닌 포인터가 저장하고 있는 주소값만 복사하는 것
    두 객체가 동일한 동적 메모리 영역을 가리키게 됨
    얕은 복사는 원복 객체 메모리 해제하면 복사된 객체의 포인터는 해제된 메모리 영역 가리킴(dangling pointer 발생)

  • 깊은 복사
    클래스의 포인터 멤버가 가리키는 동적 데이터를 새로 할당된 독립적인 메모리 영역에 복제하는 것
    원본 객체와 복사된 객체는 서로 독립적인 메모리 공간을 소유하므로 dangling pointer가 발생하지 않음

    얕은 복사와 깊은 복사 비교 예시 코드

#include <iostream>
using namespace std;

int main() {
    // 포인터 A가 동적 메모리를 할당하고 값을 30으로 설정
    int* A = new int(30);

    // 포인터 B가 A가 가리키는 메모리를 공유(얉은 복사)
    int* B = A;

    // 포인터 B가 A가 가리키는 값을 복사 (깊은 복사)
    int* deep_B = new int(*A);

    cout << "A의 값: " << *A << endl; // 출력: 30
    cout << "B의 값: " << *B << endl; // 출력: 30
    cout << "deep_B의 값: " << *deep_B << endl; // 출력: 30


    // A가 동적 메모리를 해제
    delete A;

    // 이제 B는 Dangling Pointer(해제된 메모리를 가리키는 포인터)
    // 이 시점에서 B를 통해 접근하면 Undefined Behavior 발생
    cout << "B의 값 (dangling): " << *B << endl; // 위험: 정의되지 않은 동작

    // B는 여전히 독립적으로 자신의 메모리를 관리
    cout << "deep_B의 값 (깊은 복사 후): " << *deep_B << endl; // 출력: 30

    // B의 메모리도 해제
    delete deep_B;


    return 0;
}

언리얼엔진의 메모리 관리

  • 언리얼 엔진은 객체들의 메모리 관리 자동화를 위해 가비지 컬렉션 시스템 사용
  • 개발자가 메모리 해제를 수동으로 처리하는 부담 덜고, 메모리 누수나 dangling pointer와 같은 메모리 오류 줄임
  • 가비지 컬렉션은 마크앤 스윕 알고리즘 방식으로 동작
    주기적으로 실행되며, 더 이상 프로그램에서 사용하지 않는다고 판단되는 UObject들으 식별해서 메모리 제거
    3단계로 진행
    1. 루트셋에서 시작
    • 먼저 루트셋에 포함된 객체들을 식별
    • 이 객체들은 항상 살아있다고 간주되는 특별한 객체
    • 게임 엔진 자체, 플레이어 컨트롤러 등이 루트셋에 포함(가비지 컬렉션 대상 아님)
    1. 마크 단계- 도달 가능성 분석
    • 루트셋 객체에서 시작해서 직간접적으로참조하는 UObject를 마크, 객체 사용중임을 나타냄
    1. 스윕 단계 - 메모리 회수
    • 마크 단계가 완료되면 마크되지 않은 객체들이 차지하고 있던 메모리를 회수
    • 이 과정에서 해당 객체의 소멸자가 호출되고 메모리가 반환
  • UObject에는 가비지 컬렉션 동작 방식을 제어하는 다양한 플래그 존재
    • RF_RootSet
      이 플래그가 설정된 객체는 루트셋의 일부로 관리
      즉 설정된 시점부터 가비지 컬렉션 대상 X, AddToRoot()함수를 통해 설정, RemoveFromRoot() 함수를 통해 해제
    • RF_BeginDestroyed
      객체의 BeginDestroy() 함수 호출됨을 의미
      해당 함수는 객체가 실제로 메모리에서 해제되기 전에 필요한 정리 작업 수행
    • RF_FininshDestroyed
      객체의 FinishDistroy() 함수가 호출됨을 의미
      해당 함수는 객체 소멸의 마지막 단계, 이 함수 호출 후 객체의 메모리가 완전히 해제

언리얼엔진의 리플렉션 시스템

  • 리플렉션 시스템이란 프로그램이 실행 중 자신의 구조와 상태를 검사하고 수정할 수 있는 능력
    C++ 자체적으로 지원 X, 언리얼 엔진에서 자체적으로 구축
  • 언리얼 엔진 내부에서 동작하는 여러 모듈(가비지 컬렉터, 스크립트 시스템)등은 모두 UObject 기반
  • 사용자가 정의한 타입들의 경우 엔진에서 알 지 못하므로, 이를 처리 할 수 있도록 타입 정보를 공유
  • 리플렉션의 핵심은 UHT 코드 생성기
    UHT는 C++ 컴파일러가 수행되기 전에 동작
    C++ 코드 내에서 메타 데이터를 얻고, 내부적으로 소스코드를 생성
    이후 C++ 컴파일러 수행
    매크로리플렉션에서의 목적일반적인 위치
    UCLASS()C++ 클래스를 UObject 기반의 리플렉션 시스템에 등록클래스 정의 앞
    UPROPERTY()멤버 변수를 리플렉션 시스템에 노출멤버 변수 선언 앞
    UFUNCTION()멤버 함수를 리플렉션 시스템에 노출멤버 함수 선언 앞
    USTRUCT()C++ 구조체를 리플렉션 시스템에 등록구조체 정의 앞
    GENERATED_BODY()UHT가 생성하는 리플렉션 및 엔진 지원 코드를 위한 삽입 지점클래스/구조체 본문 첫 줄

USTRUCT() 예시

// FMyStructData.h (or within another .h)
#pragma once

#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "MyStructData.generated.h"

USTRUCT(BlueprintType) // 블루프린트에서 이 구조체를 타입으로 사용할 수 있도록 합니다.
struct FMyStructData
{
   GENERATED_BODY() // UHT가 필요한 코드를 생성하도록 합니다.

public:
   UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyStructData")
   int32 SampleInt;

   UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyStructData")
   FString SampleString;

   FMyStructData() : SampleInt(0), SampleString(TEXT("Default")) {}
};

UCLASS() 예시

UCLASS(Blueprintable, BlueprintType) // 블루프린트에서 생성 가능하고 타입으로 사용 가능하도록 합니다.
class YOURPROJECT_API UMyReflectedObject : public UObject // UObject를 상속받습니다.
{
   GENERATED_BODY() // UHT가 필요한 코드를 생성하도록 합니다.

public:
   // ... Properties and Functions will go here ...
   UMyReflectedObject(); // Constructor
};

자원 관리하기 실습 1.
메모리 누수 발생 코드 분석하고 보완하기
C++로 구현된 코드를 분석해서 메모리 누수가 우려되는 부분을 확인한 후, 코드를 수정하세요.

#include <iostream>
using namespace std;

class MyClass {
private:
    int* ptr;

public:
    // 생성자
    MyClass() {
        ptr = new int(10); // 동적 메모리 할당
        cout << "메모리 할당 완료!" << endl;
    }

    // 소멸자
    ~MyClass() {

    }

    void print() const {
        cout << "값: " << *ptr << endl;
    }
};

int main() {
    MyClass obj;
    obj.print();

    // main 함수 종료
    return 0;
}

실습 코드

#include <iostream>
using namespace std;

class MyClass {
private:
    int* ptr;

public:
    // 생성자
    MyClass() {
        ptr = new int(10); // 동적 메모리 할당
        cout << "메모리 할당 완료!" << endl;
    }

    // 소멸자(생성자에서 포인터 ptr에 동적 메모리 할당했으므로 소멸자에서 delete로 삭제해줌으로써 메모리 누수 방지)
    ~MyClass() {
    delete ptr;
    cout << "MyClass 소멸: " << *ptr << endl; // 출력: MyClass 소멸
    }

    void print() const {
        cout << "값: " << *ptr << endl;
    }
};

int main() {
    MyClass obj;
    obj.print();
    // main 함수 종료
    return 0;
}

자원 관리하기 실습 2.
스마트 포인터를 활용한 로그분석기 구현

로그 기록기는 프로그램에서 발생한 중요한 사건을 기록합니다.
로그 기록기의 몇 가지 기능을 구현해 봅시다.

요구사항은 아래와 같습니다.

📌로그 메시지는 중요도에 따라(Info, Warning, Error)로 분류되어 기록될 수 있습니다. 로그 앞에 중요도가 태그가 붙어 표시됩니다.
  • [Info] 사용자 메시지
  • [Warning] 사용자 메시지
  • [Error] 사용자 메시지
📌로그 기록기는 단 하나의 인스턴스만 존재해야 하며, `unique_ptr`를 통해 이를 보장해야 합니다. 📌지금까지 기록된 로그의 총개수를 출력할 수 있어야 합니다.

완성된 모습
[로그 출력 예시]

[INFO]: System is starting.
[WARNING]: Low disk space.
[ERROR]: Unable to connect to the server.
Total logs recorded: 3
Logger instance destroyed.

실습 코드

#include <iostream>
#include <memory>
#include <string>
using namespace std;

class Logger{
    public:
        Logger() : logCount(0) {}
        ~Logger(){
            cout<<"Logger instance destroyed"<<endl;
        }
        void logInfo(const string& message){
            logCount++;
            cout<<"[INFO]: "<<message<<endl;
        }
        void logWarning(const string& message){
            logCount++;
            cout<<"[WARNING]: "<<message<<endl; 
        }
        void logError(const string& message){
            logCount++;
            cout<<"[ERROR]: "<<message<<endl;
        }
        void showTotalLogs(){
            cout<<"Total logs recorded: "<<logCount<<endl;
        }
    private:
        int logCount;

};

int main(){
    unique_ptr<Logger> logger = make_unique<Logger>();

    logger->loginfo("System is starting.");
    logger->logWarning("Low disk space.");
    logger->logError("Unable to connect to the server.");

    logger->showTotalLogs();


}

CH2 학습 가이드
미니 실습

  • 클래스 Car를 정의해요. 모델이름과 속도를 멤버 변수로 넣어주세요.
  • 모델 이름은 생성자에서 받아서 설정할 수 있어야 해요.
  • 클래스 내에 Accelerate() 함수를 구현해요. 호출할 때마다 속도가 올라가요.
  • 클래스 내에 Brake() 함수를 구현해요. 호출할 때마다 속도가 내려가요.
  • 클래스 내에 DisplayInfo() 함수를 만들어요. 자동차 이름과 현재 속도를 표시해주세요.
  • 자동차를 컨트롤 하기 위해, 사용자의 입력을 받는 아래와 같은 인터페이스를 만들어주세요.

실습 코드

#include <iostream>

using namespace std;

class Car{
    public:
        Car(string modelname){
            this->modelname=modelname;
            this->speed=0;
        };
        void Accelerate(){
            if(speed>300){
                speed=300;
            }
            else{
                speed+=10;
            }

        }
        void Break(){
            if(speed-10<0){
                speed=0;
            }
            else{
                speed-=10;
            }
        }
        void DisplayInfo(){
            cout<<"자동차 이름 : "<<modelname<<endl;
            cout<<"현재 속도 : "<<speed<<endl;
        }
    private:
            string modelname;
            int speed;
};

int main(){
    string modelname = "Aventador";
    Car car(modelname);
    int input=0;
    while(input!=3){
        cout<<"1) 가속\n";
        cout<<"2) 브레이크\n";
        cout<<"3) 종료\n";
        cin>>input;
        switch(input){
            case 1:
            {
                car.Accelerate();
                car.DisplayInfo();
                break;
            }
            case 2:
            {
                car.Break();
                car.DisplayInfo();
                break;
            }
            case 3:
            {
                cout<<"종료됩니다."<<endl;
                break;
            }

            default:
            {
                cout<<"잘못된 입력입니다.\n";
                break;
            }
        }

    }

}

도전 실습

  • 온도계를 클래스로 만들어요.

  • 멤버 변수는 섭씨 기준의 현재 온도에요.

  • 현재 온도에 대한 게터 함수와 세터 함수를 각각 만들어요.

  • 세터 함수에서 입력한 값에 범위를 걸어주세요.
    섭씨 -100도에서 100도 까지만 값 입력이 가능해요.

  • 출력 모드 전환이 되는 bool 타입의 멤버 변수, 그리고 변수의 세터를 만들어주세요.
    이 멤버 변수가 TRUE이면 [섭씨 현재 온도] 게터 함수가 [화씨 현재 온도]로 반환해줘요.

  • 아래의 인터페이스를 만들어 주세요.

    [--- 메시지 표시하는 라인 ---]
    1) 온도 설정
    2) 표시 모드 전환
    3) 온도 표시

실습 코드

#include <iostream>
using namespace std;
class Thermometer {
    public:
        Thermometer(double temperature){
            this->temperature = temperature;
            this->printTempF = false;
        }
        double getTemperature(){
            if(!printTempF){
                return temperature;
            }
            else{
                return temperature*1.8 + 32;
            }
        }
        void setTemperature(double temperature){
            if(temperature>=-100 && temperature<=100){

                if(printTempF){
                    this->temperature = (temperature - 32) / 1.8;
                    cout<<"온도가 화씨 " <<temperature <<"도로 설정됩니다."<<endl;
                }

                else{
                    this->temperature = temperature;
                    cout<<"온도가 섭씨 " <<temperature <<"도로 설정됩니다."<<endl;
                }
            }
            else{
                cout<<"잘못된 온도 입력입니다."<<endl;
            }
        }
        void setPrimtTempF(bool tempF){
            this->printTempF = tempF;
        }
        bool getPrintTempF(){
            return printTempF;
        }
    private:
        double temperature;
        bool printTempF;

};

int main(){
            double temperature =36.5;
            Thermometer thermometer(temperature);
            int input=0;
            while(true){
                cout<<"1) 온도 설정\n";
                cout<<"2) 표시 모드 전환\n";
                cout<<"3) 온도 표시\n";
                cin>>input;
                switch(input){
                    case 1:
                    {
                        cin>>temperature;
                        thermometer.setTemperature(temperature);
                        break;
                    }
                    case 2:
                    {
                        bool printTempF = !thermometer.getPrintTempF();
                        thermometer.setPrimtTempF(printTempF);
                        if(printTempF){
                            cout<<"화씨 기준 온도로 표시 모드 전환합니다."<<endl;
                        }
                        else{
                            cout<<"섭씨 기준 온도로 표시 모드 전환합니다."<<endl;
                        }
                        break;
                    }
                    case 3:{
                        if(thermometer.getPrintTempF()){
                        cout<<"현재 온도는 화씨 "<<thermometer.getTemperature() <<"도 입니다."<<endl;
                        }else{
                        cout<<"현재 온도는 섭씨 "<<thermometer.getTemperature() << "도 입니다."<<endl;
                        }
                        break;
                    }


                    default:
                    {
                        cout<<"잘못된 입력입니다. 종료됩니다.\n";
                        return 0;
                    }
                }

            }

}

도전X도전 실습

  • 비행기에 파일럿 2명을 태울거에요.

  • 파일럿 클래스는 아래와 같은 규격이에요.
    클래스: Pilot
    멤버 변수: string name, int licenseID
    생성자: Pilot(std::string name, int id);
    멤버 함수: void printPilotInfo();

  • 비행기 클래스는 다음과 같은 규격을 가지고 있어요.
    클래스 이름: Aircraft
    멤버 변수: std::string modelName, Pilot crew[2]
    생성자: Aircraft(std::string name, const Pilot& captain, const Pilot& coPilot);
    멤버 함수: void displayAircraftInfo(); void displayCrewInfo();

  • main() 함수에서 캡틴과 코파일럿을 생성하고, 비행기를 생성해주세요.

  • 비행기와 파일럿 정보를 출력해주세요.

실습 코드

#include <iostream>
using namespace std;

class Pilot {
public:
    Pilot(string name, int id) : name(name), licenseID(id) {}

    void printPilotInfo() {
        cout << "파일럿 이름: " << name << endl;
        cout << "파일럿 라이센스 ID: " << licenseID << endl;
    }

private:
    string name;
    int licenseID;
};

class Aircraft {
public:
    Aircraft(string name, const Pilot& captain, const Pilot& coPilot)
        : modelName(name), crew{ captain, coPilot } // 배열 초기화 OK
    {
    }

    void displayAircraftInfo() {
        cout << "비행기 모델명: " << modelName << endl;
    }

    void displayCrewInfo() {
        cout << "\n[캡틴]\n";
        crew[0].printPilotInfo();

        cout << "\n[코파일럿]\n";
        crew[1].printPilotInfo();
    }

private:
    string modelName;
    Pilot crew[2];  // crew[0], crew[1]
};

int main() {
    Pilot captain("James", 123456);
    Pilot copilot("Jim", 678910);

    Aircraft Asiana("BOEING 727", captain, copilot);

    Asiana.displayAircraftInfo();
    Asiana.displayCrewInfo();
}
profile
개발합시다!

0개의 댓글