본문 바로가기
C# 공부

C# 기본기 04 - 클래스, 상속, 제너릭

by 나노다 2025. 2. 10.

클래스

struct와 class 비교

둘 모두 사용자 정의 자료형을 만들기 위한 녀석들이지만, 분명한 차이가 있으니 사용에 주의해야함

  구조체 struct 클래스 class
보관되는 곳 스택 영역 힙 영역
할당 방식 값 저장 참조 저장
복사 방식 개별 복사 (복사 값 수정해도 기존 값 영향 X) 참조 복사 (복사 값 수정 시 기존 값도 변경 O)
상속 여부 불가능 가능

클래스의 접근 제한자

class Person
{
    /* 필드 */
    public string Name;         // 외부에서 자유롭게 접근 가능
    private int Age;           // 같은 클래스 내부에서만 접근 가능
    protected string Address;  // 같은 클래스 내부와 상속받은 클래스에서만 접근 가능
    /* 메서드 */
    public void Attack()
    {
        // 공격 동작 구현
    }
}
  • public은 어디서든 접근 가능, 대개 메서드는 public으로 선언
  • private는 소속 클래스 내부에서만 접근 가능, 대개 필드는 private로 선언
  • protected는 소속 클래스 및 그 클래스를 상속 받은 클래스에서만 접근 가능

생성자와 소멸자

1) 생성자 Constructor

  • 인스턴스를 생성할 때 함께 호출되는 특별한 메서드
  • 클래스와 이름이 같아야하고, void 타입이어야 함
  • 생성자도 오버로딩을 통해 다양한 매개변수 유형을 처리할 수 있음
class Person
{
    /* 필드 */
    private string name;
    private int age;
    /* 매개변수가 없는 생성자 예시 */
    public Person()
    {
        name = "Unknown";
        age = 0;
    }
    /* 매개변수를 받는 생성자 예시 */
    public Person(string newName, int newAge)
    {
        name = newName;
        age = newAge;
    }
    /* 메서드 */
    public void PrintInfo()
    {
        Console.WriteLine($"Name: {name}, Age: {age}");
    }
}

/* 인스턴스 생성 */
Person person1 = new Person();  // 디폴트 생성자 호출
Person person2 = new Person("John", 25);  // 매개변수를 받는 생성자 호출

2) 소멸자 Destructor

  • 인스턴스가 소멸될 때(=메모리에서 해제될 때) 자동으로 호출되는 특별한 메서드
  • 클래스와 이름이 같지만, 앞에 ~ 기호를 붙임
  • 매개변수를 가질 수 없으며, void 타입이어야 함
class Person
{
    private string name;
    /* 생성자 */
    public Person(string newName)
    {
        name = newName;
        Console.WriteLine("Person 객체 생성");
    }
    /* 소멸자 */
    ~Person()
    {
        Console.WriteLine("Person 객체 소멸");
    }
}

프로퍼티

  • 클래스의 필드 값을 읽거나 쓸 때 사용되는 접근자(Accesor) 메서드의 조합
  • 필드에 직접 접근할 수 없도록 제어할 수 있고, write 되는 데이터에 대한 유효성 검사 등을 수행 가능
  • get 접근자는 프로퍼티의 값을 반환하고, set 접근자는 프로퍼티의 값을 설정
  • 둘 중 하나만 작성함으로써 읽기 전용 또는 쓰기 전용 프로퍼티화 할 수 있음!!
/* 클래스 */
class Person
{
    private string name;
    private int age;

	// Name 필드는 외부에서 값을 설정할 수 없음
    public string Name
    {
        get { return name; }
        private set { name = value; }
    }
	// Age 필드에 값을 설정할 땐 0보다 큰 정수여야 함
    public int Age
    {
        get { return age; }
        set
        {
            if (value >= 0)
                age = value;
        }
    }
}

/* 인스턴스 */
Person person = new Person();
// set 접근자 실행
person.Name = "John";  // compile error : private 제한자는 외부에서 set할 수 없음
person.Age = -10;  // 음수는 set할 수 없음
// get 접근자 실행
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");

상속

기존 클래스를 확장하거나 재사용해 새로운 클래스를 생성하는 것

자식 클래스가 부모 클래스의 멤버들을 상속 후 확장하거나 수정해 활용 (멤버 : 필드, 메서드, 프로퍼티 등)

다형성 Polymorphism

같은 타입이지만 다양한 동작을 수행할 수 있는 능력

자식 클래스마다 재정의됨으로써 결이 같은 메서드여도 클래스에 따라 상이한 로직이 처리되도록 하는 특징

가상 메서드

  • 부모 클래스에서 virtual 키워드로 선언된 메서드는 자식 클래스에서 재정의될 수 있음
  • 자식 클래스에서 재정의되는 메서드는 override 키워드로 선언
/* 부모 클래스 */
public class Unit
{
    public virtual void Move()
    {
        Console.WriteLine("두발로 걷기");
    }
}

/* 자식 01 */
public class Marine : Unit
{	
    // Unit에게서 Move 상속
}

/* 자식 02 */
public class Zergling : Unit
{
    // Unit에게서 Move 상속, 가상 메서드인 Move를 저글링에 맞게 재정의
    public override void Move()
    {
        Console.WriteLine("네발로 걷기");
    }
}

추상 클래스와 추상 메서드

  • 추상 클래스는 직접 인스턴스를 생성할 수 없는 클래스 (상속 전용)
  • 추상 메서드는 추상 클래스에 선언할 수 있으며, 둘다 abstract 키워드를 통해 선언
  • 추상 메서드는 구현부가 없기 때문에, 자식 클래스에서 반드시 구현을 마쳐주어야 함
/* 추상 클래스 */
abstract class Shape
{
    public abstract void Draw(); // 구현부 {}가 없는 추상 메서드
}

/* 자식 클래스 */
class Circle : Shape
{
    public override void Draw() // 자식 클래스에서 상속받아 구현 마침
    {
        Console.WriteLine("Drawing a circle");
    }
}

제너릭 Generic

  • 클래스 또는 메서드를 일반화시켜 다양한 자료형에 대응할 수 있도록 하는 기능
  • <T> 키워드를 활용해 선언된 클래스나 메서드는 사용 시점에 자료형을 결정 (선언 시점 X)
  • 인스턴스를 생성하거나, 메서드를 호출할 때는 자료형을 지정해주어야 함
/* 제너릭 클래스 */
class Stack<T>
{
    // elements 배열에 담길 요소들의 자료형 -> 제너릭
    private T[] elements; 
    private int top;

    public Stack()
    {
        elements = new T[100];
        top = 0;
    }
    // Push 매개변수의 자료형 -> 제너릭
    public void Push(T item) 
    {
        elements[top++] = item;
    }
    // Pop 반환값의 자료형 -> 제너릭
    public T Pop() 
    {
        return elements[--top];
    }
}

/* 제너릭 클래스의 인스턴스를 생성할 땐 자료형을 지정해줘야 함 */
Stack<int> intStack = new Stack<int>(); // <T> -> <int>
intStack.Push(1);
intStack.Push(2);
Console.WriteLine(intStack.Pop()); // 출력 결과: 2