김김김의 게임개발
  • C# #5 클래스, 객체, 상속, 다형성
    2023년 08월 16일 19시 35분 43초에 업로드 된 글입니다.
    작성자: noun06

    클래스와 객체

    • 데이터와 데이터를 조작하는 메서드를 묶어서 하나의 단위로 정의하는 객체 생성을 위한 일종의 설계도.
    • 클래스는 속성(멤버 변수, 필드)과 동작(메서드)을 가짐.
    //클래스의 구조
    
    [접근 제한자] class ClassName
    {
        // 필드 (클래스의 데이터)
        // 메서드 (클래스의 동작)
    }

     

    • 객체 : 객체는 클래스를 기반으로 생성된 모든 것.
    • 인스턴스 : 클래스를 기반으로 생성된 실제 객체.
    • "클래스명 식별자 = new 클래스명();" 을 통해 객체 생성(메모리 공간을 할당 받음)
    • 필드(Fields) : 클래스나 구조체 내에서 객체의 상태(데이터)를 저장하는 변수.
    • 메서드(Methods) : 클래스나 구조체 내에서 객체의 동작(기능)을 정의하는 함수.
    class Person
    {
        //필드
        public string Name;
        public int Age;
        
        //메서드
        public void PrintInfo()
        {
        	Console.WriteLine("이름: " + "Name" + ", 나이: " + Age);
        }
    }
    
    // Person 클래스를 기반으로 생성된 인스턴스 person1, person2
    //클래스의 필드와 메서드를 가지고 있음.
    Person person1 = new Person();
    person1.Name = "Alice";
    person1.Age = 30;
    person1.PrintInfo();
    
    Person person2 = new Person();
    person2.Name = "Bob";
    person2.Age = 25;
    person2.PrintInfo();

     

    • 클래스와 구조체의 차이 : 
    클래스(복잡한 데이터와 동작 포함 시 유용) 구조체(간단한 데이터 구조나 값 타입을 나타낼 때 유용)
    참조 타입(Reference Type)으로 분류.객체가 생성되었을 때
    변수는 실제 객체의 주소를 참조.
    값 타입(Value Type)으로 분류. 구조체 변수는 실제 데이터를 직접 가짐. 다른 변수에 할당될 때 해당 데이터 복사.
    상속 가능. 다른 클래스를 기반으로 새 클래스를 생성하거나 확장할 수 있음. 상속 불가능.
    힙(heap) 메모리에 할당.  스택(stack) 메모리에 할당. 
    클래스 객체는 null값을 가질 수 있음. 값 타입으므로 null값을 가질 수 없음.

     

    접근 제한자(Access Modifier)

    • 클래스, 필드, 메서드 등의 접근 가능한 범위를 지정하는 키워드.
    접근 제한자 설명
    private 클래스 내부에서만 접근 가능.
    public 외부에서 자유롭게 접근 가능.
    protected 클래스 내부와 상속받은 클래스에서만 접근 가능.
    internal 같은 어셈블리에서만 public으로 접근 가능.

     

    생성자와 소멸자

    • 생성자(Constructor) : 객체가 생성될 때 호출되는 특별한 메서드.
    • 클래스의 인스턴스를 초기화하고 필요한 초기값을 설정하는 역할을 수행.
    • 생성자는 클래스와 동일한 이름을 가지며 메서드를 호출하지 않아도 자동으로 호출됨.
    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);           // 매개변수를 받는 생성자 호출
    //매개변수에 따라 다른 생성자 호출(오버로딩)

     

    • 소멸자(Destructor) : 객체가 소멸될 때 호출되는 특별한 메서드.
    • 해당 객체의 메모리와 리소스를 해제하거나 정리하는 역할을 수행.
    • 소멸자는 클래스 이름 앞에 '~' 기호를 붙여 정의되며 메모리에서 해제될 때 자동으로 호출됨.
    class Person
    {
        private string name;
    	
        //생성자
        public Person(string newName)
        {
            name = newName;
            Console.WriteLine("Person 객체 생성");
        }
    	
        //소멸자
        ~Person()
        {
            Console.WriteLine("Person 객체 소멸");
        }
    }

     

    프로퍼티(Property)

    • 필드에 간접적으로 접근하는데 사용되는 메서드. 'get'을 통해 필드의 값을 읽고, 'set'을 통해 필드의 값을 설정.
    • 필드에 대한 접근 제어와 데이터 유효성 검사 등의 역할을 수행. 
    [접근 제한자] [데이터 타입] 프로퍼티명
    {
        get
        {
            // 필드를 반환하거나 다른 로직 수행
        }
        set
        {
            // 필드에 값을 설정하거나 다른 로직 수행
        }
    }
    
    class Person
    {
        //필드
        private string name;
        private int age;
    	
        //프로퍼티
        public string Name
        {
            get { return name; }
            set { name = value; }
        }
    	
        //프로퍼티
        public int Age
        {
            get { return age; }
            set 
            {
            	if (value >= 0)
            	age = value;
            }
        }
    }
    
    //사용 예시
    Person person = new Person();
    person.Name = "John";   // Name 프로퍼티에 값 설정
    person.Age = -10; // 유효성 검사에 의해 나이 값이 설정되지 않음.
    Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");

     

    • 자동 프로퍼티(Auto Property) : 프로퍼티를 간단하게 정의하는 방법으로 get/set 블록을 작성하지 않고도 프로퍼티 정의가 가능하여 필드의 직접 접근을 방지하면서 코드의 간결성을 높힘.
    [접근 제한자] [데이터 타입] 프로퍼티명 { get; set; }
    
    class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
    
    //사용 예시
    Person person = new Person();
    person.Name = "John";     // 값을 설정
    person.Age = 25;          // 값을 설정
    
    Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");  // 값을 읽어 출력

     

    상속(Inheritane)

    • 한 클래스가 다른 클래스(부모 클래스, 상위 클래스)의 멤버(특성과 동작)를 물려받는 것.
    • 상속들 통해 부모 클래스의 기능을 확장하거나 수정하여 새로운 클래스를 정의. (private 선언 멤버는 상속 불가능)
    // 부모 클래스
    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 : Animal
    {
        public void Bark()
        {
            Console.WriteLine("Dog is barking.");
        }
    }
    
    public class Cat : Animal
    {
        public void Sleep()
        {
            Console.WriteLine("Cat is sleeping.");
        }
    
        public void Meow()
        {
            Console.WriteLine("Cat is meowing.");
        }
    }
    
    // 사용 예시
    Dog dog = new Dog();
    dog.Name = "Bobby";
    dog.Age = 3;
    dog.Eat();      // Animal is eating.
    dog.Sleep();    // Animal is sleeping.
    dog.Bark();     // Dog is barking
    
    Cat cat = new Cat();
    cat.Name = "KKami"; 
    cat.Age = 10;
    cat.Eat();
    cat.Sleep(); //Cat is sleeping. (메서드 재정의)
    cat.Meow();

     

    다형성(Polymorphism)

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

     

    • 가상 메서드(Virtual Method) : 부모 클래스에서 정의되고 자식 클래스에서 재정의할 수 있는 메서드
    • 'virtual' 키워드를 통해 선언되며 자식 클래스에서 'override' 키워드를 사용하여 재정의.
    • 부모 클래스의 기본 구현을 자식 클래스에서 수정하거나 확장해야 할 때 사용. 
    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<Unit> list = new List<Unit>();
    list.Add(new Marine());
    list.Add(new Zergling());
    
    foreach (Unit unit in list)
    {
        unit.Move();
    }

     

    • 추상 메서드(Abstract Method) : 선언만 되고 본체가 없는 메서드이며 반드시 자식 클래스에서 재정의되어야 함.
    • 'abstract' 키워드를 통해 선언되며 자식 클래스에서 'override' 키워드를 사용하여 재정의.
    • 구체적인 동작을 자식 클래스에서 구현하도록 하고 싶을 때 사용. 
    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();
    }

     

    • 오버라이딩(Overriding) : 부모 클래스에서 정의된 가상/추상 메서드를 하위 클래스에서 재정의하는 것.
    • 오버로딩(Overloading) : 같은 이름을 가진 메서드를 여러개 정의하지만 매개변수의 타입, 개수 등을 다르게 하는 것.

     

    제너릭(Generic)

    • 자료형을 매개변수화하여 클래스나 메서드를 작성할 수 있게 해주는 것. 
    • <T>는 제너릭 형식 매개변수로, 실제 사용 시에는 구체적인 자료형을 전달. 
    class Program
    {
    //T를 사용하지 않을 시에는 각기 다른 자료형의 2개의 메서드 생성해야함
        static void PrintArray<T>(T[] array)
        {
            foreach (T item in array)
            {
                Console.WriteLine(item);
            }
        }
    
        static void Main()
        {
        	//PrintArray 메서드는 제너릭으로 선언되어 어떤 데이터 형식이든 처리 가능
        	//메서드 호출 시 실제 자료형을 사용
            int[] intArray = { 1, 2, 3 };
            string[] stringArray = { "apple", "banana", "cherry" };
    		
            PrintArray(intArray);
            PrintArray(stringArray);
        }
    }

     

    • 다중 제너릭 : 제너릭을 2개 이상 사용하는 것. 제너릭 형식 매개변수를 ','로 구분하여 지정. 
    class Pair<T1, T2>
    {
        public T1 First { get; set; }
        public T2 Second { get; set; }
    
        public Pair(T1 first, T2 second)
        {
            First = first;
            Second = second;
        }
    }
    
    class Program
    {
        static void Main()
        {
        	//각 인스턴스의 자료형을 저장하고 출력
            Pair<int, string> pair1 = new Pair<int, string>(1, "one");
            Console.WriteLine($"First: {pair1.First}, Second: {pair1.Second}");
    
            Pair<string, double> pair2 = new Pair<string, double>("pi", 3.14159);
            Console.WriteLine($"First: {pair2.First}, Second: {pair2.Second}");
        }
    }

    out / ref

    • out : 매개변수를 통해 메서드 내에서 값을 반환하고자 할 때 사용. 
    void CalculateSumAndProduct(int a, int b, out int sum, out int product)
    {
        //return을 통해 값을 반환할 때는 값을 한 번만 반환할 수 있음.
        sum = a + b;
        product = a * b;
    }
    
    int main()
    {
        int sum, product;
        CalculateSumAndProduct(5, 3, out sum, out product);
        Console.WriteLine($"Sum: {sum}, Product: {product}");
    }

     

    • ref : 매개변수를 통해 메서드 내에서 값을 변경하고자 할 때 사용. 
    void ModifyValue(ref int value)
    {
        value = value * 2;
    }
    
    int main()
    {
        int number = 5;
        ModifyValue(ref number);
        Console.WriteLine($"Modified number: {number}");
    }

     

    오늘은 객체지향프로그래밍에서의 중요한 개념들을 공부하였습니다. 하지만 다양한 개념들과 그 개념들 간의 연결점이 복잡하여 완벽하게 이해하는 것은 힘들었습니다. 추가적인 복습과 실습을 계속 진행하면서 익숙해지는 것이 필요한 것 같습니다. 각 개념이 왜, 어떻게 사용되는지를 정확히 파악하면서 다시 학습한 후에 예제를 접하면서 더 이해를 높일 계획입니다. 

     

    'C#' 카테고리의 다른 글

    C# #7 스네이크 게임  (1) 2023.08.18
    C# #6 알고리즘 기초  (0) 2023.08.17
    C# #4 메서드와 구조체  (0) 2023.08.15
    C# #3 배열과 컬렉션  (0) 2023.08.15
    C# #2 조건문과 반복문  (0) 2023.08.14
    댓글