공부/인프런 - Rookiss

Part 1-4-5. 객체지향 : new, 다형성 (virtual, override, sealed), 문자열

셩잇님 2023. 8. 2. 16:21
반응형

 

 

new

 

    class Player
    {
        public int hp;
        public int attack;

        public void Move()
        {
            Console.Write("Player Move");
        }
    }
    
    class Knight : Player
    {
        public new void Move()
        {
            Console.Write("Kight Move");
        }
    }
Knight knight = new Knight();
knight.Move();

// 출력
// Kight Move

 

 부모 클래스로부터 상속 받은 함수와 이름은 동일하지만 이와 상관없는 완전히 다른 새로운 함수로서 재정의하고 싶다면 new키워드를 사용하면 된다. 상속 받은 함수와 동일한 이름이긴 하지만 새로운 함수를 만드는 것을 의미한다.

 

 Player의 Move()와 Knight의 Move()는 완전히 다른 함수다. new는 꼭 붙이지 않아도 된다. new 붙이지 않아도 new가 있는 것처럼 작동해주기 때문에 그냥 Knight 클래스 안에서 new붙이지 않고 public void Move()라고 정의하기만 해도 완전히 새로운 함수라고 정의된다. 그러나 new를 붙여주면 개발자 입장에선 의미를 알기 쉽기 때문에 붙이는 것을 권고하는 것 같다!

 


 

다형성

 

[가상함수와 오버라이딩(virtual, override)

 상속 하는 함수라도 부모 변수로 자식 객체들을 참조할 때(업캐스팅) 자식들마다 실행 내용을 자동으로 다르게 호출 하고 싶을 때.

 

    class Player
    {
        public int hp;
        public int attack;

        public virtual void Move()
        {
            Console.Write("Player Move");
        }
    }
    class Knight : Player
    {
        public override void Move()
        {
            Console.Write("Knight Move");
        }
    }

    class Mage : Player
    {
        public override void Move()
        {
            Console.Write("Mage Move");
        }
    }

...

int Test(Player player)
{
    player.Move();
}

 

 

 매개 변수인 player는 인수로 꼭 Player 타입의 객체 뿐만 아니라 그의 자식들인 Knight, Maze 타입의 객체도 참조할 수 있다. player에 어떤 타입의 객체가 들어오느냐에 따라서 player.Move() 이 코드 한 줄이 다르게 실행되게 하려면 Player에서 Move()를 가상 함수로 선언하고, 자식 클래스에서 이 가상함수를 오버라이딩, 재정의 하면 된다. 이것이 바로 다형성 개념이다.

 

  • player.Move();
  • Player player = new Player();
  • Player의 Move() 실행. “Player Move” 출력

 

  • Player player = new Knight();
  • Knight의 Move() 실행. “Knight Move” 출력

 

  • Player player = new Mage();
  • Mage의 Move() 실행. “Mage Move” 출력

 

부모 클래스에서 가상 함수는 virtual 키워드를 붙이면 되고, 자식 클래스에서 이 가상 함수를 오버라이딩 하려면 override 키워드를 붙이며 된다. virtual은 성능 부하가 있기 때문에 남발하여 만들지 않고 꼭 다형성이 필요한 경우만 가상 함수로 선언하는 것이 좋다. 재정의 없이 그대로 상속 받는 경우가 많은게 성능상 좋긴 하다!

 

    class Player
    {
        public int hp;
        public int attack;

        public virtual void Move()
        {
            Console.Write("Player Move");
        }
    }
    class Knight : Player
    {
        public void Move()
        {
            Console.Write("Kight Move");
        }
    }
Player player = new Knight();
player.Move();

// 출력
// Player Move

 

 Player의 Move()는 가상 함수이나 자식인 Knight 클래스에선 이를 오버라이딩 하지 않았다. 따라서 부모의 Move()와는 관련 없이 이름만 똑같을 뿐인 새로운 Move()를 정의하는 것과 같다. 즉, new를 붙인 것과 같은 상태다. 따라서 업캐스팅 하였어도 player.Move()는 Player의 Move()가 호출된다. (C++, C#은 Java와 달리 참조 하는 '변수'의 타입을 기준으로 멤버 함수를 호출한다. 가상 함수여서 다형성을 실형하는게 아니라면 변수의 타입을 기준으로 호출한다.)

 

    class Player
    {
        public int hp;
        public int attack;

        public virtual void Move()
        {
            Console.Write("Player Move");
        }
    }
    class Knight : Player
    {
        public override void Move()
        {
            Console.Write("Kight Move");
        }
    }
Player player = new Knight();
player.Move();

// 출력
// Knight Move

 

 자식 클래스인 Knight에서 이를 오버라이딩하여, player.Move()에서 Player의 Move()가 아닌, 재정의한 Knight의 Move()가 호출된다.

    class Player
    {
        public int hp;
        public int attack;

        public virtual void Move()
        {
            Console.Write("Player Move");
        }
    }
    class Knight : Player
    {

    }

    class Mage : Player
    {
        public override void Move()
        {
            Console.Write("Mage Move");
        }
    }
Player knightPlayer = new Knight();
Player magePlayer = new Mage();

knightPlayer.Move();
magePlayer.Move();

// 출력
// Player Move
// Mage Move

 

 knightPlayer는 가상 함수 Move()를 오버라이딩 하지 않고 그냥 그대로 부모인 Player의 Move()를 상속 받았다. 따라서 knightPlayer.Move()는 Player의 Move()를 호출한다. 반면에 magePlayer는 가상 함수 Move()를 오버라이딩 하였다. 따라서 magePlayer.Move()는 Mage의 Move()를 호출한다.

 

    class Player
    {
        public int hp;
        public int attack;

        public virtual void Move()
        {
            Console.Write("Player Move");
        }
    }
    class Knight : Player
    {
        public override void Move()
        {
            base.Move(); // 기능 추가
            Console.Write("Kight Move");
        }
    }
Player player = new Knight();
player.Move();

// 출력
// Player Move
// Knight Move

 

 base 키워드를 사용하여 재정의 하는 대상인 부모 클래스의 가상함수를 사용하는 식으로 부모 함수의 기능을 일부 추가할 수 있다. Knight에서 Move()를 오버라이딩 할 때 base.Move() 하는 내용도 추가했기 때문에 Player의 Move()도 같이 실행할 수 있게 되었다.

[sealed]

 sealed는 오버라이딩 할 때 같이 사용된다. 손자, 증손자 클래스들에서 더 이상 오버라이딩 하지 못하게, 딱 나까지만 오버라이딩이 가능하도록 강제한다. C++엔 없고 C#에만 있는 문법이며 그리 자주 쓰이진 않는다.

 

    class Player
    {
        public int hp;
        public int attack;

        public virtual void Move()
        {
            Console.Write("Player Move");
        }
    }
    class Knight : Player
    {
        public sealed override void Move()
        {
            Console.Write("Kight Move");
        }
    }

    class SuperKnight : Knight
    {
        public override void Move()  // ❌❌❌ 컴파일 에러 ! 
        {
            Console.Write("Kight Move");
        }
    }

 

 Knight에서 Move()를 오버라이딩할 때 sealed 키워드를 통해 봉인해 두었기 때문에 손자 클래스인 SuperKnight에선 Move()를 오버라이딩 할 수 없다. 상속 받은 것을 그대로 써야 한다.

 


 

문자열

 

[찾기]

Conains(string) 👉 호출한 문자열에 인수로 넘긴 문자열이 부분 문자열로 존재한다면 true, 아니면 false 리턴

 

string name = "Harry Potter";

if(name.Contains("Harry"))
    Console.WriteLine("True");

// 출력
// True

 

인수로 넘긴 “Harry”는 name 문자열에 부분 문자열로 존재하기 때문에 true를 리턴한다.

 

 

IndexOf(char) 👉 호출한 문자열에 인수로 넘긴 문자가 부분 문자열로 존재 한다면, 해당 문자가 있는 위치인 인덱스를 리턴하고 존재 하지 않는다면 -1를 리턴한다.

 

string name = "Harry Potter";
Console.WriteLine(name.IndexOf('P'));
Console.WriteLine(name.IndexOf('Z'));

// 출력
// 6
// -1

 

[변형]

덧붙이기 : + 연산자

 

string name = "Harry Potter";
name = name + " Junior";
Console.WriteLine(name);

// 출력
// Harry Potter Junior

 

소문자, 대문자로 변형 : ToLower, ToUpper

 

string name = "Harry Potter";
Console.WriteLine(name.ToLower());
Console.WriteLine(name.ToUpper());

// 출력
// harry potter
// HARRY POTTER

 

특정 문자 바꾸기 : Replace(char, char) 👉 호출한 문자열에 첫 번째 인수 문자에 해당하는 부분들을 전부 두 번째 인수로 바꾼다.

string name = "Harry Potter";
string newName = name.Replace('r', 'l');
Console.WriteLine(newName);

// 출력
// hally pottel

 

 name 문자열의 모든 r부분이 l로 바뀌었다.

[분할]

Split 👉 인수로 넘긴 구분자를 기준으로, 호출한 문자열을 분할하여 이를 string [] 배열로 리턴한다.

string name = "Harry Potter";
string[] names = name.Split(new char[] { ' ' }); // 배열의 원소인 ' ' 공백 문자를 기준으로 분할한 문자열들을 string [] 배열로 리턴한다.
            
for(int i = 0; i < names.Length; i++)
    Console.WriteLine(names[i]);

// 출력
// Harry
// Potter

 

Substring(int) 👉 호출한 문자열의 인수에 해당하는 인덱스부터 문자열 끝까지를 리턴한다.

Substring(int, int) 👉 호출한 문자열의 첫 번째 인수에 해당하는 인덱스부터 두 번째 인수 길이만큼 리턴한다.

string name = "Harry Potter";
Console.WriteLine(name.Substring(5));

// 출력
// Potter

string name = "Harry Potter";
Console.WriteLine(name.Substring(5, 4));

// 출력
// Pott

 

 

 

반응형