네, 코드 예제를 한 줄씩 설명하며 자세히 정리해 드릴게요.
Animal.h
)#pragma once
#pragma once
: 헤더 파일이 여러 번 포함되는 것을 방지하는 헤더 가드입니다. 즉, 컴파일 시 이 파일이 중복 포함되지 않게 합니다.#include <iostream>
#include <iostream>
: 표준 입출력을 사용하기 위해 포함하는 헤더 파일입니다. std::cout
을 사용하기 위해 필요합니다.class Animal
{
public:
virtual void eat() const
{
std::cout << "냠" << std::endl;
}
void walk() const
{
std::cout << "뚜벅" << std::endl;
}
};
class Animal
: Animal
이라는 기본 클래스를 정의합니다.public:
: 이후에 오는 멤버들은 외부에서 접근할 수 있게 공개로 설정됩니다.virtual void eat() const
: virtual
키워드가 붙어 있어 이 함수는 동적 결합(런타임에 결정)될 수 있습니다. Animal
을 상속받은 클래스에서 이 함수를 재정의할 수 있으며, 실제 객체의 타입에 따라 올바른 함수가 호출됩니다.std::cout << "냠" << std::endl;
: "냠"을 출력합니다.void walk() const
: walk()
함수는 가상 함수가 아니기 때문에 정적 결합(컴파일 타임에 결정)됩니다. 이 함수는 항상 Animal
클래스의 버전이 호출됩니다.std::cout << "뚜벅" << std::endl;
: "뚜벅"을 출력합니다.Cat
및 Dog
)class Cat : public Animal
{
public:
virtual void eat() const override
{
std::cout << "냥" << std::endl;
}
void walk() const
{
std::cout << "사뿐" << std::endl;
}
};
class Cat : public Animal
: Animal
클래스를 상속받아 Cat
클래스를 정의합니다.virtual void eat() const override
: Animal
클래스의 eat()
함수를 재정의합니다. "냥"을 출력합니다. override
는 부모 클래스의 함수를 재정의한다는 것을 명시합니다.void walk() const
: walk()
함수는 가상 함수가 아니기 때문에 정적 결합됩니다. "사뿐"을 출력합니다.class Dog : public Animal
{
public:
virtual void eat() const override
{
std::cout << "터벅" << std::endl;
}
void walk() const
{
std::cout << "뚜벅" << std::endl;
}
};
class Dog : public Animal
: Animal
클래스를 상속받아 Dog
클래스를 정의합니다.virtual void eat() const override
: Animal
클래스의 eat()
함수를 재정의합니다. "터벅"을 출력합니다.void walk() const
: "뚜벅"을 출력합니다. 정적 결합입니다.#include <iostream>
#include "Animal.h"
using namespace std;
#include "Animal.h"
: 헤더 파일을 포함합니다.using namespace std;
: std
네임스페이스를 사용합니다.class A
{
public:
int num;
};
class B : public A
{
};
class A
: A
라는 클래스 정의. num
이라는 정수형 멤버 변수를 가집니다.class B : public A
: A
클래스를 상속받는 B
클래스 정의.A operator+(const A& x, const A& y)
{
A a;
a.num = x.num + y.num;
return a;
}
B operator+(const B& x, const B& y)
{
B b;
b.num = x.num * y.num;
return b;
}
A operator+(const A& x, const A& y)
: A
객체에 대한 +
연산자를 오버로딩합니다. 두 객체의 num
값을 더합니다.B operator+(const B& x, const B& y)
: B
객체에 대한 +
연산자를 오버로딩합니다. 두 객체의 num
값을 곱합니다.void func(int x)
{
}
void func(int x, int y)
{
}
func(int x)
: 정수형 매개변수를 받는 함수입니다.func(int x, int y)
: 두 개의 정수형 매개변수를 받는 함수입니다.foo
함수)void foo(Animal* animal)
{
animal->eat(); // 동적 결합, 실제 객체의 타입에 맞는 eat() 호출
animal->walk(); // 정적 결합, Animal 클래스의 walk() 호출
}
animal->eat()
: eat()
함수는 가상 함수이므로 동적 결합됩니다. 즉, 런타임에 animal
이 실제로 어떤 타입인지에 따라 호출되는 eat()
함수가 달라집니다.animal->walk()
: walk()
함수는 정적 결합이므로 항상 Animal
클래스의 walk()
가 호출됩니다.main
함수int main()
{
B b0, b1;
b0.num = 10;
b1.num = 20;
A& a0 = b0;
A& a1 = b1;
A a2 = a0 + a1;
cout << a2.num << endl; // 30
Animal* animal = new Dog;
foo(animal);
}
B b0, b1;
: B
클래스의 객체 생성.A& a0 = b0; A& a1 = b1;
: B
객체를 A
클래스의 참조로 다룹니다. 오버로딩된 +
연산자는 정적 결합으로 인해 A
의 +
연산자가 호출됩니다.A a2 = a0 + a1;
: a0
과 a1
이 B
객체이지만, A
타입으로 취급되므로 A
의 +
연산자가 호출됩니다. 결과는 30
.Animal* animal = new Dog;
: Animal
포인터를 Dog
객체로 초기화합니다.foo(animal);
: 다형성으로 인해 Dog
클래스의 eat()
가 호출되지만, Animal
클래스의 walk()
가 호출됩니다.class A { int a; };
class B { int a; virtual ~B() {} };
class A
: 멤버 변수로 int
를 가집니다.class B
: int
멤버와 virtual
소멸자를 가집니다.sizeof(A)
: A
클래스의 크기를 출력합니다.sizeof(B)
: B
클래스의 크기를 출력합니다. B
클래스는 가상 함수 테이블(VFT)을 위한 포인터를 추가로 포함하므로 크기가 증가합니다.결론: 가상 함수는 다형성을 위해 가상 함수 테이블을 사용합니다. 이를 통해 런타임에 올바른 함수를 호출할 수 있습니다.