정적 결합, 동적 결합

Jaemyeong Lee·2024년 11월 4일
0

FastCampusC++

목록 보기
70/78

네, 코드 예제를 한 줄씩 설명하며 자세히 정리해 드릴게요.

헤더 파일 (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;: "뚜벅"을 출력합니다.

자식 클래스 정의 (CatDog)

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 네임스페이스를 사용합니다.

클래스 A와 B 정의

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;: a0a1B 객체이지만, 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)을 위한 포인터를 추가로 포함하므로 크기가 증가합니다.

결론: 가상 함수는 다형성을 위해 가상 함수 테이블을 사용합니다. 이를 통해 런타임에 올바른 함수를 호출할 수 있습니다.

profile
李家네_공부방

0개의 댓글