외로운 Nova의 작업실

c# 언어 공부 - 4(상속,오버라이딩, 중첩클래스, 분할클래스, 구조체) 본문

Programming/C#

c# 언어 공부 - 4(상속,오버라이딩, 중첩클래스, 분할클래스, 구조체)

Nova_ 2022. 5. 2. 19:59

안녕하세요. 오늘은 접근한정자에이어 클래스에 대해 좀 더 알아보도록 하겠습니다.

다시 한번 말씀드리지만 해당 설명은 c언어를 마스터하신분들께 적합한 내용입니다.

c언어 책을 한권이라도 읽지않으신 분은 이해하시기 어려울것입니다.

그럼 상속에대해서 먼저 알아 보겠습니다.

 

<상속>

강아지와 고양이는 젖을 먹인다는 공통점 때문에 포유류로 분류됩니다.

하지만 강아지와 고양이는 울음소리가 다릅니다.

강아지와 고양이 클래스를 만들때 각각 젖을 먹이는 메소드를 만들 수 있지만, 포유류라는 새로운 클래스를 만들고 그 안의 메소드와 필드등을 그대로 가져갈 수 있습니다.

먼저 코드부터 보시죠.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {
            
            Dog dog = new Dog();
            Cat cat = new Cat();

            dog.FeedMilk();
            cat.FeedMilk();

            dog.bark();
            cat.meow();
        }

    }

    class Mammalia //포유류
    {
        public void FeedMilk() //젖을 먹이는 메소드
        {
            Console.WriteLine("젖을 먹인다.");
        }
    }

    class Dog : Mammalia
    {
        public void bark() //짖는 메소드
        {
            Console.WriteLine("왈왈");
        } 
    }

    class Cat : Mammalia
    {
        public void meow() //울음소리 메소드
        {
            Console.WriteLine("냐옹");
        }
    }

}

위 코드는 Dog, Cat 클래스는 Mammalia라는 포유루 클래스를 상속하게됩니다.

상속을 하게되면 Dog, Cat 클래스는 Mammalia에 들어있는 메소드를 사용할 수 있게됩니다.

또한 Dog, Cat 클래스를 파생클래스, 자식클래스라하고 Mammalia 클래스를 기반클래스, 부모클래스라고합니다.

위의 결과는 아래와 같습니다.

c# 클래스의 상속

자식 클래스는 생성될때 부모 클래스 먼저 생성된다음에 자식클래스가 생성됩니다.

또한 종료될때는 자식 클래스가 먼저 종료되고 부모클래스가 종료됩니다.

이 모든건 CLR의 가비지 컬렉터가 하게됩니다.

또한 자식클래스는 base 키워드를 통해 부모클래스의 필드와 메소드에 접근할 수 있습니다.

먼저 코드부터 보시죠.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {
            
            Dog dog = new Dog();
            Cat cat = new Cat();

            dog.FeedMilk();
            cat.FeedMilk();

            dog.bark();
            cat.meow();
        }

    }

    class Mammalia //포유류
    {
        public int Size;
        public void FeedMilk() //젖을 먹이는 메소드
        {
            Console.WriteLine("젖을 먹인다.");
        }
    }

    class Dog : Mammalia
    {
        public void ChangeSize(int size) //포유루의 크기를 바꾸는 메소드
        {
            base.Size = size; //base 키워드를 사용해서 부모클래스의 필드에 접근
        }
        public void bark() //짖는 메소드
        {
            Console.WriteLine("왈왈");
        } 
    }

    class Cat : Mammalia
    {

        public void ChangeSize(int size) //포유루의 크기를 바꾸는 메소드
        {
            base.Size = size; //base 키워드를 사용해서 부모클래스의 필드에 접근
        }

        public void meow() //울음소리 메소드
        {
            Console.WriteLine("냐옹");
        }
    }

}

부모클래스의 필드 Size를 자식클래스에서 base.Size로 접근을 하고 있습니다.

 

자식클래스는 부모클래로 형식 변환이 가능하고 부모클래스는 자식클래스로 형식 변환이 가능합니다.

형식이란 메모리를 어떻게 읽어야하는지 알려주는 것입니다.

예를 들어 10100100001010....100으로 이루어져있는 객체를 Mamalia로 읽는다면 처음 1바이트는 Size 필드, 그다음 4바이트는 feed 메소드 등으로 읽는 것입니다.

만약 위 객체를 Dog으로 읽는다면 처음 1바이트는 부모 클래스의 Size 필드, 그다음 4바이트는 feed메소드, 그다음 4바이트는 ChangeSize메소드..이런식으로 읽는 방법이 달라진다는 것입니다.

완전히 다른 객체를 형식변환하게되면 이상한 값이 되버리지만 부모와 자식 클래스간의 형식변환은 호환이 가능하다는 것입니다.

또한 이번에 is와 as 연산자를 사용하는 방법도 함께 공부해봅시다.

먼저 코드를 보시죠.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {
            
            Mammalia mam = new Dog(); //포유류 형식에 Dog 객체를 할당

            Dog dog;

            if(mam is Dog) //mam변수가 Dog객체이면 True 반환 아니면 False반환
            {
                dog = (Dog)mam; //mam은 Mammalia 형식이므로 Dog 형식으로 변경하여 저장
            }

            Mammalia mam2 = new Cat(); //포유류 형식에 cat 객체를 할당
            Cat cat = mam2 as Cat; //mam2가 cat객체라면 mam2를 반환, 아니라면 null을 반환

            if(cat != null)
            {
                cat.meow();
            }
        }

    }

    class Mammalia //포유류
    {
        public int Size;
        public void FeedMilk() //젖을 먹이는 메소드
        {
            Console.WriteLine("젖을 먹인다.");
        }
    }

    class Dog : Mammalia
    {
        public void ChangeSize(int size) //포유루의 크기를 바꾸는 메소드
        {
            base.Size = size; //base 키워드를 사용해서 부모클래스의 필드에 접근
        }
        public void bark() //짖는 메소드
        {
            Console.WriteLine("왈왈");
        } 
    }

    class Cat : Mammalia
    {

        public void ChangeSize(int size) //포유루의 크기를 바꾸는 메소드
        {
            base.Size = size; //base 키워드를 사용해서 부모클래스의 필드에 접근
        }

        public void meow() //울음소리 메소드
        {
            Console.WriteLine("냐옹");
        }
    }

}

주석과 함께 읽으시면 코드를 이해하시는데 큰 어려움이 없을 것입니다.

 

<오버라이딩과 다형성>

다형성이란 객체가 여러 형태를 가질 수 있음을 의미합니다.

원래 다형성은 하위 형식 다형성의 준말입니다.

다시말해 파생클래스로 다형성을 실현한다는 것입니다.

오버라이딩이라는 방법으로 다형성을 실현하게됩니다.

예를들어 magic이라는 마법사 클래스는 Initialisze마법으로 자신을 보호하게됩니다.

class Magic //마법사
    {
        public void Initialize()
        {
            Console.WriteLine("마법으로 자신을 보호하기");
        }
    }

하지만 Magic의 자식 클래스인 불의 마법사는 불로 악당을 공격하고, 물의 마법사는 물로 악당을 공격합니다.

Magic의 Initialize() 마법으로는 불로 악당을 공격할 수 없습니다.

불의 마법사는 부모클래스의 Ininitilize()메소드를 다시 재정의해서 사용하기로합니다.

다시 재정의하기위해서는 부모클래스 메소드앞에 virtual 키워드를 붙여줘야하고

자식 클래스 메소드앞에는 override 키워드를 붙여줘야합니다.

먼저 코드부터 봅시다.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {

        }

    }

    class Magic
    {
        public virtual void Initialize()
        {
            Console.WriteLine("마법으로 자신을 보호하기");
        }
    }

    class FireMagic : Magic
    {
        public override void Initialize()
        {
            base.Initialize();
            Console.WriteLine("불로 악당을 공격하기");
        }
    }

    class WaterMagic : Magic
    {
        public override void Initialize()
        {
            base.Initialize();
            Console.WriteLine("물로 악당을 공격하기");
        }
    }


}

자식클래스인 FireMagic과 WaterMagic은 부모클래스의 부실한 Initialize() 마법을 자신의 능력에 맞게 기능을 추가하였습니다.

단 private로 선언한 메소드는 오버라이딩 할 수 없습니다.

또한 virtual 키워드를 사용하지않으면 오버라이딩을 할 수없지만, new 한정자를 사용하면 virtual 키워드를 사용하지않고도 오버라이딩이 가능합니다.

new 한정자는 override 키워드 대신에 넣어주면됩니다.

코드를 보시죠.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {

        }

    }

    class Magic
    {
        public void Initialize() // virtual 키워드 없어짐
        {
            Console.WriteLine("마법으로 자신을 보호하기");
        }
    }

    class FireMagic : Magic
    {
        public new void Initialize() //new 한정자로 오버라이딩 가능
        {
            base.Initialize();
            Console.WriteLine("불로 악당을 공격하기");
        }
    }

    class WaterMagic : Magic
    {
        public new void Initialize() //new 한정자로 오버라이딩 가능
        {
            base.Initialize();
            Console.WriteLine("물로 악당을 공격하기");
        }
    }


}

new 한정자는 override, virtual과 다른점이 있습니다.

코드먼저 보시죠.

 static void Main(string[] args)
        {
            Magic Fire = new FireMagic();

            Fire.Initialize();
        }

이렇게 불의 마법사 객체를 그냥 마법사 형식으로 바꾸고 Initialize() 마법을 쓰면, new한정자는 마법으로 자신만 보호하게되고, virtual, override 키워드를 사용하면, 마법으로 자신도 보호하고 .불로 악당을 공격하기까지 가능합니다.

결론적으로, new 한정자는 형식을 따라가고 virtual, override는 객체를 따라간다는 것이지요.

 

<오버라이딩 봉인하기>

부모클래스는 sealed한정자를 통해 오버라이딩을 못하게 막을 수 도 있습니다.

단, virtual로 선언된 메소드를 오버라이딩한 버전의 메소드만 가능합니다.

이유는 첫번째 부모 클래스의 메소드는 virtual을 쓰지않으면 자식 클래스의 오버라이딩을 봉인할수있지만, 자식클래스가 오버라이딩 한 메소드는 또다른 자식클래스가 또 그메소드를 오버라이딩을 제제할 방법이 없기 때문에 그 방법을 구현하다보니 그렇게 된것입니다.

코드부터 보시죠.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {
            Magic Fire = new FireMagic();

            Fire.Initialize();
        }

    }

    class Magic
    {
        public virtual void Initialize()
        {
            Console.WriteLine("마법으로 자신을 보호하기");
        }
    }

    class FireMagic : Magic
    {
        public sealed override void Initialize() //sealed 한정자로 다시 오버라이딩을 못하게 하고있음
        {
            base.Initialize();
            Console.WriteLine("불로 악당을 공격하기");
        }
    }

    class WaterMagic : Magic
    {
        public sealed override void Initialize() //sealed 한정자로 다시 오버라이딩을 못하게 하고있음
        {
            base.Initialize();
            Console.WriteLine("물로 악당을 공격하기");
        }
    }


}

이렇게되면 불의 마법사의 자식클래스는 Initialize() 마법에 손을 못대게됩니다.(오버라이딩을 못함)

 

<읽기전용 필드>

변수를 상수처럼 사용할 수있습니다.

마치 c언어의 const 키워드 같은 존재죠.

c#에서는 readonly라는 키워드로 사용합니다.

readonly 키워드를 붙이면 그 변수는 생성자에서만 수정이 가능하게됩니다.

코드부터 보시죠.

class Magic
    {
        private readonly string name; //readonly로 이름을 변하지않게 하고 있음

        public Magic(string name)
        { //readonly는 생성자에서만 수정가능함
            this.name = name;
        }
        public virtual void Initialize()
        {
            Console.WriteLine("마법으로 자신을 보호하기");
        }
    }

주석과 함께 읽으시면 이해가 되실겁니다.

 

<중첩클래스>

사람같은 휴머노이드 로봇을 객체로 생각한다면 로봇의 팔 다리도 또하나의 객체로 볼 수 있습니다.

하지만 팔 다리는 휴머노이드 안에 있음으로 중첩클래스에 해당합니다.

먼저 코드부터 보시죠.

class Humanoid //휴머노이드
    {

        public void Work() //휴머노이드가 걷는 기능
        {
            Console.WriteLine("걷는다.");
        }
        class Humanoid_arm //휴머노이드 팔
        {
            public void MoveArm() //팔만 움직이는 기능
            {
                Console.WriteLine("팔을 움직인다.");
            }
        }

        class Humanoid_reg //휴머노이드 다리
        {
            public void MoveReg() //다리만 움직이는 기능
            {
                Console.WriteLine("다리를 움직인다.");
            }
        }
    }

중첩 클래스는 클래스안에 클래스를 선언해주시면됩니다.

중첩 클래스는 자신이 소속된 클래스의 멤버(메소드,필드)에 자유롭게 접근할 수 있습니다.

private 한정자 필드 메소드도 접근할 수 있습니다.

 

<분할 클래스>

partial 키워드를 사용하면  클래스를 한번에 정의하지않고 여러개로 분할 해서 정의할 수 있습니다.

코드부터 보시죠.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {
          Humanoid Robot = new Humanoid();

            Robot.Work();
            Robot.eat();
        }

    }

    partial class Humanoid //휴머노이드
    {
        public void Work() //휴머노이드가 걷는 기능
        {
            Console.WriteLine("걷는다.");
        }
    }

    partial class Humanoid
    {
        public void eat() //밥을 먹는 기능
        {
            Console.WriteLine("밥을 먹는다");
        }
    }


}

위 코드와 같이 partial 키워드를 이용하면 따로따로 만들었지만 한개로 합쳐지게 할 수 있습니다.

 

<구조체>

구조체는 c언어처럼 struct 키워드를 사용하여 만듭니다.

먼저 코드를 봅시다.

 struct point3D
    {
        public int x;
        public int y;
        public int z;
    }

이런식으로 struct 키워드를 사용하여 정의합니다.

구조체에서 readonly는 모든 필드를 읽기전용으로 만들어야합니다.

안하면 에러가납니다.

  readonly struct point3D
    {
        public readonly int x;
        public readonly int y;
        public readonly int z;
    }

값을 변경하고싶을땐 새로운 구조체를 만들면 됩니다.

 

다음시간엔 인터페이스와 추상 클래스에대해 알아보도록 하겠습니다.

 

Comments