본문으로 바로가기

C# 복습 (4)

category 개발자과정준비/C# 2020. 7. 20. 23:04
반응형

const와 readonly의 차이(밑의 예제 주석을 확인!)

using System;
class Program
{
    class Car
    {
        const int iNum1 = 10; // 한번 대입하면 값을 바꿀수가없는것이 const (수정의 여지가 아예없다, 파일로 만드는순간(컴파일할때) 값이 정해져있음)
        readonly int iNum2;  // 바꿀수없고 읽을수만있는 readonly (초창기 값을 넣을때까지는 수정의 여지가 있음->초반에 여지가있다는 뜻, 생성자 호출때 바꿀수있다)
        // iNum2는 만약 인자로 받으면 생성할때 값을 한번 바꿀 수 있다, (실행할때 값이 고정되는 친구들)

        string _color;
        string _vender;
        string _name;

        public string name
        {
            //프로퍼티
            get
            {
                return _name;
            }
            set  // set에는 value만 써야함.
            {
                //name = "Auto : " + name;
                this._name = value;
            }
        }

        public string GetName()
        {
            return _name;   // 반환 name이 문자열이므로 반환형도 string으로 void에서 바꿔준다.
        }
        public void SetName(string name) // 데이터를 간접적으로 접근하게해주는 메서드
        {
            name = "Auto : " + name;
            this._name = name;
        }

        public void Print()
        {
            //iNum2 = 200; // 값이 대입되지않음
            Console.WriteLine("[{0}],[{1}],[{2}]", _name, _vender, _color);
        }
        public Car() // 디폴트 생성자
        {
            Console.WriteLine("Car 디폴트 생성자 호출");
        }

        //생성자 오버로딩 : 이름은 같은데 인자가 다를때 오버로딩이라 부름.
        public Car(string name) 
        : this(name, "", null)  // 초기화 목록 : 내안의 내부를 실행하기전에 다른 애의 내부를 호출해달라 -> 코드의 중복을 없애줄 수 있다.
        // ""랑 null은 같은 뜻임
        {
            ////name = name;     // 인자랑 위에서 선언한 변수의 이름이 같다고 오류가 뜰 수 있다.
            //this.name = name;   // 이 클래스 안에서의 name을 구분짓기위해 this를 입력해준다.
            //Console.WriteLine("Car 생성자 호출");
        }

        public Car(string name, string vender) 
        : this(name, vender, "")
        {
            // 초기화 목록을 사용했기때문에 코드가 필요없다.(위에 name 구문도 마찬가지)
        }

        public Car(string name, string vender, string color) // 생성자 오버로딩
        {
            this._color = color;
            this._name = name;
            this._vender = vender;
            Console.WriteLine("Car 생성자 호출");
        }

        ~Car()
        {
            Console.WriteLine("Car 소멸자 호출");
        }
    }
    static void Main(string[] args)
    {
        Console.WriteLine("1========================");

        Car aCar = new Car("케이파이브","","");
        new Car();

        Console.WriteLine("2========================");
        //메서드 사용
        aCar.Print();
        aCar.SetName("비안츠");  // public string name으로 데이터를 가져올 수 있지만, 데이터를 간접적으로 접근하게하기위해 메서드를 사용한다.
        aCar.Print();
        string name = aCar.GetName();
        Console.WriteLine("읽어온 값은 " + name + " 입니다");

        Console.WriteLine("3========================");
        //프로퍼티 사용
        aCar.name = "람보르니";
        aCar.Print();
        name = aCar.name;
        Console.WriteLine("읽어온 값은 " + name + " 입니다");
        Console.WriteLine("4========================");
    }
}

 

 

 

 

 

 

using System;

//상속
class Parent
{
    public Parent()
    {
        Console.WriteLine("Parent 클래스 생성자");
    }
    ~Parent()
    {
        Console.WriteLine("Parent 클래스 소멸자");
    }
}
class Child : Parent
{
    public Child()  // 생성자는 보통 public을 붙여주는 것이 좋다. 
    {
        Console.WriteLine("Child 클래스 생성자");
    }

    ~Child()  // 소멸자는 public을 붙이면 안된다.
    {
        Console.WriteLine("Child 클래스 소멸자");
    }
}
class Program
{
    static void Main(string[] args)
    {
        Child aChild = new Child();
    }
}

가는 데는 순서없다(?)의 예시

위의 예제를 실행하면 Parent클래스, Child클래스의 생성자와 소멸자의 순서가 있는 것을 알 수 있다. 

피라미드로 예를들면 부모꺼를 놓고, 자식꺼를 놓게된다. 이때 모두 치우려면 자식꺼부터 치워야 부모꺼를 치울 수 있다.

 

왜 상속을 사용하는가?

만약 RPG게임을 만든다고 가정해보자.

RPG게임이라면 사냥을위한 무기를 가지고 있을 것이다.

만약 간단하게 총, 칼, 활 이라는 무기 3개의 클래스를 만들었다고 생각해보면,

각 무기에 클래스를 만들고 함수도 하나하나 만들어줘야할 것이다

총은 장전해줘야하고, 탄창도 따로 생각해야하고 공격력이 변하는 변수가 필요하다.

칼은 공격만 필요하고 공격력이 변하는 변수가 필요하다.

활은 공격만 필요하지만 공격력과 사정거리를 변수로 생각해줘야한다.

 

이렇게 3개다 무기면서 공통점이 많고, 같은 클래스로 두기에는 3개의 무기는 각각 다르다.

그래서 무기를 여러개 만든다면 똑같은 공격 함수를 짜고, 공격력 변수를 만들게되면 머리가 빠져서 게임제작하기에는 힘들것이다.

상속 클래스의 예시

그래서 프로그래머가 개발도중에 도망가는 것을 막기위해 C#에서는 상속을 지원한다.

무기라는 부모클래스에 공격함수와 공격력 변수를 만들고,

총, 칼, 활의 각각 필요한 모션이나 사정거리 등만 프로그램을 만들면 되는 것이다.

 

using System;

//상속
class Parent
{
    public int iNum;
    public Parent(int iNum) // Parent의 디폴트 생성자
    {
        this.iNum = iNum;
        Console.WriteLine("Parent 클래스 생성자 : " + iNum);
    }
    ~Parent()
    {
        Console.WriteLine("Parent 클래스 소멸자");
    }
}
class Child : Parent
{
    public int iNum;
    public Child() : base(101)   // 원래는 : base()가 생략되어있다. base는 부모를 뜻한다.참고로 Java에서는 Super클래스라고 한다.
    // Child의 디폴트 생성자, 생성자는 보통 public을 붙여주는 것이 좋다.  
    {
        iNum = 200;
        Console.WriteLine("Child 클래스 생성자");
    }

    ~Child()  // 소멸자는 public을 붙이면 안된다.
    {
        Console.WriteLine("Child 클래스 소멸자");
    }
    public void Print()
    {
        Console.WriteLine("Child 클래스 iNum = " + base.iNum);  // 부모(base)클래스의 iNum호출
        Console.WriteLine("Child 클래스 iNum = " + this.iNum);  // 본인(자식)(this)클래스의 iNum호출
    }
}
class Program
{
    static void Main(string[] args)
    {
        Child aChild = new Child();
        aChild.Print();
    }
}

 

 

 

 

 

 

 

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

class Parent
{
    public void Name()
    {
        Console.WriteLine("Parent");
    }
}
class Child: Parent
{
    public void Name()
    {
        Console.WriteLine("Child");
    }
}
class GrandChild : Child
{
    public void Name()
    {
        Console.WriteLine("GrandChild");
    }
}
class Program
{
    static void Main(string[] args)
    {
        Parent obj1 = new Parent();
        Child obj2 = new Child();
        //Parent obj2 = new Child();
        GrandChild obj3 = new GrandChild();
        //객체참조변수가 어떤걸로 선언됬느냐에따라 출력되는것이 다르다
        obj1.Name();
        obj2.Name();
        obj3.Name();
    }
}

 

 

 

 

 

 

 

 

 

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

class Parent
{
    public void Name()
    {
        Console.WriteLine("Parent");
    }
}
class Child: Parent
{
    public void Name()
    {
        Console.WriteLine("Child");
    }
}
class GrandChild : Child
{
    public void Name()
    {
        Console.WriteLine("GrandChild");
    }
}
class Program
{
    static void Main(string[] args)
    {
        Parent obj1 = new Parent();
        //Child obj2 = new Child();
        Parent obj2 = new Child();
        // GrandChild obj3 = new GrandChild();
        Parent obj3 = new GrandChild();
        //객체참조변수가 어떤걸로 선언됬느냐에따라 출력되는것이 다르다
        obj1.Name();
        obj2.Name();
        obj3.Name();
    }
}

 

 

 

virtual, override 사용

using System;

//객체기반 호출
// Child객체니까 Child를 써라, GrandChild객체니까 GrandChild를 써라.363
class Parent
{
    public virtual void Name()
    {
        Console.WriteLine("Parent");
    }
}
class Child: Parent
{
    public override void Name()
    {
        Console.WriteLine("Child");
    }
}
class GrandChild : Child
{
    public override void Name()
    {
        Console.WriteLine("GrandChild");
    }
}
class Program
{
    static void Main(string[] args)
    {
        Parent obj1 = new Parent();
        Parent obj2 = new Child();
        // Child obj2 = new Child();
        // GrandChild obj3 = new GrandChild();
        Parent obj3 = new GrandChild();
        obj1.Name();
        obj2.Name();
        obj3.Name();
        Console.WriteLine();

        Parent[] Array = new Parent[]   // new Parent[]에서 3개를 선언했으니 []안의 숫자를 생략해도 상관없다.
        {
            new Parent(),
            new Child(),
            new GrandChild()
        };
        for(int i = 0; i < Array.Length; ++i)
        {
            Array[i].Name();
        }
        

        // 배열을 만든 구문과 obj.Name()으로 선언한 구문의 결과값은 같다.
        // 만약 100개의 코드를 짠다고 가정해보자.
        // 배열코드는 배열 100개만 선언해주면 for문은 그대로 써도 상관이없지만,
        // obj.Name()코드는 1부터 100까지 객체선언도 해줘야하고 .Name()도 100개를 선언해줘야한다.
        
        // virtual을 안쓰면 참조변수의 타입기반으로 돌아간다.
        // 객체지향이기때문에 객체기반으로 돌릴꺼다.
    }
}

 

 

 

 

 

 

 

오버라이드

클래스 구문에서, 메서드의 이름이 같을때 컴파일러가 오류로 잡아놓는다. 이것은 프로그래머가 이걸 만든게 숨기려고 만든건지, 우연히 메서드의 이름이 일치했는지 컴파일이 모르겠으니까 일단 오류로 잡아주는 것이다. (일단은 New를 생략한걸로 간주한다.)



 

override를 붙였을때 (좌), new를 붙였을때 (우)

부모에 있는 것을 받아쓰겠다.오버라이딩하겠다.라고 프로그래머가 간주하면 override를 써준다.

또는

이 메서드가 다른 메서드와 전혀 상관없다고 프로그래머가 간주할꺼면 new 키워드를 붙여준다.

 

 

 

 

책의 예제를 보자.

// 오버라이드 없을때의 예제
using System;
class Mammal
{
    public void Move()
    {
        Console.WriteLine("이동합니다");
    }
}

class Lion : Mammal
{
    public void Move()
    {
        Console.WriteLine("네 발로 움직인다.");
    }
}
class Whale : Mammal
{
    public void Move()
    {
        Console.WriteLine("수영한다.");
    }
}
class Human : Mammal
{
    public void Move()
    {
        Console.WriteLine("두 발로 움직인다.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Mammal one = new Mammal();
        one.Move();

        Lion lion = new Lion();
        lion.Move();

        Whale whale = new Whale();
        whale.Move();

        Human human = new Human();
        human.Move();
    }
}

컴파일하고 실행하는데는 문제가 없다.
상속된 클래스의 메서드마다 초록줄이 그어져있다

일단은 초록줄이 그어져있는데 잠시 넘어가도록하자.

 

 

 

이때 자식이 부모타입으로 암시적 형변환이 된 경우에는 어떻게 될까?

// 위의 코드에서 메인메서드만 바꿔보자
class Program
{
    static void Main(string[] args)
    {
        Lion lion = new Lion();
        Mammal one = lion; // 부모타입으로 형변환

        one.Move();
    }
}

지금 예제에서 부모타입으로 형변환되긴 했지만, 원래의 인스턴스 자체는 Lion타입으로 의도했던 동작이아니다.

즉, 기본적으로는 Lion 인스턴스가 이동했으니 Lion클래스의 Move가 호출되어서 "네 발로 움직인다"가 출력되어야할 것이다.

이런 문제를 해결하기위해 가상 메서드(virtual method)라는 것이 제공된다.

일반 메서드를 가상메서드로 바꾸려면 virtual이라는 예약어를 부모클래스 단계에서 명시하고, 자식 클래스에서는 해당 메서드가 다형성을 띠도록 override 예약어를 지정해주면 된다.

 

예제에서 메서드이름에 virtual과 override가 들어간것에 주목해보자

//오버라이드 예제
using System;
class Mammal
{
    public virtual void Move()
    {
        Console.WriteLine("이동합니다");
    }
}

class Lion : Mammal
{
    public override void Move()
    {
        Console.WriteLine("네 발로 움직인다.");
    }
}
class Whale : Mammal
{
    public override void Move()
    {
        Console.WriteLine("수영한다.");
    }
}
class Human : Mammal
{
    public override void Move()
    {
        Console.WriteLine("두 발로 움직인다.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Lion lion = new Lion();
        Mammal one = lion; // 부모타입으로 형변환
        one.Move();
    }
}

이젠 네 발로 움직인다가 출력된다

 

 Mammal 예제 맨위의 코드에서 상속된 클래스의 Move()메서드에 초록줄이 그어져있는데,

부모 클래스의 Move라는 메서드를 정의했다고해서 프로그래머가 반드시 그것과 동일한 이름을 사용하기위해 virtual/override를 붙이지 않을 수도 있다. (우연히 이름이 겹쳤을수도있다는 뜻이다.)

 

 때로는 자식클래스에서 아무런 영향을 받지않고 이름만 같은 메서드를 정의하고 싶을때도 있는데, 컴파일러는 이 경우까지 따져서 프로그래머가 가상메서드를 만든건지, 우연히 이름이 겹친건지 모르겠으니까 일단은 초록줄로 알려주는 것이다.

 

 만약, 이름이 겹쳐서 순수하게 독립적인 하나의 메서드의 이름을 정의해야할때, C#에서는 같은 이름의 메서드를 일부러 겹쳐서 정의했다는 프로그래머의 의도를 명시적으로 표현하는 new 예약어를 제공한다.

 

이번 예제에서 코드에 new가 들어간것에 주목해보자.

//오버라이드 new예제
using System;
class Mammal
{
    public void Move()
    {
        Console.WriteLine("이동합니다");
    }
}

class Lion : Mammal
{
    public new void Move()
    {
        Console.WriteLine("네 발로 움직인다.");
    }
}
class Whale : Mammal
{
    public new void Move()
    {
        Console.WriteLine("수영한다.");
    }
}
class Human : Mammal
{
    public new void Move()
    {
        Console.WriteLine("두 발로 움직인다.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Lion lion = new Lion();
        Mammal one = lion; // 부모타입으로 형변환
        one.Move();
    }
}

 

 따라서, 부모와 자식 클래스에서 같은 이름의 메서드(Move())를 사용하려면 두 가지 중 하나를 고려해야 한다.

1. 메서드 오버라이드를 사용한다. -> 그렇다면 virtual/override를 사용해라.

2. 단순히 자식 클래스에서 같은 이름의 메서드가 필요하다.(우연히 이름이 겹쳤다) -> 그렇다면 new를 사용해라

 이미지에서 보면 아무것도 안썼을때 초록줄이 그어져있는데, 이때 컴파일러는 2번으로 간주한다.

 

 

 

 

 위에서는 virtual 메서드를 정의한 부모클래스에서 override 예약어를 이용해 기능을 재정의하는 것을 봤는데, 때로는 부모 클래스의 인스턴스를 생성하지 못하게 하면서 특정 메서드에 대해 자식 클래스들이 반드시 재정의 하도록 강제하고 싶은 경우가 있을 수 있다.

 이때 사용하는 것이 추상 클래스(abstract class)와 추상 메서드(atstract method)이다.

추상 메서드는 일반클래스에 존재할 수 없으며, 반드시 추상 클래스 안에서만 선언할 수 있다. abstract 예약어가 지정된 추상메서드를 쉽게 정의하면 "코드없는 가상 메서드(virtual method)"라고 이해해도된다.

 

 추상메서드에는 접근 제한자로 private를 지정할 수 없고, 반드시 자식 클래스에서 재정의 해야한다.

 

 추상클래스는 객체를 생성할 수 없다.

 

// 추상클래스, 추상메서드 예제
using System;
abstract class Musician  // 추상클래스(객체 생성할 수 없다.) : abstract 키워드에 선언
   // 미연 실수방지, 사용자가 강제한대로 구문을 작성해야함.
{
    public virtual void Name()
    {
        Console.WriteLine("뮤지션입니다.");
    }
   // public abstract void Instruments();  // 추상메서드(완성되지않는 메서드)
   public virtual void Instruments() // 추상메서드가 없다고해서 추상클래스가아니다. 는 아니다.
    // 추상메서드는 추상클래스안에서만 만들 수 있다. (추상클래스 내부에서만 선언할 수 있다)
    {

    }
}
class ViolinPlayer : Musician
{
    public override void Name()
    {
        Console.WriteLine("바이올린 연주자입니다.");
    }
    public override void Instruments()
    {
        Console.WriteLine("저는 바이올린인데요?");

    }
}
class PianoPlayer : Musician
{
    public override void Name()
    {
        Console.WriteLine("피아노 연주자입니다.");
    }
    public override void Instruments()
    {
        Console.WriteLine("저는 피아노인데요?");
    }
}
class FlutePlayer : Musician   // 악기 구현안하면 상속이 안된다.
{
    public override void Name()
    {
        Console.WriteLine("플루트 연주자입니다.");
    }
    public override void Instruments()
    {
        Console.WriteLine("저는 플루트인데요?");
    }
}
class Program
{
    static void Main(string[] args)
    {
        Musician[] Array = new Musician[3];
        Array[0] = new ViolinPlayer();  // 왼쪽은 뮤지션객체, 오른쪽은 바이올린객체인데 오류가 안뜬다.
        Array[1] = new PianoPlayer();
        Array[2] = new FlutePlayer();
        for (int i = 0; i<Array.Length; ++i)
        {
            Array[i].Name();
            Array[i].Instruments();
            Console.WriteLine("********************");
        }
    }
}

 

 

 

인터페이스

인터페이스는 간단하게 예약(contract)라고 정의되며, 구현 없이 메서드 선언만 포함되어 있다.

추상 메서드만 담고 있는 추상 클래스라고 생각해도 무방하다.

//인터페이스 예제
using System;
// 추상메서드만 보관하고있는 클래스를 인터페이스 클래스라고한다.
// 설계 요약 설명하는 역할
//인터페이스를 왜 쓰는가? 다중상속처럼 쓸 수 있기때문.
interface test  
{
    //int iNum;   // 변수선언이 되지않는다.
    //void tt() { }  // 메서드 선언이 되지않는다.
    //public voidtt(){}  // 접근 제한자도 못쓴다.
    void tt();  
}

class Auto : test
{
    public void tt()
    {

    }
}

 

인터페이스의 추상메서드를 상속받고 void tt()를 선언해주지않으면 오류가 발생한다

인터페이스는 추상메서드만 가지고있고, 인터페이스를 상속받으면 반드시 추상메서드를 선언해주어야한다.

 

 

 

 

인터페이스를 사용하는 이유?

C#에서 "클래스는 다중 상속이 불가능"하다는 특징이있다. 하지만, 인터페이스는 클래스가 아니기때문에 다중 상속이 허용되어서 밑의 예제처럼 IMusician, IActor의 두개의 상속을 받을 수 있다.

또한, 인터페이스의 메서드를 자식클래스에서 구현할때는 반드시 public 접근 제한자를 명시해주어야한다.(이때 public이 없다고해서 private가 되는건 아니다.)

using System;
interface IMusician  // 상속받을때 구별하려고 I를 써주고 첫글자를 대문자로 해주는 것이 편하다.
{
    void Singing();   // 이렇게 선언해주면 IMusician을 상속받은애들은 무조건 Singing을 선언해야한다.
}

interface IActor  
{
    void Playing();   
}

class Musical : IMusician, IActor
{
    public void Singing()
    {
        Console.WriteLine("노래하다");
    }
    public void Playing()
    {
        Console.WriteLine("연기하다");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Musical aMusical = new Musical();
        aMusical.Playing();
        aMusical.Singing();
    }
}

 

 

 

 

 

 

 

using System;
class Engin
{
    string name;
    string vender;
    public Engin()
    {
        name = "휘발 V6";
        vender = "비엔츠";
    }
    public override string ToString()
    {
        string Temp = "["
                    + "{" + name + "},"
                    + "{" + vender + "}"
                    + "]";
        return Temp;
    }
}
class Car
{
    string name;
    string color;
    string vender;
    Engin aEngin;
    public Car()
    {
        name = "케이세븐";
        color = "빨간색";
        vender = "기아";
        aEngin = new Engin();
    }
    public override string ToString() // 변수들의 정보를 디버그로 확인할때 쓴다.
    {
        string Temp = "["
                     + "{" + name + "},"
                     + "{" + color + "},"
                     + "{" + vender + "}"
                     + "{" + aEngin + "}"
                     + "]";
        return Temp;
    }
}
class Program
{
    static void Main(string[] args)
    {
        Car aCar = new Car();
        //Console.WriteLine(aCar.Equals("test")); // aCar의 객체와 "test"가 같냐?를 비교하는 메서드 : False가 출력
        //Console.WriteLine(aCar.GetHashCode()); // 단방향 함수, 민증번호같은거라고 생각하면됨.
        //aCar = new Car();
        //Console.WriteLine(aCar.GetHashCode());
        Console.WriteLine(aCar.GetType());  // Type을 알 수 있는 메서드
        Console.WriteLine(aCar.ToString());  // getType과 출력값이 Car로 같다.
        Console.WriteLine(aCar);  // 이렇게만쓰면 컴파일러가 ToString으로 인식해서 Car를 출력
    }
}

 

 

 

반응형

'개발자과정준비 > C#' 카테고리의 다른 글

[C#] Random 클래스  (0) 2020.08.23
[C#] 제네릭 프로그래밍(메서드, 클래스)  (2) 2020.08.19
C# 복습 (3)  (0) 2020.07.14
C# 복습 (2)  (0) 2020.07.14
C# 복습 (1)  (0) 2020.07.14