C# 기초 정리 4 - 상속과 다형성

woollim·2024년 9월 22일
0

C#

목록 보기
4/11

1. 상속

○ 상속이란?

  • 개념
    • 상속은 기존의 클래스를 확장하거나 재사용하여 새로운 클래스를 생성하는 것
    • 자식 클래스는 부모 클래스의 멤버(필드, 메서드, 프로퍼티 등)를 상속받아 사용할 수 있음
    • 상속을 통해 부모 클래스의 기능을 확장하거나 수정하여 새로운 클래스를 정의할 수 있음
  • 장점
    • 코드의 재사용성
      : 상속을 통해 기존 클래스의 코드를 재사용할 수 있으므로, 반복적인 코드 작성을 줄일 수 있음
    • 계층 구조의 표현
      : 클래스 간의 계층 구조를 표현하여 코드의 구조를 명확하게 표현할 수 있음
    • 유지보수성의 향상
      : 상속을 통해 기존 클래스의 수정이 필요한 경우, 해당 클래스만 수정하면 됨. 이로써 코드의 유지보수성이 향상
  • 종류
    • 단일 상속
      : 하나의 자식 클래스가 하나의 부모 클래스만 상속받는 것. C#에서는 단일 상속만을 지원
    • 다중 상속
      : 하나의 자식 클래스가 여러 개의 부모 클래스를 동시에 상속받는 것. C#은 다중 상속을 지원하지 않음
    • 인터페이스 상속
      : 클래스가 인터페이스를 상속받는 것. 인터페이스는 다중 상속을 지원하며, 클래스는 하나의 클래스와 여러 개의 인터페이스를 동시에 상속받을 수 있음
  • 특징
    • 부모 클래스의 멤버에 접근
      • 자식 클래스는 상속받은 부모 클래스의 멤버에 접근할 수 있으며, 이를 통해 부모 클래스의 기능을 재사용할 수 있음
    • 메서드 재정의
      • 자식 클래스는 부모 클래스의 메서드를 재정의하여 자신에게 맞게 수정할 수 있음. 이를 통해 다형성(Polymorphism)을 구현할 수 있음
    • 상속의 깊이
      • 클래스는 다수의 계층적인 상속 구조를 가질 수 있음. 부모 클래스가 또 다른 클래스의 자식 클래스가 될 수 있으며, 이를 통해 상속의 계층 구조를 형성할 수 있음
      • 상속의 깊이가 깊어질수록 클래스 간의 관계가 복잡해질 수 있으므로, 적절한 상속의 깊이를 유지하고 상속을 적절하게 사용하는 것이 중요
// 부모 클래스
public class Animal
{
    public string Name { get; set; }
    public int Age { get; set; }

    public void Eat()
    {
        Console.WriteLine("Animal is eating.");
    }

    public void Sleep()
    {
        Console.WriteLine("Animal is sleeping.");
    }
}

// 자식 클래스
public class Dog
{

}

public class Cat
{

}


2. 다형성

○ 가상 (Virtual) 메서드

  • 가상 메서드는 기본적으로 부모 클래스에서 정의되고 자식 클래스에서 재정의할 수 있는 메서드
  • 가상 메서드는 virtual 키워드를 사용하여 선언되며, 자식 클래스에서 필요에 따라 재정의될 수 있음
  • 이를 통해 자식 클래스에서 부모 클래스의 메서드를 변경하거나 확장할 수 있음
public class Unit
{
    public virtual void Move()
    {
        Console.WriteLine("두발로 걷기");
    }

    public void Attack()
    {
        Console.WriteLine("Unit 공격");
    }
}

public class Marine : Unit
{

}

public class Zergling : Unit
{
    public override void Move()
    {
        Console.WriteLine("네발로 걷기");
    }
}
// 사용 예시
// #1 참조형태와 실형태가 같을때
Marine marine = new Marine();
marine.Move();
marine.Attack();

Zergling zergling = new Zergling();
zergling.Move();
zergling.Attack();

// #2 참조형태와 실형태가 다를때
List<Unit> list = new List<Unit>();
list.Add(new Marine());
list.Add(new Zergling());

foreach (Unit unit in list)
{
    unit.Move();
}


3. 추상 (Abstract) 클래스와 메서드

  • 추상 클래스는 직접적으로 인스턴스를 생성할 수 없는 클래스
  • 주로 상속을 위한 베이스 클래스로 사용
  • 추상 클래스는 abstract 키워드를 사용하여 선언되며, 추상 메서드를 포함할 수 있음
  • 추상 메서드는 구현부가 없는 메서드로, 자식 클래스에서 반드시 구현되어야 함
abstract class Shape
{
    public abstract void Draw();
}

class Circle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("Drawing a circle");
    }
}

class Square : Shape
{
    public override void Draw()
    {
        Console.WriteLine("Drawing a square");
    }
}

class Triangle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("Drawing a triangle");
    }
}
List<Shape> list = new List<Shape>();
list.Add(new Circle());
list.Add(new Square());
list.Add(new Triangle());

foreach (Shape shape in list )
{
    shape.Draw();
}


4. 오버라이딩과 오버로딩

○ 오버라이딩 (Overriding)

  • 부모 클래스에서 이미 정의된 메서드를 자식 클래스에서 재정의하는 것을 의미
  • 이는 상속 관계에 있는 클래스 간에 발생하며, 메서드의 이름, 매개변수 및 반환타입이 동일해야 함
  • 오버라이딩을 통해 자식 클래스는 부모 클래스의 메서드를 재정의하여 자신에게 맞는 동작을 구현할 수 있음
public class Shape
{
    public virtual void Draw()
    {
        Console.WriteLine("Drawing a shape.");
    }
}

public class Circle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("Drawing a circle.");
    }
}

public class Rectangle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("Drawing a rectangle.");
    }
}

Shape shape1 = new Circle();
Shape shape2 = new Rectangle();

shape1.Draw();  // Drawing a circle.
shape2.Draw();  // Drawing a rectangle.

○ 오버로딩 (Overloading)

  • 동일한 메서드 이름을 가지고 있지만, 매개변수의 개수, 타입 또는 순서가 다른 여러 개의 메서드를 정의하는 것을 의미
  • 오버로딩을 통해 동일한 이름을 가진 메서드를 다양한 매개변수 조합으로 호출할 수 있음
public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }

    public int Add(int a, int b, int c)
    {
        return a + b + c;
    }
}

Calculator calc = new Calculator();
int result1 = calc.Add(2, 3);         // 5
int result2 = calc.Add(2, 3, 4);      // 9


5. C++와 C#의 상속 차이점

○ 기본 상속 접근 지정자

  • C++
    • C++에서 클래스 상속 시 기본 접근 지정자는 private. 즉, 상속받은 멤버는 기본적으로 파생 클래스에서는 private로 취급
    • 만약 상속된 멤버를 외부에서 접근 가능하게 하려면 명시적으로 public으로 상속
class Base {
public:
    void Print() { std::cout << "Base class" << std::endl; }
};

// 기본적으로 private 상속
class Derived : Base {
    // Base::Print()는 private으로 상속되어 외부에서 호출할 수 없음
};

// public 상속
class PublicDerived : public Base {
    // Base::Print()는 public으로 상속됨
};
  • C#
    • C#에서는 기본 상속 접근 지정자는 public. 클래스가 상속될 때, 상속된 모든 public 및 protected 멤버는 파생 클래스에서도 동일하게 사용 가능
class Base {
public:
    void Print() { Console.WriteLine("Base class"); }
}

class Derived : Base {
    // Base의 Print 메서드는 여전히 public
}

○ 다중 상속

  • C++
    • C++는 다중 상속을 지원. 즉, 하나의 클래스가 여러 개의 부모 클래스를 상속받을 수 있음
  • C#
    • C#은 다중 상속을 지원하지 않음. 즉, 하나의 클래스는 하나의 부모 클래스만 상속받을 수 있음. 하지만 여러 인터페이스를 구현할 수 있음
class Base { /* ... */ }

class Derived : Base, IInterface1, IInterface2 {
    // 클래스는 하나만 상속 가능, 여러 인터페이스는 구현 가능
}

○ 가상 함수 및 오버라이딩

  • C++
    • C++에서는 가상 함수를 만들기 위해 virtual 키워드를 사용하고, 파생 클래스에서 이 함수를 오버라이드하려면 override 키워드를 사용할 수 있음
    • 부모 클래스의 가상 함수를 파생 클래스에서 다시 정의하지 않으면, 부모 클래스의 구현이 사용
class Base {
public:
    virtual void Print() { std::cout << "Base Print" << std::endl; }
};

class Derived : public Base {
public:
    void Print() override { std::cout << "Derived Print" << std::endl; }
};
  • C#
    • C#에서도 비슷한 방식으로 가상 메서드를 정의할 수 있음. virtual 키워드를 부모 클래스에서 사용하고, 파생 클래스에서 메서드를 오버라이드하려면 override 키워드를 사용
class Base {
public virtual void Print() {
    Console.WriteLine("Base Print");
}
}

class Derived : Base {
public override void Print() {
    Console.WriteLine("Derived Print");
}
}

○ 추상 클래스

  • C++
    • C++에서 추상 클래스는 적어도 하나 이상의 순수 가상 함수(pure virtual function)를 포함한 클래스
class Base {
public:
    virtual void Print() = 0; // 순수 가상 함수
};
  • C#
    • C#에서는 추상 클래스는 abstract 키워드를 사용하여 선언. 추상 클래스는 추상 메서드(구현 없이 선언된 메서드)를 포함할 수 있으며, 이는 파생 클래스에서 구현해야 함
abstract class Base {
public abstract void Print(); // 추상 메서드
}

○ 접근 제어자

  • C++

    • C++에서는 public, protected, private와 같은 접근 제어자가 있음. 또한 상속 시 접근 제어자가 어떻게 변경되는지에 따라, 상속된 멤버의 접근 범위가 달라질 수 있음
    • 상속 시 접근 제어자에 따라 부모 클래스의 멤버가 파생 클래스에서 어떤 접근 수준을 가지는지 결정 됨
  • C#

    • C#에서는 public, protected, private, internal, protected internal이 있으며, 상속 시 기본적으로 public 상속이 이루어지며, 접근 제어자는 상속된 멤버의 접근 수준을 유지함

○ 가상 소멸자

  • C++
    • C++에서는 소멸자가 가상 함수여야만 파생 클래스의 소멸자가 올바르게 호출 됨. 부모 클래스에 가상 소멸자를 명시적으로 선언하지 않으면, 파생 클래스의 소멸자가 호출되지 않으며 메모리 누수가 발생할 수 있음
class Base {
public:
    virtual ~Base() { std::cout << "Base destructor" << std::endl; }
};
  • C#
    • C#에서는 가상 소멸자가 필요 없음. C#의 가비지 컬렉터가 메모리를 자동으로 관리하므로, 소멸자에 대한 걱정 없이 객체를 사용하면 됨

○ 요약

  • 다중 상속 : C++는 다중 상속을 지원하지만, C#은 지원하지 않으며 인터페이스로 이를 대체
  • 기본 접근 제어자 : C++의 기본 상속 접근 제어자는 private, C#의 기본은 public
  • 메모리 관리 : C++은 수동으로 메모리를 관리하며, 가상 소멸자를 명시적으로 선언해야 함. C#은 가비지 컬렉션으로 자동 메모리 관리를 처리
  • 추상 클래스 : C++에서는 순수 가상 함수를 사용하고, C#에서는 abstract 키워드를 사용

0개의 댓글