OOP summary

정혜창·2024년 12월 26일
1

내일배움캠프

목록 보기
11/43

C++ 수업을 시작한지 5일차이다. 첫 과제는 프로그래밍 문법을 활용한 과제로 금방한 것 같은데 두 번째 과제는 C++의 꽃, 객체지향을 이해하고 있어야해서 공부하는데 많은 시간이 소모되었다. (크리스마스도 15시간 정도 공부만 한 것 같다.) 그래도 시간이 모자랐던 것 같다. 이제 클래스가 무엇인지, 객체, 인스턴스 그리고 클래스선언, 생성자, 소멸자 등 다 이해는 하는데 아직 빌드를 많이 해보지 않아서 그런지 아직 헷갈리는 부분도 많고 힘들었다. 아마 5일간 포인터, 배열등 프로그래밍의 '벽'이라고 할 수 있는 부분도 써야하고 객체지향도 뭔가 개념이 쉽게 잡히지 않아서 더 그런 것 같다. 근 5일동안 너무 지식이 한꺼번에 들어와서 그런가.. 휘발성 메모리(RAM) 마냥 머리속에 쌓이지 못하고 쉽게 날라가는 기분이였다...

그래도 최선을 다해 과제를 한 만큼 그동안 작성했던 코드들을 해석해보자
우선 과제는 다음과 같다.

일단 첫날 이것을 보았을 땐 내 머리속은 '???' 였다. 일단 용어도 거의 모르는 용어로 도배되어 있을 뿐더러 클래스는 RPG의 도적,법사,전사 이런거 인줄 알았지 프로그래밍에서의 클래스는 도저히 무엇인지 감을 잡지 못하였다. 그래서 열심히 공부를 하게되었고 우선 내가 알아본 바에 의하면

  • 클래스 : 멤버 변수와, 멤버 함수로 이루어진 일종의 틀, 객체를 찍어내게 만들어 주는 일종의 설계도 같은 역할이다.
  • 객체 : 클래스라는 틀에서 만들어진 실체. 객체는 클래스에서 정의한 멤버변수와 멤버함수 모두 가지게 된다.
  • 인스턴스 : 객체가 실제로 메모리에 배치되고난 실체. 뭔가 객체보다 좀 더 구체적인 느낌? 실제 사용될 때 인스턴스라고 부르는 느낌이다.

그래서 나는 바로 클래스를 만들어 보았다.


1. 부모 클래스 (Animal)

class Animal {
public:
    // 생성자
    Animal() {
        cout << "Animal 생성자" << endl;
    }

    // 순수 가상 함수: 자식 클래스에서 반드시 구현해야 합니다.
    virtual void makeSound() = 0;

    // 가상 소멸자: 자식 클래스 소멸자가 올바르게 호출되도록 합니다.
    virtual ~Animal() {
        cout << "Animal 소멸자" << endl;
    }
};

나중에 만들 Cat, Dog, Cow 의 부모 클래스인 Animal이다. class (이름) { }; 으로 선언하고 내요은 public, protected, privated 에 따라 접근을 달리 한다.

  • public : 클래스, 자식 클래스, 외부 함수등 어디서든 접근가능
  • protected : 클래스, 상속 받은 자식 클래스 내에서만 접근 가능
  • private : 클래스 내에서만 접근가능

클래스에는 객체가 생성될 때, 소멸될 때 호출되는 특별한 함수가 있는데 생성자와 소멸자이다. 각각 초기상태를 설정하거나, 할당된 메모리를 해제하는 일 등을 한다. 함수 이름은 반드시 클래스 이름과 같고 소멸자는 클래스 이름앞에 '~'를 붙인다. 둘다 반환형은 없다.

이제 가운데에 virtual void makeSound() = 0; 이 있다. 이건 순수 가상 함수로써 virtual이 앞에 붙으면 가상함수, 그 값을 0을 주면 순수 가상함수가 된다. 순수 가상함수는 부모 클래스에서 구현이 안된다. 그래서 반드시 자식클래스에서 구현해야 한다. 또한 가상함수로써 자식클래스에서 오버라이드(덮어쓰기)를 할 수 있다. 자식클래스에서 같은 함수 시그니처로 부모와는 다르게 구현되는 함수를 오버라이드 한다고 한다.


2. 자식 클래스(Dog, Cat, Cow)

이제 부모클래스를 봤으니 아래 자식클래스들을 보자.

class Dog : public Animal {
public:
    Dog() {
        cout << "Dog 생성자" << endl;
    }

    void makeSound() override {
        cout << "왈! 왈!" << endl;
    }

    ~Dog() {
        cout << "Dog 소멸자" << endl;
    }
};

class Cat : public Animal {
public:
    Cat() {
        cout << "Cat 생성자" << endl;
    }

    void makeSound() override {
        cout << "냥~ 냥~" << endl;
    }

    ~Cat() {
        cout << "Cat 소멸자" << endl;
    }
};

class Cow : public Animal {
public:
    Cow() {
        cout << "Cow 생성자" << endl;
    }

    void makeSound() override {
        cout << "음머~ 음머~" << endl;
    }

    ~Cow() {
        cout << "Cow 소멸자" << endl;
    }
};

솔직히 자식클래스에서는 크게 어렵거나 볼만한 요소는 없다. 클래스 이름 : pulic Animal 하여 상속받고 makeSound 함수를 오버라이드 하였다.


3. 관리 클래스 (Zoo)

class Zoo {
private:
    Animal* animals[10]; // 동물 객체를 저장하는 포인터 배열
    int size;             // 동물의 수

public:
    Zoo() : size(0) {}

    // 동물 객체를 zoo에 추가하는 함수
    void addAnimal(Animal* animal) {
        if (size >= 10) {
            cout << "Zoo is full!" << endl;
            return;
        }
        animals[size++] = animal;
    }

    // 동물원에 있는 모든 동물의 소리와 행동을 출력하는 함수
    void performActions() {
        for (int i = 0; i < size; i++) {
            animals[i]->makeSound();
        }
    }

    // Zoo 소멸자: 동물 객체 메모리 해제
    ~Zoo() {
        for (int i = 0; i < size; i++) {
            delete animals[i];
        }
    }
};

동물 객체를 zoo에 추가하는 함수

이 부분이 가장 작성하기 힘들었던 부분이다. 우선 이 Zoo라는 클래스는 동물들의 객체를 관리한다. 위에서 부터 한줄 한줄 정리해보면
Animal* animals[10];

  • Animal* 타입의 포인터 10개를 저장하는 배열을 선언한 것이다. 배열의 각 요소는 동물 객체의 메모리 주소를 가지고 있다. 한 마디로 동물 객체에 접근하기 위해서 선언하였음!

Zoo() : size(0) {}

  • zoo 클래스의 생성자 함수이다. 초기화 목록을 통해 zoo에 동물객체가 비어 있는 상태에서 시작한다.

void addAnimal(Animal* animal)

  • 배열에 새로운 동물객체를 추가하기 위해 만들어진 함수이다. 이걸 어떻게 해야 될지 몰라서 정말 오래 걸렸다... 우선 매개변수로 받은 Animal* animal은 동물 객체의 포인터이다. 우선 10개 이상의 동물 객체가 만들어지면 더 이상 추가하지 못하게 if 문을 설정 하였고 마지막 animals[size++] = animal;이 핵심인데 만약 size가 0이라면 animals[0] (동물 객체를 저장하는 포인터 배열) 도 빈 공간이게 된다. 여기서 animal은 함수 매개변수로 전달된 동물 객체의 포인터이다. 따라서 전달받은 포인터를 배열의 현재 위치 size에 저장하는 것이다. 결과적으로, 배열의 size번째 칸에 동물 객체의 주소를 저장한다.

size++

  • size는 동물의 수를 추적하는 변수 이다. 후위 연산자이므로 size = 0일때 animals[0] = animal; 수행 후 size가 1 증가한다. 다음 동물 객체를 추가하면 animals[1]에 저장되는 식.

    • 결론 : 결국 실행되는 쪽은 main 함수이다. main 함수에서 동물 객체를 생성하고 그 포인터를 addAnimal 함수에 전달하면 함수가 호출이 되고 전달 받은 포인터 (animal)animals 배열의 현재 size 인덱스에 저장한다.

동물 소리 수행 함수

animals[i] -> makeSound();

  • -> 처음보는 연산자가 있는데 , animals[i]가 가리키는 객체의 주소를 통해 그 객체의 멤버함수를 호출하겠다는 뜻이다. 약간 . 연산자와 비슷한데 . 연산자는 객체의 멤버에 직접 접근할 때 사용하고 -> 연산자는 포인터가 가리키는 객체의 멤버에 접근할 때 사용한다. 따라서 for문을 통해 animals[i] 에 저장 되어 있는 모든 동물 객체의 makeSound 함수를 수행하게 한다.

소멸자

delete animals[i];

  • 나중에 메인 함수를 보면 알겠지만 각 동물 객체가 동적으로 할당(new)되었기 때문에 , 힙에 저장된다. 따라서 메모리를 수동으로 해제해야 한다. 따라서 for문을 통해 저장되어있는 동물 객체 모두 순차적으로 해제 하도록 적어주었다. delete animals[i]

4. 랜덤 함수

Animal* createRandomAnimal() {
    int random = rand() % 3; // 0, 1, 2 중 랜덤 선택
    if (random == 0) {
        return new Dog();
    }
    else if (random == 1) {
        return new Cat();
    }
    else {
        return new Cow();
    }
}

우선 실행을 하면 일정하게 출력하는 것이 아니라 랜덤으로 동물 객체를 반환하는 함수를 구현하라고 적혀있었기 때문에 구현해보았다. 사실 이전에는 한번도 랜덤함수를 구현을 해본적이 없어서 인터넷을 통해 공부하여 적용시켜 보았다. (컴퓨터가 완벽한 난수는 생성못한다는게 매우 충격이였던... )

우선 랜덤함수를 구현하기 위해선 rand()srand()를 사용해서 난수 생성을 하여야 한다.

  • rand() 함수는 C 라이브러리에서 제공하는 키워드이며 기본적으로 0 ~ RAND_MAX (32767)사이의 값을 반환한다.

  • srand() 함수는 난수 생성기의 초기값(seed)를 설정한다. 동일한 seed를 사용하면 항상 동일한 난수 시퀀스가 생성되기 때문에 일반적으로는 time(0) 을 사용하여 현재시간을 seed로 설정한다.

  • 이렇게 하더라도 난수 품질이 안좋아서 최근에는 현대적인 난수 생성기인 <random> 헤더를 사용하는 방법이 있지만 아직 잘모르므로 이렇게 설정하였다. 나중에 한번 알아보고 설정해보고 싶다.

이러한 사실을 기반으로 살펴보면 이따가 알겠지만 메인함수 시작할 때 부터srand(time(0));로 랜덤 시드를 초기화 하였다. 따라서 매 시간 마다 다른 값을 rand()이 반환하므로 rand() % 3 의 결과는 0, 1, 2 중 랜덤하게 계속해서 나오게 된다. (실행할 때 마다 달라짐) 따라서 여기에 있는 값 3개중 하나씩 자식 클래스 Dog, Cat, Cow에 각각 부여해서 객체를 동적으로 생성하게 하였다.


5. main 함수

int main() {
    srand(time(0)); // 랜덤 시드 초기화

    Zoo zoo1;

    // 동물 랜덤 생성 및 추가
    for (int i = 0; i < 5; i++) {
        zoo1.addAnimal(createRandomAnimal());
    }

    // 동물원에 있는 모든 동물들의 소리 출력
    zoo1.performActions();

    // Zoo 소멸자가 동적 할당된 메모리를 자동으로 해제합니다.
    return 0;
}

항상 main 함수가 엄~~~청 길어진 모습만보다가 클래스를 만들고 함수를 밖에서 선언하고 하니깐 매우 간단해졌다. 우선 랜덤 시드 초기화를 먼저 해주고
클래스 Zoo의 객체 zoo1을 생성해준다.(Zoo zoo1;)

그리고 for 을 통해 동물 객체를 5개만 생성했다. 많이 생성하고 싶으면 자유롭게 조절가능하다 (앞에서 동적 할당을 했으니깐!! 대신 10개보다 많게하려면 Zoo 클래스에서 Size 멤버 변수를 건드려야 한다. 동물 객체가 꽉 차면 자동으로 사이즈를 늘리는 방향도 있겠지만 그러려면 이중 포인터를 써야하는데 그쪽 개념을 알긴하지만 아직 익숙치 않아서 조금 더 나한테 맞는 방향으로 코드를 작성하였다.

이제 모든 코드를 합친 완성된 코드는 다음과 같다.

#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

class Animal {
public:
    // 생성자
    Animal() {
        cout << "Animal 생성자" << endl;
    }

    // 순수 가상 함수: 자식 클래스에서 반드시 구현해야 합니다.
    virtual void makeSound() = 0;

    // 가상 소멸자: 자식 클래스 소멸자가 올바르게 호출되도록 합니다.
    virtual ~Animal() {
        cout << "Animal 소멸자" << endl;
    }
};

class Dog : public Animal {
public:
    Dog() {
        cout << "Dog 생성자" << endl;
    }

    void makeSound() override {
        cout << "왈! 왈!" << endl;
    }

    ~Dog() {
        cout << "Dog 소멸자" << endl;
    }
};

class Cat : public Animal {
public:
    Cat() {
        cout << "Cat 생성자" << endl;
    }

    void makeSound() override {
        cout << "냥~ 냥~" << endl;
    }

    ~Cat() {
        cout << "Cat 소멸자" << endl;
    }
};

class Cow : public Animal {
public:
    Cow() {
        cout << "Cow 생성자" << endl;
    }

    void makeSound() override {
        cout << "음머~ 음머~" << endl;
    }

    ~Cow() {
        cout << "Cow 소멸자" << endl;
    }
};

class Zoo {
private:
    Animal* animals[10]; // 동물 객체를 저장하는 포인터 배열
    int size;             // 동물의 수

public:
    Zoo() : size(0) {}

    // 동물을 동물원에 추가하는 함수
    void addAnimal(Animal* animal) {
        if (size >= 10) {
            cout << "Zoo is full!" << endl;
            return;
        }
        animals[size++] = animal;
    }

    // 동물원에 있는 모든 동물의 소리와 행동을 출력하는 함수
    void performActions() {
        for (int i = 0; i < size; i++) {
            animals[i]->makeSound();
        }
    }

    // Zoo 소멸자: 동물 객체 메모리 해제
    ~Zoo() {
        for (int i = 0; i < size; i++) {
            delete animals[i];
        }
    }
};

Animal* createRandomAnimal() {
    int random = rand() % 3; // 0, 1, 2 중 랜덤 선택
    if (random == 0) {
        return new Dog();
    }
    else if (random == 1) {
        return new Cat();
    }
    else {
        return new Cow();
    }
}

int main() {
    srand(time(0)); // 랜덤 시드 초기화

    Zoo zoo1;

    // 동물 랜덤 생성 및 추가
    for (int i = 0; i < 5; i++) {
        zoo1.addAnimal(createRandomAnimal());
    }

    // 동물원에 있는 모든 동물들의 소리 출력
    zoo1.performActions();

    // Zoo 소멸자가 동적 할당된 메모리를 자동으로 해제합니다.
    return 0;
}
profile
Unreal 1기

0개의 댓글

관련 채용 정보