[C#] static, Inheritance, Abstract

Lingtea_luv·2025년 3월 25일
0

C#

목록 보기
12/37
post-thumbnail

static


static

프로그램 내부에서 메모리 공간을 요청하면 임의의 공간이 할당되어, 사용하고자 하는 데이터를 보관하고, 사용이 끝났을 때 할당을 해제한다고 알고 있었다. 특히 힙영역에 저장되는 클래스나 배열의 경우 프로그램 실행 중에도 저장된 위치가 계속 바뀌는 동적인 모습을 보이는데, 이와 반대로 메모리 한 공간을 고정적으로 계속 차지하는 정적 메모리를 static이라고 한다.

  • static 변수

static 키워드로 변수를 선언할 경우 프로그램이 종료될 때까지 할당이 해제되지 않고 데이터 영역에 저장된다. 다만 사용시 주의를 요하는데 할당 해제가 되지 않는다는 말은, 필요하지 않은 경우에도 계속 저장이 되어있다는 것과 같으므로 이는 불필요하게 메모리를 사용하여 메모리 낭비를 유발시키기 때문이다.
1. 프로그램 구동 중 항상 사용해야하는 데이터
2. 어디서든 사용할 수 있는 데이터(전역적인 접근이 가능)

  • static 메서드

static 키워드로 선언된 메서드의 경우 간편하게 호출할 수 있는 정적 메서드로 활용할 수 있다. 다만 메서드의 경우 데이터 영역에 저장되는 것은 아니고 단순히 전역적으로 사용 가능하게끔 만들기 위해 사용된다.

  • static class

static class Setting 
{
    public int screen;   // static이 아니어서 선언 불가
    public static int volume = 100; 
}

static 키워드로 선언된 class의 경우 모든 멤버 변수와 멤버 함수가 static인 class를 의미한다. 인스턴스를 생성할 수 없으며, 인스턴스를 생성할 필요가 없는 경우 static으로 선언한다. 메서드와 마찬가지로 데이터 영역에 저장되는 것이 아니고 전역적으로 사용가능하도록 만든다. ( ex. Console )

class Monster  // 힙 영역에 저장
{
	public int hp1 = 200;

	public static void WrongTakeDamage(int damage)
	{                                                
    	Console.WriteLine("몬스터가 데미지를 받았습니다.");
    	hp1 -= damage;                                     
    	Console.WriteLine($"몬스터의 체력은 {hp} 입니다.");
	}
}

위의 코드에는 2가지 문제가 존재한다.

  • 멤버 함수의 static 선언
    멤버 함수는 class가 가지는 고유한 기능이라고 볼 수 있다. 하지만 이를 static으로 선언할 경우 class 밖에서도 사용이 가능하기 때문에, class의 취지에 맞지 않은 행위이다.
  • static으로 멤버 함수를 선언한 경우 static으로 선언하지 않은 멤버 변수는 사용할 수 없다. 인스턴스 생성이 되지 않았기에 hp1은 존재하지않기 때문이다.

상속(Inheritance)


상속이란?

객체지향 프로그램의 특징으로 코드의 중복을 줄이고, 유지 보수성을 개선할 수 있는 기능이다. 공통점을 묶어 부모 클래스를 두고, 여기서 자식 클래스를 파생시켜 중복된 코드를 줄이고, 간결하게 작성할 수 있다.

class Monster
{
    public int hp;
    public float speed;
    
    public void Move()
    {
    	Console.WriteLine($"{speed}로 이동합니다.")
    }
}

class Slime : Monster
{
    public void Split()
	{
    	Console.WriteLine("몸체를 2개로 분열합니다.");
	}
}

먼저 부모 클래스를 생성하고, 자식 클래스를 파생시키는 경우, 자식 클래스는 부모 클래스의 속성을 물려받게된다. Monster 라는 부모 클래스의 필드 hp speed와 메서드 Move() 모두 파생시킨 Slime 이라는 자식 클래스에서도 사용이 가능하다. 하지만 Slime 에서 새롭게 정의된 멤버 함수 Split의 경우 Slime 만 사용가능하다.

static void Main(string[] args)
{
    Slime slime = new Slime();
    Dragon dragon = new Dragon();

    slime.Move();
    dragon.Move();

    slime.speed = 10;
    dragon.speed = 20;
}

위처럼 각각의 class로 인스턴스를 생성했을 때 부모 클래스 Monster의 속성을 모두 사용가능하다는 것을 볼 수 있다.

protected

class Monster
{
    public int speed;
    private bool value;
    protected string property;
}

public Slime()
{
    speed = 10;        // 기본적으로 this.speed를 의미한다.
    this.speed = 20;   // 자식에서 선언된 speed
    base.speed = 30;   // 부모에서 선언된 speed
    value = true;      // 부모에서 private으로 선언시 자식에서 사용x
    property = "액체";  // 부모에서 protected로 선언시 자식에서 사용 가능
}

상속에서 protected 접근제한자가 등장하는데 부모 클래스에서 protected로 선언된 것은 외부에서 사용할 수 없고, 자식 클래스에서 사용이 가능하다. 물론 privatepublic은 앞서 배운 내용과 동일하다.

Overoading

class Player
{
    public void Use(Item item)
    {
		// 아이템 사용시 구동되는 함수
    }
    public void Use(Skill skill)
    {
		// 스킬 사용시 구동되는 함수
    }
}

함수 오버로딩은 같은 이름의 함수를 매개변수를 다르게 하여 중복 사용이 가능하도록 만들어주는 것이다. 위처럼 Use 라는 동일한 이름의 함수여도, 매개변수의 종류가 다르면 모두 정의할 수 있다. (ex. Console.WriteLine() )

sealed

sealed class Player
{
}

class AdvancedPlayer : Player
{
    // 응 그런거 없어 돌아가~
}

class의 상속을 막기 위해서 class 선언시 sealed 키워드를 붙이면 된다. 위처럼 sealed를 붙여 정의를 하게 되면 상속이 불가능하며, class AdvancedPlayer 생성이 불가능해진다.

업 캐스팅

class Monster
{
}
class Slime : Monster
{
}
static void Main(string[] args)
{
	Monster monster = new Slime();
}

위처럼 자식클래스는 부모클래스 자료형으로 암시적 형변환이 가능한데, 이를 업캐스팅이라고 한다. 이때 Slime 생성자를 활용하여 인스턴스를 생성했지만, Monster에 담았기 때문에 Slime 전용 함수는 사용이 불가능하다. 즉 업캐스팅이 된 경우에는 부모의 기능만 사용가능하다.

Monster[] field = new Monster[3];
field[0] = monster0;
field[1] = monster1;
field[2] = new Slime();

업 캐스팅을 사용하는 것에는 위처럼 부모 클래스 배열에 자식 클래스로 생성한 인스턴스를 넣어야할 때 사용하는 등 다양한 이유가 존재한다.

다운 캐스팅

Slime slime = (Slime)monster     -1 

if (monster is Slime)            -2
{
    Slime slime = (Slime)monster;
    slime.Split();
}
(monster as Slime).Split();      -3

업캐스팅과 반대로 부모 클래스를 자식 클래스의 자료형으로 형변환하는 것을 다운 캐스팅이라하며 여러 방법이 존재한다. 먼저 1번 방식은 결과를 알 수 없기 때문에 굉장히 위험한 방식으로 2,3번을 많이 사용한다. 2번은 is 키워드를 활용하는 방식으로, monsterSlime인 경우 맞으면 true 틀리면 false에 해당하는 bool값을 반환하는데 이렇게 형변환을 하게되면 안전하게 변환을 할 수 있다. 마찬가지로 3번의 as 경우 monsterSlime인 경우 Slime 으로 바꾸어 기능을 수행한다는 뜻으로 is와 함께 많이 사용한다.

생성자

상속 개념에서 자식 클래스의 인스턴스를 생성할 때 부모 클래스의 생성자도 함께 실행되는 것을 볼 수 있다. 이를 통해 부모 클래스의 생성자 형식이 자식 클래스의 생성자 형식에 영향을 준다는 것을 알 수 있다.

class Car
{
    public Car(string name, float speed)
    {
        this.name = name;
        this.speed = speed;
        Console.WriteLine("부모생성자");
    }
}
class Truck : Car
{
    public Truck() : base("트럭", 10)
    {
        capacity = 10;
        Console.WriteLine("자식생성자");
    }
}

위처럼 자식 클래스의 생성자를 정의할 때 : base 키워드로 부모 클래스의 생성자를 올바른 형식으로 지정해야 오류가 뜨지 않는다.

class Truck : Car
{
    public Truck(string name, float speed) : base(name, speed)
    {
        Console.WriteLine("자식생성자");
    }
}

또한 이렇게 부모 클래스 생성자의 형식을 맞추었다면, 자식 클래스의 생성자는 자유롭게 정의하는 것이 가능하다.

다형성(Polymophism)


다형성

다형성이란 객체의 속성이나 기능이 상황에 따라 여러가지 형태를 가질 수 있는 성질을 뜻하며 그 의미는 다음과 같다.
1. 새로운 클래스 추가, 확장 시 기존 코드 수정 및 영향을 최소화
2. 클래스 간 의존성은 줄이고 확장성은 높임

public class Car
{
    public string name;
    public int speed;

    public Car(string name, int speed)
    {
        this.name = name;
        this.speed = speed;
    }

    public void Move()
    {
        Console.WriteLine($"{name}이(가) {speed}의 속도로 이동합니다.");
    }
}
static void Main(string[] args)
{
    Car sportCar = new Car("스포츠 카", 100);
    Car truck = new Car("트럭", 20);

    sportCar.Move();
    truck.Move();
스포츠 카이() 100의 속도로 이동합니다.
트럭이() 20의 속도로 이동합니다.

Overriding

오버라이딩은 부모 클래스의 가상함수를 동일한 이름 및 매개변수로 자식 클래스에서 재정의하여 자식 클래스만의 기능으로써 작동하도록 만드는 것이다.

public class Skill
{
    public string name;
    public float coolTime;
    
    public virtual void Execute()
    {
        Console.WriteLine($"{name}스킬을 사용합니다.");
        Console.WriteLine($"{coolTime} 쿨타임을 진행합니다.");
    }
}
public class Smash : Skill
{
    public Smash()
    {
        name = "강타";
        coolTime = 2.5f;
    }
    public override void Execute()
    {
        Console.WriteLine("강력한 일격!!");
        base.Execute();
    }
}
  • virtual : 부모 클래스 함수 중 자식 클래스에 의해 재정의할 가상함수를 지정
  • override : 지정된 가상함수를 덮어쓸 함수를 지정
  • base.Execute(); : 가상함수의 기능을 활용하고 싶을 때 base. 을 활용

추상


추상 클래스

일반 클래스 처럼 사용할 수 있지만, 이를 상속 받은 자식 객체들에게 정확하게 정의되지 않은 메서드를 물려주고, 자식 객체에게 반드시 이를 구현하도록 하는 기능이다. 이때 부모 클래스에 있는 추상 함수의 내용은 비어있어야하며, 해당 모습이 추상적이다는 의미로 추상 함수라고 말한다.

public abstract class Item
{
    public abstract void Use();    
}

public class Potion : Item
{
    public override void Use()
    {
        Console.WriteLine("포션을 사용하여 체력을 회복합니다.");
    }
}

추상 함수가 있는 부모(추상) 클래스는 선언시 abstract 키워드를 사용하며, 상속받은 자식 클래스는 반드시 추상 함수를 구체화해야한다.

사용 의의

  1. 구현이 어려운 상위 클래스를 설계하기 위한 수단
  2. 상위 클래스의 인터페이스를 구현하기 위한 수단
  3. 추상적인 기능을 구체화하지 않은 경우 인스턴스 생성이 불가능
  4. 자식 클래스의 추상 함수의 구현을 의무화하여, 실수를 방지

과제


다음 조건에 맞도록 포켓몬에 대한 데이터를 구현하세요.

  • 포켓몬은 최소 6종류 이상 구현한다.
  • Class Trainer
    필드로 포켓몬을 보관할 수 있는 배열을 6의 크기로 가진다
    현재 꺼내져 있는 포켓몬 인스턴스에 대한 참조를 필드로 가진다
    Pick(int index) : 배열 기준으로 입력받은 인덱스(매개변수)에 있는 포켓몬을 '현재 꺼내져 있는 포켓몬을 참조하는 필드'에 대입한다
    Print() : 자신이 가진 모든 모든 포켓몬의 이름을 출력한다
  • abstract class Pokemon
    모든 포켓몬의 공통 부모 클래스로 동작한다.
    모든 포켓몬은 공통으로 Attack() 이라는 기능을 가지며, 포켓몬마다 다른 공격에 대한 내용을 콘솔에 출력할 수 있어야 한다.

Class Trainer

public class Trainer
{
    public string Name;
    public Pokemon[] _Pokemon = new Pokemon[6];
    public Pokemon currentPokemon;

    public void Pick(int index, Pokemon monster)
    {
        if (_Pokemon[index] == null)
        {
            currentPokemon = monster;
            _Pokemon[index] = currentPokemon;
        }
    }

    public void Print()
    {
        Console.WriteLine($"<{Name}의 포켓몬>");
        for (int i = 0; i < 6; i++)
        {
            if (_Pokemon[i] != null)
            {
                Console.WriteLine(_Pokemon[i].Name);
            }
            else
            {
                continue;
            }
        }
    }
    public Trainer()
    {
        Name = "이석원";
    }
}

Pick() 함수를 구현할 때 몬스터볼로 포켓몬을 잡는 행위를 구현하고자 했다. 따라서 매개변수에 입력하는 indexPokemon monster는 가지고 있는 몬스터볼의 순서와 잡을 포켓몬이라고 보면 된다.

abstract class Pokemon

public abstract class Pokemon
{
    public string Name;
    public string SkillName;

    public abstract void Attack();
}

public class Mangnanyong : Pokemon
{
    public Mangnanyong()
    {
        this.Name = "망나뇽";
        this.SkillName = "역린";
    }

    public override void Attack()
    {
        Console.WriteLine($"{Name}이 {SkillName}을 시전합니다.");
    }
}

부모 클래스 Pokemon 에 추상 함수 Attack() 을 추가하여 자식 클래스에서 반드시 구현되도록 설정했다.

Main 메서드

static void Main(string[] args)
{
    Pokemon monster0 = new Isang();
    Trainer player = new Trainer();
    monster0.Attack();

    Console.WriteLine("");

    player.Pick(0, monster0);
    player.Print();
}

이상해씨와 플레이어의 인스턴스를 생성하고 각 class의 함수를 구현시키고자 했다.

profile
뚠뚠뚠뚠

0개의 댓글