네, 코드 예제를 한 줄씩 설명하며 자세히 정리해 드릴게요.
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)을 위한 포인터를 추가로 포함하므로 크기가 증가합니다.결론: 가상 함수는 다형성을 위해 가상 함수 테이블을 사용합니다. 이를 통해 런타임에 올바른 함수를 호출할 수 있습니다.