왜.... 오셨나요?

부끄러워요

유니티개발 TIL

2주차 3일 TIL_C#기초-3(클래스)

와피했는데 2025. 7. 9. 21:11

<객체지향 프로그래밍(Object-Oriented Programming, OOP)의 특징>

1) 추상화 (Abstraction)

      ● 관련 특성 및 엔터티의 상호 작용을 클래스로 모델링하여 시스템의 추상적 표현을 정의

      ● 클래스가 추상적인 표현을 정의하는 경우 자식에서 구체화시켜 구현할 것을 염두하고 추상화(abstract) 시킴

      ● 추상클래스를 상속하는 자식클래스가 추상화한 함수를 재정의하여 실체화한 경우 사용가능

      ● 클래스나 인터페이스를 사용하여 실제 세계의 개념을 모델링하고, 필요한 부분에 대한 명세를 정의

   +예시

더보기
더보기
더보기
더보기
 public abstract class Animal
  {
      // 추상적인 클래스에서 구체화 할 수 없는 함수는 하나이상의 추상함수를 선언하고 내용을 정의하지 않음
 public abstract void Cry();
  }
  public class Bird : Animal
  {
      // 추상함수를 재정의하여 실체화한 경우 사용 가능 (반드시 넣어야함 자식클래스를 만들수있다)
      public override void Cry()
      {
          Console.WriteLine("짹짹");
      }
  }
  public class Dog : Animal
  {
      public override void Cry()
      {
          Console.WriteLine("멍멍");
      }
  }
  public void Test()
  {
      // Animal animal = new Animal();	// error : 추상클래스의 인스턴스는 생성불가
      Animal bird = new Bird();           // 추상클래스를 구체화시킨 자식클래스의 인스턴스는 생성가능
      Animal dog = new Dog();
      bird.Cry();
      dog.Cry();

      Katar katar = new Katar();
      katar.Attack();
  }

 

2) 캡슐화 (Encapsulation)

      ● 객체를 상태와 기능으로 묶는 것을 의미
          * 정보은닉 : 객체의 내부 상태와 기능을 숨기고, 허용한 상태와 기능만의 액세스 허용

      ● 객체를 상태와 기능으로 묶는 것, 객체의 상태와 기능을 맴버라고 표현함(맴버변수==상태, 맴버함수==기능)

      ● 현실세계의 객체를 표현하기 위해 객체가 가지는 정보와 행동을 묶어 구현하며 이를 통해 객체간 상호작용

      ● C# 의 맴버로는 맴버변수(필드), 맴버함수(메서드), 상수, 속성(프로퍼티), 생성자, 이벤트, 연산자, 인덱서 등이 있음

   +예시

더보기
더보기
더보기
더보기
public class Player
{
    public int hp;      // 외부에서 접근 가능
    private int mp;             // 접근제한자를 지정하지 않은 경우 기본접근제한자 private

    public Player(int hp, int mp)
    {
        this.hp = hp;
        this.mp = mp;
    }

    public void TakeDamage(int damage)
    {
        hp -= damage;
        // 피격음 재생
        // 넉백 등
    }

    public void UseMagic(int cost)
    {
        mp -= cost;
    }
}
public static class Encapsulation
{
    public static void Test()
    {
        Player player = new Player(100, 20);

        player.TakeDamage(20);
        player.UseMagic(10);

        player.hp = 0;      // public으로 선언한 경우 외부에서 접근 가능(의도에 벗어날 수 있음)
        // player.mp = 0;	// private로 선언한 경우 외부에서 접근 불가(public으로 의도한 사용만 허용)
    }
}

 

3) 상속 (Inheritance)

      ● 부모클래스의 모든 기능을 가지는 자식클래스를 설계하는 방법 (가능하면 모든기능을 공유할수있는 것으로 부모클래스에 넣어주면 좋다)
      ●  is-a 관계 : 부모클래스가 자식클래스를 포함하는 상위개념일 경우 상속관계가 적합함  // 생물학에 종분류같이 해주는게 좋음

   +예시

더보기
더보기
더보기
더보기
 internal class Inheritance
 {
     public static void Test()
     {
         Person person = new Person("Kim");
         Student student = new Student("Lee", 1);
         Worker worker = new Worker("Park");

         // 부모클래스 Person을 상속한 자식클래스는 모두 부모클래스의 기능을 가지고 있음
         person.SayName();
         student.SayName();
         worker.SayName();

         // 자식클래스는 부모클래스의 기능에 자식만의 기능을 더욱 추가하여 구현 가능
         student.Study();
         worker.Work();

         // 부모클래스에 자식 인스턴스를 담아둘 수 있음  
         Person studentInPerson = new Student("Choi", 2);
         Person workerInPerson = new Worker("Jung");

         // 자식클래스는 부모클래스의 모든 기능이 있기 때문에 부모의 기능 사용 가능 //부모자리를 대신하는 리스코프 치환이 가능하다
         studentInPerson.SayName();
         workerInPerson.SayName();

         // 부모클래스에 담은 자식 인스턴스는 자식 인스턴스만의 기능을 사용할 수 없음
         // studentInPerson.Study();		// error : studentInPerson은 Person에 담겨 있어 Study가 없음
         // workerInPerson.Work();		// error : workerInPerson은 Person에 담겨 있어 Worker가 없음

         // 자식클래스의 기능을 사용하고 싶다면 다시 자식클래스에 담아서 사용가능
         Student castStudent = (Student)studentInPerson;
         castStudent.Study();

         // 부모클래스에 담겨있는 자식 인스턴스가 자식클래스로 다시 자식클래스에 담는 것을 정적형변환으로 할 경우 위험할 수 있음
         // 담겨있던 인스턴스가 형변환이 불가한 경우 예외 발생
         // Student castFail = (Student)workerInPerson;		// error : 담겨있던 인스턴스가 형변환이 불가함

         // is : 담겨있는 인스턴스가 형변환이 가능한지 확인
         if (studentInPerson is Student)
         {
             Console.WriteLine("{0} 인스턴스는 Student로 형변환 가능");
             Student cast = (Student)studentInPerson;
         }
         else
         {
             Console.WriteLine("{0} 인스턴스는 Student로 형변환 불가");
         }

         if (studentInPerson is Worker)
         {
             Console.WriteLine("{0} 인스턴스는 Worker로 형변환 가능");
             Worker cast = (Worker)studentInPerson;
         }
         else
         {
             Console.WriteLine("{0} 인스턴스는 Worker로 형변환 불가");
         }

         // as : 담겨있는 인스턴스가 형변환이 가능하다면 형변환 결과를 불가하다면 null을 반환
         Student asStudent = studentInPerson as Student;     // 형변환
         Worker asWorker = studentInPerson as Worker;        // null 반환
     }
 }

 

4) 다형성 (Polymorphism)

      ● 부모클래스의 함수를 자식클래스에서 재정의하여 자식클래스의 서로 다른 반응을 구현

      ● 하나의 메서드 이름이 다양한 객체에서 다르게 동작할 수 있도록 하는 것으로, 오버로딩과 오버라이딩을 통해 구현됩니다.

      ● 하이딩(hiding) : 부모클래스의 함수를 자식클래스에서 같은 이름, 매개변수로 재정의하여 자식클래스가 우선되도록 함

      ● 오버라이딩(overriding) : 부모클래스의 가상함수를 자식클래스에서 override를 통해 재정의, 부모함수가 자식함수로 대체됨

   +예시

더보기
더보기
더보기
더보기
public class Monster
{
    public string name;

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

    public void Move()
    {
        Console.WriteLine("{0}이 움직입니다.", name);
    }

    public virtual void TakeDamage()    // 가상함수(virtual) : 자식에서 재정의할 경우 부모의 함수가 대체됨 
                                        // 부모클래스에서 이렇게 작동하되 자식클래스에서는 다르게 작동되길 원하면 사용
    {
        Console.WriteLine("{0}가 데미지를 받습니다.", name);
    }
}
public class Dragon : Monster
{
    public Dragon(string name) : base(name)
    {
    }

    // 하이딩 : 부모클래스의 함수를 자식클래스에서 같은 이름, 매개변수로 재정의하여 자식클래스가 우선되도록 함
    // 자식클래스를 부모클래스에 넣게될 경우 하이딩은 작동하지만 자식클래스가 우선적으로 되지는 않는다

    public new void Move()  // 부모클래스의 기능을 숨기는 것을 의도한 경우 new 키워드(없어도 동작하나 의도했다는 것을 명확하게 함)
    {
        Console.WriteLine("{0}이 날아서 움직입니다.", name);
    }

    // 오버라이딩
    public override void TakeDamage()
    {
        base.TakeDamage();      // base : 부모클래스의 this를 자식클래스에서 가져옴
        Console.WriteLine("{0}이 분노합니다", name);
    }
}

public void Test()
{
    Monster monster = new Monster("몬스터");
    Dragon dragon = new Dragon("드래곤");
    monster.Move();
    dragon.Move();

    Monster dragonInMonster = dragon;
    dragonInMonster.Move();
    dragonInMonster.TakeDamage();
    Console.WriteLine();
        
}
<객체설계 5원칙> (지향 5원칙)
  ● (S)단일 책임 원칙 : 객체는 오직 하나의 책임을 가져야 함(300줄이하 소스로)
     // 여러책임을 관리하는 객체를 수정할때 복잡함을 덜기위해 수정 및 관리의 용이성을 위해 분산으로 구조를 짜는걸 추천한다
  ● (O)개방 폐쇄 원칙 : 객체는 확장에 대해서는 개방적이고 수정에 대해서는 폐쇄적이어야 함
    // 어디에 써도 잘되는 객체면서 수정이 굉장히 적은 객체면 좋음
  ● (L)리스코프 치환 원칙 : 자식클래스는 언제나 자신의 부모클래스를 대체할 수 있어야 함
    // 부모 - 자동차, 자식 - 차종류들 //자식클래스에서 부모클래스에서 기대할수있는 것이 나와야한다
  ● (I)인터페이스 분리 원칙 : 인터페이스(동작단위)는 작은 단위들로 분리시켜 구성하며, 사용하지 않는 함수는 포함하지 않아야 함
  ● (D)의존성 역전 원칙 : 추상성이 높고 안정적인 고수준의 클래스는 구체적이고 불안정한 저수준의 클래스에 의존하지 않아야 함   
    // 부모클래스가 자식클래스에 의존하지 않는것 처럼 되는 상황을 만들면 안됨
  ● (S)단일 책임 원칙 : 10줄짜리 50개와 500개 짜리 하나중 10줄짜리 50개가 더 나을수있음

클래스 (class)
  ● 데이터와 관련 기능을 캡슐화할 수 있는 참조 형식
  ● 객체지향 프로그래밍에 객체를 만들기 위한 설계도  //절차지향이란 위에서 부터 아래로 차례로 절차를 행하는 것을 말한다
  ● 클래스는 객체를 만들기 위한 설계도이며, 만들어진 객체는 인스턴스라 함
  ● 참조 : 원본을 가리키고 있음 == 원본의 주소를 가지고 있음

 

<클래스 구성>

    ◆ class 클래스이름 {클래스내용}

class 클래스이름 
{클래스내용}

 

  ● 예시

더보기
더보기
더보기
더보기
class Student
{
    public string name;
    public int math;
    public int english;
    public float GetAverage()
    {
        return (math + english) / 2f;
    }
}
static void Main(string[] args)
{
    Student student = new Student();                //인스턴스의 변수에 접근하기 위해 변수에
    student.name = "김감자";
    student.math = 5;
    student.english = 10;
    student.GetAverage();                          // 인스턴스의 함수에 접근하기 위해 변수에
}

 

  1) 생성자

      ● 반환형이 없는 클래스이름의 함수를 생성자라 하며 클래스의 인스턴스를 만들 때 호출되는 역할로 사용
      ● 힙영역에 초기화 값을 생성하는 것을 말함
      ● 생성자를 포함하지 않아도 기본 생성자(매개변수가 없는 생성자)는 자동으로 생성됨
      ● 기본 생성자 외 매개변수가 있는 생성자를 포함한 경우 기본 생성자는 자동으로 생성되지 않음

 

  ● 예시

더보기
더보기
더보기
더보기
 class Player
 {
     public string name;
     public int hp;
     public int mp;

     // 기본 생성자는 다른 생성자를 포함하지 않은 경우만 기본 생성됨, 생성자로 초기화 하지않아도 문제는 없다
     // 하나라도 생성하면 기본생성자는 생성안됨
     /*public Player()
		{
		}*/

     public Player(string name, int hp)
     {
         // 생성자에서 초기화 하지 않은 맴버 변수는 기본값으로 초기화됨
         this.name = name;
         this.hp = hp;
     }
 }

 static void Test3()
 {
     Player player = new Player("김감자", 20);
 }

<값형식 참조형식>

 

  1) 값형식

      ● 스택영역에 저장, 정적으로 메모리에 할당
      ● 복사할 때 실제값이 복사됨, 블록이 끝날때 소멸
      ● struct는 값형식

 

  2) 참조형식

      ● 힙영역에 저장, 동적으로 메모리에 할당
      ● 복사할 때 원본주소가 복사됨, 사용하지 않을때 가비지 컬렉터에 의해 소멸
      ● class는 참조형식

 

  ● 값형식과 참조형식 예제

더보기
더보기
더보기
더보기
static void Swep(int left, int right)
{
    int temp = left;
    left = right;
    right= temp;    

}

static void Swep(RefType left, RefType right) // 구조체의 경우 함수내용을 바뀌는 방식으로 해도 값은 변하지 않는다
{
    int temp = left.value;
    left.value = right.value;
    right.value = temp;
}

 

 

  3) 값형식과 참조형식의 차이

      ● 값형식 : 데이터가 중요, 값이 복사됨
      ● 참조형식 : 원본이 중요, 원본 주소가 복사됨

 

  ● 값형식과 참조형식 차이점 예제

더보기
더보기
더보기
더보기
struct ValueType
{
    public int value;
}

class RefType
{
    public int value;
}

static void Main2()
{
    ValueType valueType1= new ValueType() { value = 10 };
    ValueType valueType2 = valueType1;              // 값에 의한 복사
    valueType2.value = 20;
    Console.WriteLine(valueType1.value);            // output : 10으로 구조체는 처음 값이 변하지 않는다

    RefType refType1 = new RefType() { value = 10 };
    RefType refType2 = refType1;                    // 원본의 주소가 복사댐
    // RefType refType2 = null;                     // 값을 지정안하고 기본값을 사용한다는 내용임
    refType2.value = 20;
    Console.WriteLine(refType2.value);              // output : refType1, 2 둘다 20으로 바뀜

}

 

  4) ref

      ● ref키워드를 통해 값형식을 원본으로 참조하는 방법을 지원함

  ● 예제

더보기
더보기
더보기
더보기
 static void Swep2(ref int left, ref int right) //ref를 넣으면 원본도 바뀜
 {
     int temp = left;
     left = right;
     right = temp;
 }

 

 

4) 박싱, 언박싱

      ● 박싱 : 값형식의 데이터를 참조형식으로 변환하는 방법
      ● 언박싱 : 참조형식의 데이터를 값형식으로 변환하는 방법
      ● 형변환된 값에 다른 값이 들어올경우 프로그램 오류가 발생한다

  ● 예제

더보기
더보기
더보기
더보기
        static void Swep3(object left, object right)
        {
            object a = 1;                   // object에 넣는건 박싱
            object b = true;
            object c = 1.1;
            object str = "감자";

            int temp = (int)left;           // 오브젝트의 형변환을 하는건 언박싱
            right = left;
            left = temp;
        }
        static void Test()                          // 이 경우 함수에 값을 특정할수없음
                                                    // 이렇게 runtime 도중 값을 할당하는 값은 heep영역에 할당해서 값을 불러오는식으로 해야함
                                                    // class에 짜야함
        {
            Console.Write("숫자를 입력하세요 : ");
            string str = Console.ReadLine();
            int[] life = new int[int.Parse(str)];
        }

        //출력하면 두자리가 바뀌어있음
        static void Main3()
        {
            // 데이터가 중요한 상황 (원본 체력값이 변하지 않길바란다면)에서는 구조체로 작성하는게 좋음
            RefType a = new RefType() { value = 10 };
            RefType b = new RefType() { value = 20 };  // new RefType()라고 쓰면 힙영역에 저장된다
            Swep(a, b);
            Console.WriteLine(a);
            Console.WriteLine(b); 
        }

일반화 (Generic)
  ● 클래스 또는 함수가 코드에 의해 선언되고 인스턴스화될 때까지 형식의 사양을 연기하는 디자인
  ● 거의 모든 부분이 동일하지만 일부 자료형만이 다른 경우 사용

  ● 예제

더보기
더보기
더보기
더보기
       public static void ArrayCopy<T>(T[]source, T[]output)
       {
           output = new T[source.Length];
           for (int i = 0; i < source.Length; i++)
               output[i] = source[i];
       }

  1) 일반화 자료형 제약

      ● 일반화 자료형을 선언할 때 제약조건을 선언하여, 일반화가 가능한 자료형을 제한
      ● 클래스 객체생성하는것 처럼 특정 연산에 대한 자료형으로 제약할수있음

  ● 예제

더보기
더보기
더보기
더보기
class StructClass<T> where T : struct { }
class ClassClass<T> where T : class { }
class ParentClass{ }
class ChildClass<T> where T: ParentClass { }
class InterfaceClass<T> where T : IComparable { }
class InterfaceClass1<T> where T : ParentClass, IComparable { }
class InterfaceClass2<T> where T : IComparable, IEnumerable { }

 

2.

 public class Monster
 {
     public void Attack()
     {
         Console.WriteLine("Start");
     }
 }
 
  public void Test3<T>(T param) where T : Monster
 {
     param.Attack();      
 }