프로그래밍 언어 - C#(6) 구조체와 열거형
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. 사용
using System;
class Program
{
static void Main(string[] args)
{
}
}
struct MonsterData
{
}
- 기본 형태.
- 사용은 다음과 같다.
using System;
class Program
{
static void Main(string[] args)
{
MonsterData monsterData;
}
}
struct MonsterData
{
}
- 변수 그 자체. 자료형 하나를 만듦 > 변수 선언. 심플하다.
using System;
class Program
{
static void Main(string[] args)
{
// 변수 그 자체. 자료형 하나를 만듦 > 변수 선언. 심플하다.
MonsterData monsterData;
}
}
struct MonsterData
{
public string Name;
public int Health;
public int AttackValue;
}
- 구조체 안에 넣은 자료형. 어떤 데이터들이 있는지. 이것을 MonsterData의 Field라고 부른다.
- 다음, 이제 값을 할당해보도록 한다.
using System;
class Program
{
static void Main(string[] args)
{
MonsterData monsterData;
}
}
struct MonsterData
{
public string Name = "용";
public int Health = 100;
public float AttackValue = 50f;
}
- 생성자가 필요하다고 나오며 불가해진다.
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;
}
- 저렇게 변수명을 쓰고 .을 누르면 가지고 있는 원소들에 접근이 가능해진다.
- 이름부터 설정해보도록 하자.
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;
}
- 출력이 가능해진다.
- 다른 것도 설정해보자.
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;
}
- 데이터가 설정한대로 출력이 정상적으로 이뤄진다.
- 이것을 함수로 만들자.
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. 생성자
- 데이터 형을 만들었을 때, 실제 변수로 만들고 데이터로 생성하는 과정에서 초기에 데이터를 넣어주게 할 수 있는 특수 함수.
- 구조체를 만들 때, 필드를 함께 채워주는 함수.
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()
{
}
}
- 구조체에서 반환형 없이 이렇게 하면 생성자가 된다.
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;
}
}
- 정상 출력.
- 다음은 아까 출력 함수를 구조체 안에서 사용할 때는 다음과 같다.
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 쪽 선언 시, 고블린 데이터에 접근해서 .찍고 접근했었다.
- 구조체 내부에서 했을 때는 내부 필드니까 .찍을 필요 없이 그대로 접근이 가능하다.
- 데이터 집단을 이렇게 다룰 수 있다.
- 이런 것들이 가능한 것이라서 그래서 이런 것도 가능했던 것.
string text = "123";
int number = int.Parse(text);
- 그러니까 int가 사실 구조체였기 때문.
- 생성하기 전에 정보를 얻으려고 하면 초기화가 안되있다고 발생. 따라서 데이터를 채워보도록 한다.
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. 사용
- 다음은 사용한 형태이다.
enum GenderType
{
Male,
Female
}
- 이제 자료형으로 쓸 수 있게 된다.
- 타입형으로 변환해보자.
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이다.
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부터 쭉 시작한다.
- 사람이 보기 좋을라고 쓰는 사항이다.
- 그런데 다음과 같은 사항이 있다.
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에서 처리는 어떻게 할지를 봐야 한다.
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형으로 형변환하여 사용 가능하다.
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));
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이 출력되고 만다.
- 다음은 플레이어 상태에 대한 것을 해보도록 하자.
using System;
using System.Xml;
class Program
{
static void Main()
{
}
}
enum PlayerState
{
Idle,
Move,
Attack,
Dead
}
- 이것을 고도화 시키면 상태 패턴에 적용해봄직 하다.
- 기반 형식을 정할 수 있는 것도 있다.
public enum ItemGrade : byte
{
Common,
Rare,
Epic,
Legendary
}
- 허나 당장은 기본형으로 사용하는 것이 나을 수 있다.
- 구조체 상에서 같다는 것을 비교하는 대상이 무엇인지 CPU는 인지를 못한다.
- 그래서 연산자 재정의(아직은 이르다)를 통해 선언을 하여 비교가 가능하게끔 할 수도 있음.




