[C#] 클래스와 객체

장민제·2025년 4월 15일

C# Study

목록 보기
6/11
post-thumbnail

객체지향 프로그래밍 ? (OOP, Object-Oriented Programming)

  • 프로그래밍 패러다임 중 하나로 프로그램을 여러 개의 독립된 단위 '객체'들의 모임으로 파악하고, 객체끼리 상호작용하도록 프로그래밍하는 방식.

객체지향 프로그래밍의 특징

특징설명예시
캡슐화ㅤㅤ데이터(속성)와 기능(메서드)을 하나로 묶고, 외부에서 직접 접근하지 못하도록 보호클래스 내부의 private 필드와 이를 제어하는 public 메서드 사용
상속기존 클래스의 특성을 물려받아 새로운 클래스를 만드는 것class Warrior : Character (Character 클래스를 상속받는 Warrior)
다형성같은 이름의 메서드가 상황에 따라 다르게 동작하는 것Attack() 메서드가 각 캐릭터마다 다르게 구현될 수 있음
추상화불필요한 세부 사항은 숨기고 핵심적인 부분만 보여주는 것인터페이스나 추상 클래스를 통해 필요한 기능만 노출

📃 클래스 ? (Class)

  • 클래스는 객체(Object)를 만들기 위한 설계도
  • 하나의 클래스는 데이터(변수, 필드)동작(메서드)을 묶어서 하나의 틀로 정의

게임을 만든다면 클래스캐릭터의 설계도
객체는 그 설계도로 만들어진 진짜 캐릭터 (실제 동작을 수행)

구성요소설명예시
필드(Field)객체가 가지는 변수 데이터string name, int hp
메서드(Method)객체가 할 수 있는 동작(기능)Attack(), Move()
생성자(Constructor)객체를 만들 때 자동으로 실행되는 메서드Player(string name)
속성(Property)필드를 간접적으로 접근하는 기능public int HP { get; set; }
public class Player
{
	public string name;
    public int hp;
    public int level;
    
    // 생성자(Constructor)
    public Player(string nameInput)
    {
    	name = nameInput;	// 이름을 매개변수로 받아 이름 지정
        hp = 100;	// hp 값 literal 값으로 초기화 (기본 체력 설정)
    }
    
    // 메서드(Attack) - 동작
    public void Attack()
    {
    	Console.WriteLine($"{name}이(가) 공격!");
    }
}

// 사용 예시
public static void Main()
{
	Player player1 = new Player("Hero"); // 객체 생성
	player1.Attack(); // 객체안에 정의된 메서드 사용
}

>>> Hero이() 공격!

클래스를 왜 사용하는가 ?

  • 관련된 데이터와 기능을 묶어서 재사용 가능
    -> Player, Monster, Item 과 같은 클래스를 각각 만들고, 필요한 곳에서 객체로 생성해서 사용
  • 유지보수가 용이하다
    -> 기능이 클래스로 잘 나뉘어져 있으면 수정, 디버깅, 확장이 쉬워진다.
  • 코드 재사용 가능
    -> 클래스를 재사용하면 비슷한 객체도 빠르게 생성 가능

클래스와 구조체 모두 코드의 재사용성유지보수에 용이한 사용자 정의 데이터 타입
그렇다면 차이점은?

클래스 vs 구조체 차이

항목클래스 (Class)구조체 (Struct)
형식참조 타입 (Reference Type)값 타입 (Value Type)
메모리 저장 위치힙(Heap)에 저장, 변수는 참조를 가짐스택(Stack)에 저장
상속다른 클래스 상속 가능상속 불가
생성자사용자 정의 생성자 + 기본 생성자 존재 가능기본 생성자 자동 제공 (사용자 정의 생성자 가능)
성능객체가 커지면 성능 저하 가능크기가 작고 단순할 때 성능 유리
사용 용도상태 유지가 필요한 복잡한 객체 표현에 적합간단한 데이터 집합 표현에 적합

클래스는 동작이 많은 복잡한 객체를 만들 때
구조체는 작고 가벼운 데이터 묶음을 만들 때

생성자 (Constructor) & 소멸자 (Destructor)

🔧 생성자 ?

  • 객체가 생성 될 때 자동으로 호출되는 메서드
  • 주로 필드 초기화에 사용
  • 클래스 이름과 동일한 이름을 가지며, return 타입❌

생성자의 특징

  • 여러 개의 생성자 오버로딩 가능
  • 기본 생성자(Default Constructor)는 매개변수가 없는 생성자
  • 생성자를 정의하지 않으면 컴파일러가 기본 생성자 자동 생성
public class Player
{
	public string name;
    public int hp;
    public int level;
    
    // 기본 생성자
    public Player()
    {
    	name = "Player";	
        hp = 100;
        level = 1;
    }
    
    // 오버로딩된 생성자
    public Player(string nameInput, int hpInput, int levelInput)
    {
    	name = nameInput;
        hp = hpInput;
        level = levelInput;
    }    
}

// 사용 예시
public static void Main()
{
	Player player1 = new Player(); // 기본 생성자로 객체 생성
	Player player2 = new Player("Hero", 300, 30); // 오버로딩된 생성자로 객체 생성
}

🧹 소멸자 ?

  • 객체가 메모리에서 제거될 때 자동 호출되는 메서드
  • C#에서는 잘 사용되지 않지만(GC), 리소스 해제가 필요할 때 유용
  • 클래스 이름 앞에 '~'를 붙여 선언
  • 매개변수를 가질 수 없고, 오버로딩 불가능
public class Player
{
    public string name;

    // 생성자
    public Player(string name)
    {
        this.name = name;
        Console.WriteLine($"{name} 생성됨!");
    }

    // 소멸자
    ~Player()
    {
        Console.WriteLine($"{name} 소멸됨!");
    }
}

프로퍼티 (Property) ?

  • 필드에 대한 접근을 제어하는 메서드와 변수의 중간 형태
  • getset을 통해 값을 읽고, 쓰는 로직을 캡슐화 가능

◾ 프로퍼티의 기본 구조

private int _hp; // private 필드

public int Hp	// public 프로퍼티
{
	get { return _hp; } // 읽기 (값 가져오기)
    set{ _hp = value; } // 쓰기 (값 설정)
}

◾ 자동 구현 프로퍼티

public string Name { get; set; }
  • 따로 필드를 만들지 않아도 자동으로 처리
  • 기본적인 get/set 동작만 필요할 때 유용하다.

◾ 읽기 전용 / 쓰기 전용 프로퍼티

// 읽기 전용
public int Level { get; private set; }

// 쓰기 전용
public int Score { private get; set; }

◾ 사용 예시

public class Player
{
    public string Name { get; set; } // 자동 구현 프로퍼티
    private int _hp;

    public int Hp
    {
        get { return _hp; }
        set
        {
            _hp = Math.Max(0, value); // 체력 설정 시 0 미만의 값 0으로 고정
        }
    }
}

// 사용
Player player = new Player();
player.Name = "용사";
player.HP = -10;
Console.WriteLine(player.HP);

>>> 0

클래스 내부의 private 필드에 보다 안전하고 유연하게 데이터 제어를 위해 사용한다.


상속(Inheritance)과 다형성(Polymorphism)

🧬 상속 ?

  • 기존 클래스(부모 클래스)의 속성과 기능을 새로운 클래스(자식 클래스)가 물려받는 것.
  • 상속을 통해 부모 클래스의 기능을 확장수정하여 새로운 클래스 정의 가능

상속을 사용하는 이유?
->역시나 목적은 코드의 재사용성유지보수성의 향상을 위함!

🎭 다형성 ?

  • 하나의 부모 클래스(또는 인터페이스)로부터 여러 가지 형태의 동작을 구현하는 것.
  • 메서드 오버라이딩(재정의)를 통해 구현된다.

상속과 다형성이 어떤 역할을 하는지는 알겠는데, 뭐 어떻게 굴러가는 것인가?

가상 메서드(virtual) 을 이용한 클래스의 상속 예제

// 부모 클래스
public class PersonCharacter
{
    public string name;
    public int hp;
    public int attackPower;

    // 생성자
    public PersonCharacter(string name, int hp, int attackPower)
    {
        this.name = name;
        this.hp = hp;
        this.attackPower = attackPower;
    }

    // 일반 메서드 - 모든 자식 클래스가 이동 가능
    public void Move()
    {
        Console.WriteLine($"{name}이(가) 이동합니다.");
    }

    // 가상 메서드 - 자식 클래스에서 override 키워드를 통해 다르게 구현 가능
    public virtual void Attack()
    {
        Console.WriteLine($"{name}이(가) 칼을 휘둘러 공격!");
        Console.WriteLine($"데미지: {this.attackPower}");
    }
}

public class Archer : PersonCharacter
{
    public Archer(string name, int hp, int attackPower) : base(name, hp, attackPower) { }
	
    // 공격 메서드 재정의
    public override void Attack()
    {
        Console.WriteLine($"{name}이(가) 화살을 쏴서 공격!");
        Console.WriteLine($"데미지: {this.attackPower}");
    }
}

public class Orc : PersonCharacter
{
    public Orc(string name, int hp, int attackPower) : base(name, hp, attackPower) { }
	
    //공격 메서드 재정의
    public override void Attack()
    {
        Console.WriteLine($"{name}이(가) 소리를 지르며 돌진합니다!");
        Console.WriteLine($"데미지: {this.attackPower}");
    }
}

위와 같은 부모 클래스와 그것을 상속받은 자식 클래스를 정의하고

public static void Main()
{
	// 배열의 타입은 부모 클래스타입이지만 
    // 자식 객체 참조하여 사용가능
    PersonCharacter[] party = new PersonCharacter[]
    {
            new Archer("Archer Robin", 100, 300),
            new Orc("Orc Graw", 500, 100)
    };

    foreach (var character in party)
    {
        character.Move();        // 부모 클래스의 공통 메서드 사용
        character.Attack();      // 자식 클래스에서 재정의한 메서드 실행 (다형성)
    }
}

>>> Archer Robin이() 이동합니다.
>>> Archer Robin이() 화살을 쏴서 공격!    
>>> 데미지: 300
>>> Orc Graw이() 이동합니다.
>>> Orc Graw이() 소리를 지르며 돌진합니다!
>>> 데미지: 100

생성자를 통해 원하는 값으로 객체를 생성 후, 같은 메서드 이름으로 다른 동작을 수행하게 할 수 있다.

PersonCharater(부모 클래스)를 상속받는 Archer, Orc
virtual / override 키워드를 통해 같은 메서드이지만 다르게 동작하도록 구현

  • virtual : 부모 클래스에서 해당 메서드가 자식에서 재정의될 수 있도록 허용
  • override : 자식 클래스에서 부모의 virtual 메서드를 새롭게 구현

추상 클래스(Abstract Class)

  • 객체를 직접 생성할 수 없는 클래스
  • 하나 이상의 추상 메서드(abstract method)를 포함하며, 자식 클래스는 해당 메서드를 반드시 구현해야한다.
  • 공통된 기능은 미리 정의, 자식 클래스마다 달라질 부분은 추상 메서드
// 추상 클래스
public abstract class Animal
{
    public string name;

    public Animal(string name)
    {
        this.name = name;
    }

    // 일반 메서드 - 모든 동물에게 공통되는 기능
    public void Breathe()
    {
        Console.WriteLine($"{name}이(가) 숨을 쉽니다.");
    }

    // 추상 메서드 - 자식이 반드시 구현해야 함
    public abstract void MakeSound();
}

// 자식 클래스 - Dog
public class Dog : Animal
{
    public Dog(string name) : base(name) { }
	
    // 추상 메서드 구현
    public override void MakeSound()
    {
        Console.WriteLine($"{name}이(가) 멍멍 짖습니다!");
    }
}

// 자식 클래스 - Cat
public class Cat : Animal
{
    public Cat(string name) : base(name) { }
	
    // 추상 메서드 구현
    public override void MakeSound()
    {
        Console.WriteLine($"{name}이(가) 야옹 울어요!");
    }
}

// 사용 예시
public static void Main()
{
    Animal dog = new Dog("뽀삐");
    Animal cat = new Cat("나비");

    dog.Breathe();
    dog.MakeSound();

    cat.Breathe();
    cat.MakeSound();
}

>>>뽀삐이() 숨을 쉽니다.
>>>뽀삐이() 멍멍 짖습니다!
>>>나비이() 숨을 쉽니다.
>>>나비이() 야옹 울어요!

공통된 기능을 가지지만, 자식 클래스에게 일부 메서드의 구현을 강제할 때 사용

  • abstract class : 추상 클래스 선언 키워드 (직접 인스턴스 생성 불가)
  • abstract mathod : 자식 클래스가 반드시 override 해야하는 메서드

profile
Unity, C#

0개의 댓글