[2번 과제] OOP Summary

정채은·2025년 6월 5일

C++ 프로그래밍

목록 보기
8/16

✅ 필수 기능 가이드

  • Animal이라는 기본 클래스를 정의 합니다.
  • Animal 클래스에는 makeSound()라는 순수 가상 함수를 포함합니다.
  • Animal 클래스를 상속받아 다양한 동물 클래스를 생성합니다. 예) Dog, Cat, Cow
  • 각 클래스에서 makeSound()함수를 재정의하여 해당 동물의 소리를 출력합니다.
  • 메인 함수에서 Animal 타입의 포인터 배열을 선언합니다.
  • Dog, Cat, Cow 자료형의 변수를 선언하고, 배열에 저장해봅니다.
  • Animal 배열을 반복문으로 순회하면서 각 동물의 울음소리를 내게 합니다.
  • 전체적인 구조는 아래와 같습니다.

🔥 도전 기능 가이드

  • 필수 기능 가이드에 있는 요구사항을 만족하는 코드를 구현했다면,
    아래 코드 스니펫을 보고 요구사항대로 Zoo 클래스를 정의해주세요!

Zoo 클래스 요구사항

class Zoo {
private:
    Animal* animals[10]; // 동물 객체를 저장하는 포인터 배열
public:
    // 동물을 동물원에 추가하는 함수
    // - Animal 객체의 포인터를 받아 포인터 배열에 저장합니다.
    // - 같은 동물이라도 여러 번 추가될 수 있습니다.
    // - 입력 매개변수: Animal* (추가할 동물 객체)
    // - 반환값: 없음
    void addAnimal(Animal* animal);

    // 동물원에 있는 모든 동물의 행동을 수행하는 함수
    // - 모든 동물 객체에 대해 순차적으로 소리를 내고 움직이는 동작을 실행합니다.
    // - 입력 매개변수: 없음
    // - 반환값: 없음
    void performActions();

    // Zoo 소멸자
    // - Zoo 객체가 소멸될 때, 동물 벡터에 저장된 모든 동물 객체의 메모리를 해제합니다.
    // - 메모리 누수를 방지하기 위해 동적 할당된 Animal 객체를 `delete` 합니다.
    // - 입력 매개변수: 없음
    // - 반환값: 없음
    ~Zoo();
};
  • 동물 객체를 반환하는 createRandomAnimal()함수를 구현하세요 자세한 사항은 아래 코드 스니펫을 참조하시면 됩니다!

createRandomAnimal() 함수 요구사항

#include <cstdlib>
#include <ctime>

// 랜덤 동물을 생성하는 함수
// - 0, 1, 2 중 하나의 난수를 생성하여 각각 Dog, Cat, Cow 객체 중 하나를 동적으로 생성합니다.
// - 생성된 객체는 Animal 타입의 포인터로 반환됩니다.
// - 입력 매개변수: 없음
// - 반환값: Animal* (생성된 동물 객체의 포인터)
Animal* createRandomAnimal();

내가 짠 코드

헤더 파일 (HW02.h)

#pragma once

#include <iostream>
#include <cstdlib>
#include <ctime>


//Animal이라는 기본 클래스를 정의 합니다.
//Animal 클래스에는 `makeSound()`라는 순수 가상 함수를 포함합니다.
class Animal {
public:
    virtual void makeSound() = 0;
    virtual ~Animal() {}
};

//Animal 클래스를 상속받아 다양한 동물 클래스를 생성합니다. 예) Dog, Cat, Cow
class Dog : public Animal {
public:
    //각 클래스에서 makeSound()함수를 재정의하여 해당 동물의 소리를 출력합니다.
    void makeSound() override;
};

class Cat : public Animal {
public:
    void makeSound() override;
};

class Cow : public Animal {
public:
    void makeSound() override;
};


class Zoo {
private:
    Animal* animals[10]; // 동물 객체를 저장하는 포인터 배열
    int animalCount = 0;  // 현재 등록된 동물 수
public:
    // 동물을 동물원에 추가하는 함수
    void addAnimal(Animal* animal);

    // 동물원에 있는 모든 동물의 행동을 수행하는 함수
    void performActions();

    // Zoo 소멸자
    ~Zoo();
};

// 랜덤 동물을 생성하는 함수
Animal* createRandomAnimal();

소스 파일 (HW02.cpp)

#include <iostream>
#include "HW02.h"

using namespace std;


void Dog::makeSound() {
    cout << "Dog : Bark" << endl;
}

void Cat::makeSound() {
    cout << "Cat : Meow" << endl;
}

void Cow::makeSound() {
    cout << "Cow : Moo" << endl;
}

// 동물을 동물원에 추가하는 함수
void Zoo::addAnimal(Animal* animal) {
    if (animalCount < 10) {
        animals[animalCount++] = animal;
    }
    else {
        cout << "동물원이 가득 찼습니다." << endl;
    }
}


// 동물원에 있는 모든 동물의 행동을 수행하는 함수
void Zoo::performActions() {
    for (int i = 0; i < animalCount; i++) {
        animals[i]->makeSound();
    }
}

// Zoo 소멸자
Zoo ::~Zoo() {
    for (int i = 0; i < animalCount; i++) {
        delete animals[i];
    }
}

// 랜덤 동물을 생성하는 함수
Animal* createRandomAnimal() {
    int r = rand() % 3; 
    switch (r) {
    case 0: return new Dog();
    case 1: return new Cat();
    case 2: return new Cow();
    default: return nullptr;
    }
}

main.cpp

int main()
{
    cout << "- - - - - - 필수 기능 - - - - - -" << endl;
    // 메인 함수에서 Animal 타입의 포인터 배열을 선언합니다.
    Animal* animal[3];
    // Dog, Cat, Cow 자료형의 변수를 선언하고, 배열에 저장해봅니다.
    animal[0] = new Dog();
    animal[1] = new Cat();
    animal[2] = new Cow();

    // Animal 배열을 반복문으로 순회하면서 각 동물의 울음소리를 내게 합니다.
    for (int i = 0; i < 3; i++) {
        animal[i]->makeSound();
    }
    for (int i = 0; i < 3; i++) {
        delete animal[i];
    }

    cout << endl << "- - - - - - 도전 기능 - - - - - -" << endl;
    srand(static_cast<unsigned int>(time(NULL)));

    Zoo zoo;

    for (int i = 0; i < 10; ++i) {
        zoo.addAnimal(createRandomAnimal());
    }

    cout << "동물원에서 들리는 소리" << endl;
    zoo.performActions();

    return 0;
}

💡알게 된 것

1️⃣ 난수 생성

C/C++에서 난수(랜덤 값)를 생성할 때 rand() 함수를 사용 할 수 있다.

사용 방법

#include <cstdlib>  // rand() 함수는 이 헤더에 정의되어 있음
#include <ctime>    // 시간 관련 함수 time()

int main() {
    srand(time(0)); // 랜덤 시드 설정 (한 번만)
    
    int randomValue = rand();  // 0부터 RAND_MAX 사이의 정수 반환
    cout << randomValue << endl;
}

🧩 rand() 함수의 특징

항목설명
반환값0 이상 RAND_MAX 이하의 정수 (보통 32,767)
헤더파일 (#include <stdlib.h>도 가능)
시드 설정srand(seed);로 설정. 같은 시드 → 같은 난수열
초기화 추천srand(time(0));로 현재 시간을 시드로 줘서 매번 다른 난수 생성

🎯 특정 범위에서 랜덤값 얻는 법

  • 0 ~ 2 사이의 값 (3개 중 하나)
int r = rand() % 3;  // 0, 1, 2 중 하나
  • a ~ b 범위
int r = rand() % (b - a + 1) + a;
  • 10~20 사이 값
int r = rand() % 11 + 10;  // (20 - 10 + 1) + 10

2️⃣ 오버라이드(override)

C++에서 override 키워드는 자식 클래스에서 부모 클래스의 가상 함수를 재정의(오버라이딩) 할 때 사용한다.
반드시 써야 하는 건 아니지만, 매우 권장되는 좋은 습관이다.

왜 override를 쓰는 게 좋을까?

▶ 실수 방지
부모 클래스 함수와 정확히 일치하지 않으면 컴파일 에러가 난다.

class Cat : public Animal {
public:
    void makesound() const override {  // ❌ 오타 → 컴파일 에러 발생!
        std::cout << "Meow!\n";
    }
};

makesound는 makeSound가 아니라서 오버라이딩이 아니라 새 함수가 된다.
override를 안 썼다면 다른 함수가 추가된 걸로 처리되어 버그 발생 가능.

✅ override는 언제 붙여야 하나?

  • 부모 클래스의 virtual 함수를 재정의할 때
  • 함수 이름, 매개변수, const 여부가 정확히 같아야 함
class A {
public:
    virtual void func(int x) const;
}; 
  
class B : public A {
public:
    void func(int x) const override;  // ✅ const까지 정확히 일치해야 함
};

🔁 override vs virtual

키워드의미
virtual부모 클래스에서 오버라이딩 가능한 함수 표시
override자식 클래스에서 정확히 오버라이딩한다고 표시

3️⃣ C++에서 virtual이 없으면 오버라이딩 못하나요?

그렇다, 기본적으로 부모 클래스에서 함수가 virtual로 선언되어 있어야 자식 클래스에서 진짜 오버라이딩이 된다.

만약 부모 함수가 virtual이 아니면, 자식 클래스에서 같은 이름과 시그니처로 함수를 다시 만들 수는 있지만, 이건 함수 숨기기(hiding) 일 뿐이고, 가상 함수 호출(다형성)은 작동하지 않는다.

예시

#include <iostream>
using namespace std;

class Parent {
public:
    virtual void speak() {  // virtual 함수
        cout << "부모가 말해요" << endl;
    }

    void walk() {  // virtual 아님
        cout << "부모가 걷는다" << endl;
    }
};

class Child : public Parent {
public:
    void speak() override {  // 오버라이딩
        cout << "자식이 말해요" << endl;
    }

    void walk() {  // 함수 숨기기
        cout << "자식이 걷는다" << endl;
    }
};

int main() {
    Parent* p = new Child();

    p->speak();  // 자식의 speak()가 호출됨 (가상 함수)
    p->walk();   // 부모의 walk()가 호출됨 (비가상 함수)

    delete p;
    return 0;
}

speak()는 부모에서 virtual이라서 자식에서 재정의 가능하고, 포인터를 통해 호출하면 자식 버전이 호출된다

walk()는 부모에서 virtual이 아니기 때문에, 포인터를 통해 호출하면 항상 부모 버전이 호출된다.

profile
누군가에게 추억을 만들어주는 그날까지

0개의 댓글