[강의] 11월 8일 수업정리
오늘도 상속과 일반함수, 가상함수 오버라이딩에 대한 수업을 들었다.
1. 상속
1.1 설명
상속은 부모 클래스를 통해 자식 클래스를 관리하기 위해 사용이 된다.
상속을 통해 자식 클래스 객체들을 부모 클래스 포인터나 참조로 다룰 수 있으며, 코드를 확장시켜 주는 역할을 한다.
그러나 클래스 포인터를 사용하면 부모 클래스에 정의된 멤버 함수만 호출할 수 있어 자식 클래스의 확장된 기능을 호출하기 위해서 가상 함수 오버라이딩이 필요하다.
1.2 예제
[header.h]
//animal.h
#pragma once
#include <stdio.h>
class C_ANIMAL
{
public:
void animal();
};
// cat.h
#pragma once
#include "animal.h"
class C_CAT : public C_ANIMAL
{
public:
void cat();
};
// dog.h
#pragma once
#include "animal.h"
class C_DOG : public C_ANIMAL
{
public:
void dog();
};
[header.cpp]
//animal.cpp
#include "animal.h"
void C_ANIMAL::animal()
{
printf("동물\n");
}
// cat.cpp
#include "cat.h"
void C_CAT::cat()
{
printf("고양이\n");
}
// dog.cpp
#include "dog.h"
void C_DOG::dog()
{
printf("개\n");
}
[main.cpp]
#include <iostream>
#include "cat.h"
#include "dog.h"
void func(C_ANIMAL* pAnimal);
int main()
{
C_DOG cDog{};
C_CAT cCat{};
cDog.animal();
cDog.dog();
cCat.animal();
cCat.cat();
}
void func(C_ANIMAL* pAnimal)
{
pAnimal->animal();
}
상속은 C_ANIMAL 클래스 포인터로 자식 객체들을 관리하는 데 도움을 준다. 그러나 자식 클래스에서 정의한 고유한 기능을 부모 클래스 포인터로 호출하려면 가상 함수 오버라이딩이 필요하다.
2. 일반함수 오버라이딩
2.1 설명
일반 함수 오버라이딩에서는 부모와 자식 클래스에서 동일한 이름의 함수를 정의해 호출 시 자식 클래스의 함수가 호출된다. 하지만 부모 클래스의 기능을 사용해야 하는 경우 명시적으로 부모 클래스의 네임스페이스를 지정해 부모클래스이름::함수이름()의 형태로 호출이 가능하다.
2.2 예제
#include <iostream>
class C_PARENT
{
public:
void print();
};
class C_CHILD : public C_PARENT
{
public:
void print();
};
int main()
{
C_CHILD cChild{};
cChild.print();
cChild.C_PARENT::print();
C_PARENT* pParent{};
pParent = &cChild;
pParent->print();
}
void C_PARENT::print()
{
printf("부모기능\n");
}
void C_CHILD::print()
{
printf("자식기능\n");
}
이처럼 자식 클래스 객체의 print() 함수 호출 시 자식 클래스 printf()가 우선 호출된다.
부모의 기능을 호출하기 위해선 cChild.C_PARENT::print();와 같이 부모의 이름을 명시해야 한다.
일반 함수 오버라이딩은 가상 함수를 배우기 위한 단계이며 실제로는 잘 사용되지 않는다.
3. 가상함수 오버라이딩
3.1 설명
가상 함수 오버라이딩은 부모 클래스의 가상 함수 (virtual 함수)를 자식 클래스에서 재정의하여, 부모 클래스 포인터로도 자식 클래스의 함수를 호출할 수 있도록 한다.
가상 함수 오버라이딩을 통해 다형성을 구현할 수 있으며, 이는 사용자 입장에서 클래스의 세부 구현을 알지 못하더라도 다양한 형태로 사용할 수 있게끔 해준다.
가상 함수를 사용하면 부모 클래스에 virtual을 붙이고, 자식 클래스에는 override를 명시하여 가독성을 높이고 오버라이딩 여부를 명확히 할 수 있다.
3.2 예시
[header.h]
// animal.h
#pragma once
#include <stdio.h>
class C_ANIMAL
{
public:
virtual void move();
};
// cat.h
#pragma once
#include "animal.h"
class C_CAT : public C_ANIMAL
{
public:
virtual void move() override;
};
// dog. h
#pragma once
#include "animal.h"
class C_DOG : public C_ANIMAL
{
public:
virtual void move() override;
};
[header.cpp]
// animal.cpp
#include "animal.h"
void C_ANIMAL::move()
{
printf("동물이 이동\n");
}
// cat.cpp
#include "cat.h"
void C_CAT::move()
{
printf("고양이가 이동\n");
}
// dog.cpp
#include "dog.h"
void C_DOG::move()
{
printf("개가 이동\n");
}
[main.cpp]
#include <iostream>
#include "cat.h"
#include "dog.h"
int main()
{
C_ANIMAL* arAnimal[5]{};
arAnimal[0] = new C_DOG{};
arAnimal[1] = new C_CAT{};
arAnimal[2] = new C_DOG{};
arAnimal[3] = new C_CAT{};
arAnimal[4] = new C_DOG{};
for (int i = 0; i < 5; i++)
{
arAnimal[i]->move();
}
}
3.2 설명
가상 함수 오버라이딩을 통해 부모 클래스 포인터로도 자식 클래스의 move() 함수가 호출이 된다.
이를 통해 배열처럼 여러 자식 클래스 객체를 같은 부모 클래스 포인터로 관리하고, 각 객체의 move()를 호출 가능하다.