프로그래밍 언어 - C#(6) 구조체와 열거형

이형눈이이상해
이형눈이이상해
∙∙조회 0

1. 복기

1.1. 자료형에서 흔히 나올 질문

  • 값 타입은 데이터를 메모리에 직접 보유한 형식이다. int, float, char, double 등.

<값 타입은 직접 데이터를 들고 있다>

  • 참조 타입은 데이터를 직접적으로 들지 않고 어딘가 메모리에 데이터가 있는 곳의 주소값을 들고 있는 형식을 말한다. 배열, string 같은 것이 이에 해당한다.

<주소값을 찾아가야 데이터를 취급할 수 있다>

  • CbV, Call by Value의 경우 값을 복사해서 함수 내부에서 이용하는 방식. 이 경우에는 함수 밖에서 반영되지 않는다.
  • CbR, Call by Reference의 경우는 주소값만을 복사하여 간접적으로 접근하게 되다 보니 주소를 타고 들어가 데이터를 수정 가능하다.

1.2. 향후 알아야 될 키워드

  • 객체지향 4대 원칙
    • 추상화
    • 캡슐화
    • 상속
    • 다형성
  • SOLID
    • S : SRP, 단일 책임의 원칙
    • O : OCP, 개방 폐쇄의 원칙
    • L : LSP, 리스코프 치환의 원칙
    • I : ISP, 인터페이스 분리의 원칙
    • D : DIP, 의존관계 역전의 원칙

2.구조체

2.1. 개요

  • 변수 선언, 필요할 때 갖다 쓰기, 수행할 로직 제어, 반복 작업, 배열을 통해 데이터 다수 제어를 했음. 그리고 기능에 별명을 붙여 갖다쓰기 쉬운 함수까지 알아봤다.
  • 하나만 생각해보면 몬스터에 대한 데이터를 다룰 것인데 다양한 자료형을 가질 수 있다.
  • 게임 내의 오브젝트 등이 다양한 데이터 유형을 가져야 하므로 이 다양한 데이터들을 묶어서 다룰 수 있는 단위의 필요성이 대두된다. 그게 구조체이다.
  • C#에서는 struct로 정의.
  • class와 비교되는 개념.
  • class는 참조 타입이고 구조체는 값 타입이다.
  • 구조체란, 관련있는 값을 여러개를 묶어 하나의 형식으로 만드는 문법이다.
  • 하나의 커스텀 자료형을 만드는 것이라고 생각하면 된다.
  • 심지어 기본적으로 제공하는 값 타입 자료형조차 구조체로 묶여있는 것을 다루고 있는 것이다.
  • 데이터 값이 가지는 것 말고도 추가적인 함수 등이 제공이 되고 있고 여러 데이터를 int라는 구조체로 다룰 수 있게 만든 것.

2.2. 사용

csharp
using System;

class Program
{
    static void Main(string[] args)
    {
        
    }
}

struct MonsterData
{
    
}

  • 기본 형태.
  • 사용은 다음과 같다.
csharp
using System;

class Program
{
    static void Main(string[] args)
    {
        MonsterData monsterData;
    }
}

struct MonsterData
{
    
}

  • 변수 그 자체. 자료형 하나를 만듦 > 변수 선언. 심플하다.
csharp
using System;

class Program
{
    static void Main(string[] args)
    {
        // 변수 그 자체. 자료형 하나를 만듦 > 변수 선언. 심플하다.
        MonsterData monsterData;
    }
}

struct MonsterData
{
    public string Name;
    public int Health;
    public int AttackValue;
}

  • 구조체 안에 넣은 자료형. 어떤 데이터들이 있는지. 이것을 MonsterData의 Field라고 부른다.
  • 다음, 이제 값을 할당해보도록 한다.
csharp
using System;

class Program
{
    static void Main(string[] args)
    {
        MonsterData monsterData;
    }
}

struct MonsterData
{
    public string Name = "용";
    public int Health = 100;
    public float AttackValue = 50f;
}

  • 생성자가 필요하다고 나오며 불가해진다.
csharp
using System;

class Program
{
    static void Main(string[] args)
    {
        MonsterData goblinData;
        MonsterData slimeData;
        MonsterData dragonData;
        
        goblinData.
    }
}

struct MonsterData
{
    public string Name;
    public int Health;
    public float AttackValue;
}

  • 저렇게 변수명을 쓰고 .을 누르면 가지고 있는 원소들에 접근이 가능해진다.
  • 이름부터 설정해보도록 하자.
csharp
using System;

class Program
{
    static void Main(string[] args)
    {
        MonsterData goblinData;
        MonsterData slimeData;
        MonsterData dragonData;
        
        goblinData.Name = "Goblin";
        Console.WriteLine(goblinData.Name);
    }
}

struct MonsterData
{
    public string Name;
    public int Health;
    public float AttackValue;
}

  • 출력이 가능해진다.
  • 다른 것도 설정해보자.
csharp
using System;

class Program
{
    static void Main(string[] args)
    {
        MonsterData goblinData;
        MonsterData slimeData;
        MonsterData dragonData;
        
        goblinData.Name = "고블린";
        goblinData.Health = 200;
        goblinData.AttackValue = 20f;
        Console.WriteLine("--------------------------------------");
        Console.WriteLine($"이름: {goblinData.Name}");
        Console.WriteLine($"체력: {goblinData.Health}");
        Console.WriteLine($"공격력: {goblinData.AttackValue}");
        Console.WriteLine("--------------------------------------");
    }
}

struct MonsterData
{
    public string Name;
    public int Health;
    public float AttackValue;
}

  • 데이터가 설정한대로 출력이 정상적으로 이뤄진다.
  • 이것을 함수로 만들자.
csharp
using System;

class Program
{
    static void Main(string[] args)
    {
        MonsterData goblinData;
        MonsterData slimeData;
        MonsterData dragonData;
        
        goblinData.Name = "고블린";
        goblinData.Health = 200;
        goblinData.AttackValue = 20f;
        PrintMonsterData(goblinData);
    }

    static void PrintMonsterData(MonsterData monsterData)
    {
        Console.WriteLine("--------------------------------------");
        Console.WriteLine($"이름: {monsterData.Name}");
        Console.WriteLine($"체력: {monsterData.Health}");
        Console.WriteLine($"공격력: {monsterData.AttackValue}");
        Console.WriteLine("--------------------------------------");
    }
}

struct MonsterData
{
    public string Name;
    public int Health;
    public float AttackValue;
}

  • 함수로 하면 용이하게 다른 것에도 사용 가능하다.
  • 각 몬스터에 대한 것을 설정해야 하는 것은 피로감이 있다. 따라서 생성자라는 것을 볼 것이다.

2.3. 생성자

  • 데이터 형을 만들었을 때, 실제 변수로 만들고 데이터로 생성하는 과정에서 초기에 데이터를 넣어주게 할 수 있는 특수 함수.
  • 구조체를 만들 때, 필드를 함께 채워주는 함수.
csharp
using System;

class Program
{
    static void Main(string[] args)
    {
        MonsterData goblinData;
        MonsterData slimeData;
        MonsterData dragonData;
       
    }

    static void PrintMonsterData(MonsterData monsterData)
    {
        Console.WriteLine("--------------------------------------");
        Console.WriteLine($"이름: {monsterData.Name}");
        Console.WriteLine($"체력: {monsterData.Health}");
        Console.WriteLine($"공격력: {monsterData.AttackValue}");
        Console.WriteLine("--------------------------------------");
    }
}

struct MonsterData
{
    public string Name;
    public int Health;
    public float AttackValue;

    // 
    public MonsterData()
    {
        
    }
}

  • 구조체에서 반환형 없이 이렇게 하면 생성자가 된다.
csharp
using System;

class Program
{
    static void Main(string[] args)
    {
        MonsterData goblinData = new MonsterData("Goblin", 100, 20f);
        //goblinData = new MonsterData("Goblin", 100, 20f);
        MonsterData slimeData;
        MonsterData dragonData;

        PrintMonsterData(goblinData);
    }

    static void PrintMonsterData(MonsterData monsterData)
    {
        Console.WriteLine("--------------------------------------");
        Console.WriteLine($"이름: {monsterData.Name}");
        Console.WriteLine($"체력: {monsterData.Health}");
        Console.WriteLine($"공격력: {monsterData.AttackValue}");
        Console.WriteLine("--------------------------------------");
    }
}

struct MonsterData
{
    public string Name;
    public int Health;
    public float AttackValue;

    public MonsterData(string name, int health, float attackvalue)
    {
        Name= name;
        Health = health;
        AttackValue = attackvalue;
    }
}

  • 정상 출력.
  • 다음은 아까 출력 함수를 구조체 안에서 사용할 때는 다음과 같다.
csharp
using System;

class Program
{
    static void Main(string[] args)
    {
        MonsterData goblinData = new MonsterData("고블린", 100, 20f);
        //goblinData = new MonsterData("Goblin", 100, 20f);
        MonsterData slimeData;
        MonsterData dragonData;
        
        // 출력 함수를 구조체에 구현한 것을 사용할 때
        goblinData.PrintInfo();
    }

}

struct MonsterData
{
    public string Name;
    public int Health;
    public float AttackValue;

    public MonsterData(string name, int health, float attackvalue)
    {
        Name= name;
        Health = health;
        AttackValue = attackvalue;
    }
    
    public void PrintInfo()
    {
        Console.WriteLine("--------------------------------------");
        Console.WriteLine($"이름: {Name}");
        Console.WriteLine($"체력: {Health}");
        Console.WriteLine($"공격력: {AttackValue}");
        Console.WriteLine("--------------------------------------");
    }
}

  • main 쪽 선언 시, 고블린 데이터에 접근해서 .찍고 접근했었다.
  • 구조체 내부에서 했을 때는 내부 필드니까 .찍을 필요 없이 그대로 접근이 가능하다.
  • 데이터 집단을 이렇게 다룰 수 있다.
  • 이런 것들이 가능한 것이라서 그래서 이런 것도 가능했던 것.
csharp
string text = "123";
int number = int.Parse(text);

  • 그러니까 int가 사실 구조체였기 때문.
  • 생성하기 전에 정보를 얻으려고 하면 초기화가 안되있다고 발생. 따라서 데이터를 채워보도록 한다.
csharp
using System;

class Program
{
    static void Main(string[] args)
    {
        MonsterData goblinData = new MonsterData("고블린", 100, 20f);
        MonsterData slimeData = new MonsterData("슬라임", 50, 5f);
        MonsterData dragonData = new MonsterData("드래곤", 100000, 900f);

        
        goblinData.PrintInfo();
        slimeData.PrintInfo();
        dragonData.PrintInfo();
    }
}

struct MonsterData
{
    public string Name;
    public int Health;
    public float AttackValue;

    public MonsterData(string name, int health, float attackvalue)
    {
        Name= name;
        Health = health;
        AttackValue = attackvalue;
    }
    
    public void PrintInfo()
    {
        Console.WriteLine("--------------------------------------");
        Console.WriteLine($"이름: {Name}");
        Console.WriteLine($"체력: {Health}");
        Console.WriteLine($"공격력: {AttackValue}");
        Console.WriteLine("--------------------------------------");
    }
}

  • 각자가 별개의 데이터를 가질 수 있다고 확인이 된다.
  • 처음에 혼동할 수 있는 것은 MonsterData라는 구조체를 생성해서 필드들을 가지는데 어떻게 다르게 나오는지 생각할 수 있음.
  • class에서도 언급해둘 것이지만 양식만을 만들고 따로 생성해서 넣어줬다고 생각하면 편하다.

2.4. 메모리 할당

  • MonsterData는 값이 직접적으로 가지고 있는 형태이다.
  • 내부에서는 Name, Health, AttackValue가 존재한다.(생성자와 함수는 일단 논외로 따지도록 한다.)
  • 데이터 자체는 일단 이렇게 3가지가 MonsterData에 할당되어 있다.
  • 이 순간, MonsterData인데 goblinData라고 할당이 된다. 각 메모리 주소로 데이터가 할당이 된다.
  • 그런데 Name은 string, 참조 타입이기 때문에 주소값만 담기고 어딘가에 string의 원본이 자리한다(또 다른 주소가 존재하나 일단 간소화).
  • slimeData도 똑같이 갈 것이다.
  • 이런식으로 그려가면서 도식화가 자동으로 되도록 훈련할 것.

2.5. 실습 팁

  • 포켓몬 구현 가능할 것.
  • 데이터 단위를 묶어서 사용하는 것이 구조체이다.
  • 대입이 가능할만한 요소는 유저 데이터라고 볼 수 있다.
  • 또 소재 사항으로 퀘스트가 될 수도 있다.

## 3. 열거형

3.1. 개요

  • 유저 데이터를 판별할 때, 프로그램 내에서 약속을 한다고 한다면 다른 사람이 보더라도 알 수 있어야 한다.
  • 핵심은 들어갈 숫자 등에 별명을 붙여줄 수 있다.

3.2. 사용

  • 다음은 사용한 형태이다.
csharp
enum GenderType
{
    Male,
    Female
}

  • 이제 자료형으로 쓸 수 있게 된다.
  • 타입형으로 변환해보자.
csharp
using System;

class Program
{
    static void Main()
    {
        UserData user1=new UserData("거여동44세사일러스못함", GenderType.Male);
    }
}

struct UserData
{
    public string Name;
    public GenderType Gender;

    public UserData(string name, GenderType gender)
    {
        Name = name;
        Gender = gender;
    }
    public void PrintInfo()
    {
        Console.Write($"이름: {Name} / 성별: ");
        if (Gender == GenderType.Male)
        {
            Console.Write("남쟈!");
        }
        else if (Gender == GenderType.Female)
        {
            Console.Write("여짜!");
        }
    }
}
  • 숫자에 별명을 붙인다고 했냐면 GenderType을 선언한 곳을 가서 마우스를 올려보면 Male은 0, Female은 1로 발생한다.
  • 내부적으로 정수형인데 사람이 보면 알 수 있도록 한 것이 Enum이다.
csharp
using System;
using System.Xml;

class Program
{
    static void Main()
    {
        
    }
}

enum PokemonType
{
    Normal = 5,
    Fire,
    Water,
    Grass,
    Thunder,
    Ice,
    Fight,
    Poison,
    Ground,
    Flying,
    Esper,
    Bug,
    Rock,
    Ghost,
    Dragon,
    Evil,
    Steel,
    Fairy
}

  • 아까처럼 대응되는 숫자가 확인할 수 있음(아무것도 적지 않으면 자동으로 0 ~ 17).
  • 또한 숫자를 5부터 하면 5부터 쭉 시작한다.
  • 사람이 보기 좋을라고 쓰는 사항이다.
  • 그런데 다음과 같은 사항이 있다.
csharp
using System;
using System.Xml;

class Program
{
    static void Main()
    {
        
    }
}

enum PokemonType
{
    Normal = 5,
    Fire,
    Water,
    Grass = 20,
    Thunder,
    Ice,
    Fight = 15,
    Poison,
    Ground,
    Flying,
    Esper,
    Bug,
    Rock,
    Ghost,
    Dragon,
    Evil,
    Steel,
    Fairy
}

  • 중복이 되는 것이 보이는데 CPU에서 처리는 어떻게 할지를 봐야 한다.
csharp
using System;
using System.Xml;

class Program
{
    static void Main()
    {
        Console.WriteLine(PokemonType.Bug);
        Console.WriteLine(PokemonType.Grass);
    }
}

enum PokemonType
{
    // 대응되는 숫자가 확인할 수 있음. 
    // 숫자를 5부터 하면 5부터 쭉 시작한다. 
    Normal = 5,
    Fire,
    Water,
    Grass = 20,
    Thunder,
    Ice,
    Fight = 15,
    Poison,
    Ground,
    Flying,
    Esper,
    Bug,
    Rock,
    Ghost,
    Dragon,
    Evil,
    Steel,
    Fairy
}

  • 이럴 경우에는 가장 앞서서 나온 것으로 가게 된다.
  • 음수도 가능하다.
  • int형으로 형변환하여 사용 가능하다.
csharp
using System;
using System.Xml;

class Program
{
    static void Main()
    {
        Console.WriteLine((PokemonType)-7);
        Console.WriteLine((PokemonType)1);
    }
}

enum PokemonType
{
    // 대응되는 숫자가 확인할 수 있음. 
    // 숫자를 5부터 하면 5부터 쭉 시작한다. 
    Normal = -7,
    Fire,
    Water,
    Grass,
    Thunder,
    Ice,
    Fight,
    Poison,
    Ground,
    Flying,
    Esper,
    Bug,
    Rock,
    Ghost,
    Dragon,
    Evil,
    Steel,
    Fairy
}

  • 위의 것은 오류가 뜬다. 그래서 괄호를 통해 나눠줘야 한다. ex) Console.WriteLine((PokemonType)(-7));
csharp
using System;

class Program
{
    static void Main()
    {
        Console.WriteLine((PokemonType)20);
    }
}

enum PokemonType
{
    Normal,
    Fire,
    Water,
    Grass,
    Thunder,
    Ice,
    Fight,
    Poison,
    Ground,
    Flying,
    Esper,
    Bug,
    Rock,
    Ghost,
    Dragon,
    Evil,
    Steel,
    Fairy
}

  • 이럴 경우는 그냥 20이 출력되고 만다.
  • 다음은 플레이어 상태에 대한 것을 해보도록 하자.
csharp
using System;
using System.Xml;

class Program
{
    static void Main()
    {
        
    }
}

enum PlayerState
{
    Idle,
    Move,
    Attack,
    Dead
}
  • 이것을 고도화 시키면 상태 패턴에 적용해봄직 하다.
  • 기반 형식을 정할 수 있는 것도 있다.
csharp
public enum ItemGrade : byte  
{  
    Common,  
    Rare,  
    Epic,  
    Legendary  
}

  • 허나 당장은 기본형으로 사용하는 것이 나을 수 있다.
  • 구조체 상에서 같다는 것을 비교하는 대상이 무엇인지 CPU는 인지를 못한다.
  • 그래서 연산자 재정의(아직은 이르다)를 통해 선언을 하여 비교가 가능하게끔 할 수도 있음.