싱글톤 패턴 (Singleton Patton)

싱글톤 패턴이란 클래스의 인스턴스를 오직 하나만 생성하도록 보장하는 디자인 패턴이다.

이를 통해 전역적으로 접근할 수 있는 유일한 객체를 제공하며 보통 설정, 데이터 관리, 게임 메니저 등에서 활용된다.


싱글톤 구현

public class Singleton
{
    private static Singleton instance;  // 인스턴스 저장 변수
    private static readonly object lockObject = new object(); // 동기화를 위한 객체

    // 외부에서 객체 생성 금지
    private Singleton() { }

    public static Singleton Instance
    {
        if (instance == null)
            {
                if (instance == null)
                {
                	instance = new Singleton();
                }
            }
            return instance;
    }

    public void SingletonCalling()
    {
        Console.WriteLine("싱글톤 인스턴스의 메서드 호출!");
    }
}

싱글톤 패턴 장/단점

장점

  1. 전역 접근 가능 - 어디서든 동일한 인스턴스를 쉽게 사용할 수 있다
  2. 메모리 절약 - 하나의 인스턴스만 유지하여 불필요한 객체 생성을 방지한다.
  3. 데이터 일관성 유지 - 전역적으로 동일한 데이터를 유지할 수 있어 설정값이나 상태 관리가 편리하다.

단점

  1. 테스트가 어려움 - 의존성 주입이 어렵기 때문에 단위 테스트 작성이 어려워질 수 있다.
  2. 의존성이 높아짐 - 특정 클래스에 강하게 결합되어 유지보수가 어려워질 수 있다.
  3. 멀티스레드 환경에서 동기화 문제 발생 가능 - 다중 스레드 환경에서 예상하지 않은 버그가 발생할 수 있다.

 

싱글톤 패턴 사용 시 주의할 점

1. 불필요한 싱글톤 남용 금지

     - 모든 클래스를 싱글톤으로 만들면 코드가 복잡해지고 유지보수가 어려워진다.

     - 전역 상태를 너무 많이 공유하면 예상치 못한 버그가 발생할 가능성이 높다.

2. 멀티스레드 환경 고려

     - 싱글톤이 여러 스레드에서 동시에 접근될 경우, lock을 사용하여 안전하게 동작하도록 해야 한다.

3. 유닛 테스트 어려움 해결

     - 인터페이스 또는 의존성 주입을 사용하여 테스트 가능하도록 설계하는 것이 좋다.


결론

싱글톤 패턴을 잘 사용하면 메모리 절약을 하면서 전역에서 접근이 가능하지만, 무분별하게 사용하면 유지보수가 어려워질 수 있다.

'내일배움캠프 > C# 이론' 카테고리의 다른 글

C# 입문 (변수와 자료형)  (0) 2025.01.24

문제 : 사용자로부터 배열을 입력받아 내림차순으로 정렬하는 함수를 작성하시오

 

내가 쓴 풀이 : 이중 for문을 이용하여 직접 하나씩 하니씩 비교해가며 정렬 하는 방식으로 작성하였다.

using System;

class Program
{
    static void Main(string[] args)
    {
        Console.Write("배열의 숫자를 공백으로 구분하여 입력하세요: ");
        string input = Console.ReadLine(); // 사용자 입력 받기
        string[] strArr = input.Split(' '); // 공백 기준으로 나누기
        int[] intArr = Array.ConvertAll(strArr, int.Parse); // 문자열 배열을 정수 배열로 변환

        // 정렬 (버블 정렬 방식)
        for (int i = 0; i < intArr.Length - 1; i++)
        {
            for (int j = 0; j < intArr.Length - 1 - i; j++)
            {
                if (intArr[j] < intArr[j + 1]) // 내림차순 정렬
                {
                    int temp = intArr[j];
                    intArr[j] = intArr[j + 1];
                    intArr[j + 1] = temp;
                }
            }
        }

        Console.Write("정렬된 배열: ");
        foreach (int num in intArr)
        {
            Console.Write(num + " ");
        }
    }
}

 

 


 

다른 방법 : 기능함수인 Array.Sort()를 사용하여 배열을 먼저 오름차순으로 정렬을 해주고 Array.Reverse()를 이용하여 오름차순으로 정렬된 배열을 뒤집어 내림차순으로 변경해준다

class Program
{
    static void Main(string[] args)
    {
        Console.Write("배열의 숫자를 공백으로 구분하여 입력하세요: ");
        string input = Console.ReadLine();
        int[] intArr = Array.ConvertAll(input.Split(' '), int.Parse);

        Array.Sort(intArr); // 배열 정렬 (오름차순)
        Array.Reverse(intArr);

        Console.WriteLine("정렬된 배열: " + string.Join(" ", intArr));
    }
}

 

 

내가 쓴 풀이와 다른 풀이를 비교 : 내가 풀이는 이중 for을 사용하여 버블 정렬로 배열을 정리하고 다른 풀이는 제공 메서드를 이용하여 퀵 정렬로 배열을 정리한다. 그래서 속도 성능을 생각했을때 내가 쓴 풀이는 이중 for문으로 인해 불필요한 계산이 발생해 속도가 느려지고 다른풀이는 C# 기본 제공 메서드를 사용하여 내부적으로 빠르게 연산하여 속도가 빠르다.

그래서 배열이 크고 빠른 연산이 필요할때는 다른 풀이 처럼 Array.Sort() 메서드를 사용하고 정렬 알고리즘을 직접 구현해야 하거나 커스텀이 필요할 경우 이중 for문을 이용한 버블 정렬이 용이하다고 한다.

 

특정 문자 제거하기

문제 설명 

문자열 my_string 과 문자 letter 이 매개변수로 주어집니다. my_string에서 letter를 제거한 문자열을 return하도록 solution 함수를 완성해주세요.

 

제한사항

- 1 ≤ my_string의 길이 ≤ 100

- letter은 길이가 1인 영문자입니다

- my_string과 letter은 알파벳 대소문자로 이루어져 있습니다

- 대소문자와 소문자를 구분합니다

 

입출력 예

my_string letter result
"abcdef" "f" "abcde"
"BCBdbe" "B" "Cdbe"

solution.c

using System;

public class Solution {
    public string solution(string my_string, string letter) {
        string answer = "";
        return answer;
    }
}

 

 

이 문제는 문자열에서 특정 문자열을 제거 하는 문제이니 Replace()를 사용해서 입력한 문자열을 받아 입력한 문자열과 같은 문자열은 제거해준다.

 

using System;

public class Solution {
    public string solution(string my_string, string letter) {
        string answer = "";
        answer = my_string.Replace(letter,"");
        return answer;
    }
}

 

 


문자열의 뒤의 n글자

문제 설명

문자열 my_string과 정수 n이 매개변수로 주어질 때, my_string의 뒤의 n 글자로 이루어진 문자열을 return 하는 solution 함수를 작성해 주세요

 

제한사항

- my_string은 숫자와 알파벳으로 이루어져 있습니다.

- 1 my_string 의 길이 1,000

- 1 n my_string의 길이

 

입출력 예

my_string n result
"ProgrammerS123" 11 "grammerS123"
"He110W0r1d" 5 "W0r1d"
using System;

public class Solution {
    public string solution(string my_string, int n) {
        string answer = "";
        return answer;
    }
}

 

이 문제는 문자열의 길이를 얼만큼 자르는 문제로 Substring([시작위치], [문자열길이])를 사용하여

시작위치를 문자열의 길이를 측정해서 입력한 숫자를 빼줘서 시작위치를 정하고 뒤에 문자열 길이는 입력 하지 않으면 끝까지 출력을 하니 빈칸으로 둔다.

 

using System;

public class Solution {
    public string solution(string my_string, int n) {
        string answer = "";
        answer = my_string.Substring(my_string.Length-n);
        return answer;
    }
}

 

어제 만들었던 Text Rpg를 이어서 인벤토리를 구현해자

 

public interface Item
{
    public string Name { get; }
    public int State { get; }
    public string ItemType { get; }
    public string Description { get; }
}

public class Items
{
    public string Name { get; set; }
    public int State { get; set; }
    public string ItemType { get; set; }
    public string Description { get; set; }
    public bool IsEquip;

    public Items(string name, string itemType, int state, string description, bool isEquip)
    {
        Name= name;
        State = state;
        ItemType = itemType;
        Description = description;
        IsEquip = isEquip;
    }
}

어제 플레이어를 만들때 사용했던 인터페이스를 다시 사용하여 아이템의 틀을 만들어 준다.

 

이제 만들어진 아이템의 정보를 담을 리스트를 만들어 주고 게임을 시작할 때  아이템 리스트를 초기화기능을 호출 할수 있게 만들어 준다.

public class Inventory
{
    private List<Items> ItemList; //아이템 저장 리스트

    public Inventory()
    {
        ItemList = new List<Items>(); // 아이템 리스트 초기화
    }

    public void AddItems(Items items) //아이템을 인벤토리에 추가하는 메서드
    {
        ItemList.Add(items);
    }

    public void ShowInven() // 인벤토리에 있는 아이템 출력
    {
        for (int i = 0; i < ItemList.Count; i++)
        {
            if (ItemList[i].IsEquip == true) Console.Write("[E] ");
            Console.WriteLine($"{ItemList[i].Name} | {ItemList[i].ItemType} + {ItemList[i].State} | {ItemList[i].Description}");
        }
    }
    public void ItemEquipManger(Player player)
    {
        //아이템 장착 관리
    }
}

 

이렇게 만들어진 인벤토리는 Main에서 인벤토리 생성후 아이템 리스트를 초기화 해주고

class Program
{
    static void Main(string[] args)
    {
        
        Console.WriteLine("무한의 던전에 오신 여러분 환영합니다.");
        /*.... 처음 인트로 ...*/

        Player player = new Player(name, select);
        
        Inventory inventory = new Inventory();

        MainStage mainStage = new MainStage(player, inventory);
        mainStage.Start();
    }
}

생성된 인벤토리 객채는 메인스테이지로 넘겨준다

 

public class MainStage(Player player, Inventory inventory)
{
    bool game = true;
    
    public void Start()
    {
        Items items1 = new Items("초보자 나무 목검", "공격력", 5, "초보자도 쉽게 다룰수 있는 나무 목검", false);
        Items items2 = new Items("초보자 천갑옷", "방어력", 5, "초보자에게 처음 지급되는 천갑옷", false);
        inventory.AddItems(items1);
        inventory.AddItems(items2);
        MainMenu();
    }
    public void InventoryUI()
    {
        while (game)
        {
            Console.Clear();
            Console.WriteLine("인벤토리");
            Console.WriteLine("보유 중인 아이템을 관리할 수 있습니다.\n");
            Console.WriteLine("[아이템 목록]\n");
            inventory.ShowInven();
            Console.WriteLine("1. 장착 관리");
            Console.WriteLine("0. 나가기\n");
            Console.WriteLine("원하시는 행동을 입력해주세요.");
            Console.Write(">> ");

            string input = Console.ReadLine();
            if (input == "1") ItemManagerUI();
            else if (input == "0") MainMenu();
            else
            {
                Console.WriteLine("잘못된 입력입니다. 계속하려면 아무 키나 누르세요...");
                Console.ReadKey();
            }
        }

    }
}

 

 

간단하게 아이템을 생성해주고 생성된 아이템은 인벤토리 리스트에 저장을 해주면 인벤토리UI를 통해 출력이 된다.

 

 

이어서 바로 장착 관리를 만들어 보자

 

public void ItemEquipManger(Player player) // 아이템 장착 관리
{
    bool game = true;
    while (game)
    {
        Console.Clear();
        Console.WriteLine("인벤토리 - 장착 관리");
        Console.WriteLine("보유 중인 아이템을 관리할 수 있습니다.\n");
        Console.WriteLine("[아이템 목록]\n");
        for (int i = 0; i < ItemList.Count; i++)
        {
            Console.Write($"- {i + 1} ");
            if (ItemList[i].IsEquip == true) Console.Write("[E] ");
            Console.WriteLine($"{ItemList[i].Name} | {ItemList[i].ItemType} + {ItemList[i].State} | {ItemList[i].Description}");
        }
        

        Console.WriteLine("0. 나가기\n");
        Console.WriteLine("원하시는 행동을 입력해주세요.");
        Console.Write(">> ");

        int select = int.Parse(Console.ReadLine());

        if (select == 0) game = false;
        else if (select > 0 && select <= ItemList.Count && ItemList[select - 1] != null )
        {
            if (ItemList[select - 1].IsEquip == true)//장비 해제
            {
                ItemList[select - 1].IsEquip = false;
                if (ItemList[select - 1].ItemType == "공격력") player.EquipAttack -= ItemList[select - 1].State;
                else if (ItemList[select - 1].ItemType == "방어력") player.EquipDefense -= ItemList[select - 1].State;
            }
            else //장비 장착
            {
                ItemList[select - 1].IsEquip = true;
                if (ItemList[select - 1].ItemType == "공격력") player.EquipAttack += ItemList[select - 1].State;
                else if (ItemList[select - 1].ItemType == "방어력") player.EquipDefense += ItemList[select - 1].State;

                if (ItemList[select - 1].ItemType == "공격력")// 선택한 아이템이 공격력이면
                {
                    //공격력 아이템은 모두 false로 변경
                    for (int i = 0; i < ItemList.Count; i++)
                    {
                        if (ItemList[i].ItemType == "공격력" && ItemList[i].IsEquip == true)
                        {
                            ItemList[i].IsEquip = false;
                            player.EquipAttack -= ItemList[i].State;
                        }
                    }
                    ItemList[select - 1].IsEquip = true;
                    player.EquipAttack += ItemList[select - 1].State;
                }
                else if (ItemList[select - 1].ItemType == "방어력")// 선택한 아이템이 공격력이면
                {
                    //공격력 아이템은 모두 false로 변경
                    for (int i = 0; i < ItemList.Count; i++)
                    {
                        if (ItemList[i].ItemType == "방어력" && ItemList[i].IsEquip == true)
                        {
                            ItemList[i].IsEquip = false;
                            player.EquipDefense -= ItemList[i].State;
                        }
                            
                    }
                    ItemList[select - 1].IsEquip = true;
                    player.EquipDefense += ItemList[select - 1].State;
                }
            }
        }
        else 
        {
            Console.WriteLine("잘못된 입력입니다. 계속하려면 아무 키나 누르세요...");
            Console.ReadKey();
        }
    }
}

현재 if문으로 도배를 해놔서 가독성이 엄청 떨어지고 중복으로 또 장착했다가 해제하는데 이렇게 구현된 이유가

장착된 아이템과 같은 타입의 장비가 장착 되어있으면 장착된 아이템을 해제하고 장착을 하기위해 이렇게 구현이 되었다.

 

사실 저도 이게 맞는지 모르겠지만 엄청 가독성이 떨어지고 문제가 많다는 것은 알고 있지만 일단 완성은 했으니....

 이 짤처럼 어쨋든 작동하니까... 아무튼 작동은 하니까....

C# 기본 문법을 배운지 4일차 

아직까지는 기본 문법만 공부해서 그런지 아직까지는 큰 무리는 없는것 같다(강의 숙제가 힘든건 함정.....) 

 

그래서 이번까지 배운 문법을 활용해서 TEXT RPG를 만드는 것이 목표이다.

 

우선 interface를 활용하여 플레이어의 인터페이스를 구현하자

public interface ICharacter
{
    string Name { get; set; }
    string ClassName { get; }
    int Health { get; set; }
    int Attack { get; }
    int Defence { get; set; }
    bool IsDead { get; }
    void TakeDamage(int damage);
}

public class Player : ICharacter
{
    public string Name { get; set; }
    public string ClassName { get; set; }
    public int Health { get; set; }
    public int AttackPower { get; set; }
    public int Defence { get; set; }
    public bool IsDead => Health <= 0;
    public int Attack => new Random().Next(10, AttackPower); // 공격력은 랜덤

    public int Level = 1;
    public int Gold = 1500;

    public Player(string name, string Select)
    {
        Name = name;

        if (Select == "1" || Select == "전사")
        {
            ClassName = "전사";
            Health = 120;
            AttackPower = 20;
            Defence = 10;
        }
        else if (Select == "2" || Select == "도적")
        {
            ClassName = "도적";
            Health = 100;
            AttackPower = 30;
            Defence = 5;
        }
    }
}

여기서 ICharacter 인터페이스를 만든 이유는 지금 당장은 플레이어 뿐이지만 나중에 몬스터를 추가 했을때 동일한 구조를 따르도록 설계하기 위해서 인터페이스로 만들었다.

 

다음으로 플레이어 이름과 직업 클래스를 사용자로 부터 입력 받기위해

class Program
{
    static void Main(string[] args)
    {
        
        Console.WriteLine("무한의 던전에 오신 여러분 환영합니다.");
        Console.WriteLine("원하시는 이름을 설정 해주세요.");
        Console.Write("이름 : ");
        string name = Console.ReadLine();

        //저장 시스템 만들기

        //직업 선택창 만들기
        Console.Clear();
        Console.WriteLine("스파르타 던전에 오신 여러분 환영합니다.");
        Console.WriteLine("원하시는 직업을 골라주세요.");
        Console.WriteLine("1. 전사 ");
        Console.WriteLine("2. 도적 ");
        Console.Write(">> ");
        string select = Console.ReadLine();
        
        Player player = new Player(name, select);
        
        MainStage mainStage = new MainStage(player);
        mainStage.Start();
    }
}

플레이어 객체를 생성하고 생성된 플레이어 객체를 통해 플레이어의 이름과 직업 클래스를 저장한다.

데이터가 저장된 플레이어 객체를 MainStage 클래스에 전달을 해줌으로서 앞으로 게임 진행을 할때 플레이어 데이터를 더 효과적으로 관리가 가능하다.

 

이제  MainStage 클래스에는 게임에 대한 메인 메뉴를 만들 것이다.

기본적으로 상태창, 인벤토리, 상점을 구연을 해보자

public class MainStage(Player player)
{
    bool game = true;
    public void Start()
    {
        MainMenu();
    }
    public void MainMenu()
    {
        while (game)
        {
            Console.Clear();
            Console.WriteLine("에데온 마을에 오신 여러분 환영합니다.");
            Console.WriteLine("이곳에서 던전으로 들어가기전 활동을 할 수 있습니다.\n");
            Console.WriteLine("1. 상태 보기");
            Console.WriteLine("2. 인벤토리");
            Console.WriteLine("3. 상점\n");
            Console.WriteLine("0. 게임 종료\n");
            Console.WriteLine("원하시는 행동을 입력해주세요.");
            Console.Write(">> ");
            string choice = Console.ReadLine();

            switch (choice)
            {
                case "1":
                    StateUI();
                    break;
                case "2":
                    InventoryUI();
                    break;
                case "3":
                    //ShopUI();
                    break;
                case "0":
                    game = false;
                    return;
                default:
                    Console.WriteLine("잘못된 입력입니다. 계속하려면 아무 키나 누르세요...");
                    Console.ReadKey();
                    break;
            }
        }
    }
}

 

 

스위치 문을 통해 입력 받은 숫자에 따라 상태창, 인벤토리, 상점으로 넘어간다.

이제 메인에서 전달 받은 플레이어 정보를 상테창에 표시를 하면 간단하게 상태창을 만들 수 있다.

public void StateUI()
{
    while (game)
    {
        Console.Clear();
        Console.WriteLine("상태 보기");
        Console.WriteLine("캐릭터의 정보가 표시됩니다.\n");
        Console.WriteLine($"Lv. {player.Level}");
        Console.WriteLine($"{player.Name} ( {player.ClassName} )");
        Console.WriteLine($"공격력 : {player.AttackPower}");
        Console.WriteLine($"방어력 : {player.Defence}");
        Console.WriteLine($"체 력 : {player.Health}");
        Console.WriteLine($"Gold : {player.Gold} G");
        Console.WriteLine("\n0. 나가기\n");
        Console.WriteLine("원하시는 행동을 입력해주세요.");
        Console.Write(">> ");


        string input = Console.ReadLine();
        if (input == "0") MainMenu();
        else
        {
            Console.WriteLine("잘못된 입력입니다. 계속하려면 아무 키나 누르세요...");
            Console.ReadKey();
        }
    }

}

오늘은 TEXT RPG 게임의 기본틀과 상태창까지 만들어 보았다. 아직까지는 큰 어려움이 없었지만 이제 만들 인벤토리와 상점을 만들 생각을 하니 살짝 두려움이 몰려온다........

 

1번 문제 : 숫자 맞추기 게임 만들기

 

풀이 : Random()함수를 이용해 랜덤 값을 number에 저장을 하고 사용자가 입력한 숫자와 number가 서로 다르면 while문이 계속 작동을 할 수 있게 작성을 한다.

while문 안에서는 if문을 통해 number의 숫자보다 크거나 작으면 다른 숫자를 입력하라는 문구가 출력되고 number의 숫자를 맞추면 축하 문구와 함께 시도 횟수를 출력해준다.

int number = new Random().Next(1, 101);
int count = 0;
int inputnum = 0;

Console.WriteLine("1부터 100 사이의 숫자를 맞춰보세요");

while (inputnum != number)
{
    Console.Write("숫자를 입력하시오  : ");
    inputnum = int.Parse(Console.ReadLine());
    count++;

    if (inputnum < number)
    {
        Console.WriteLine("좀 더 큰 숫자를 입력하세요.");
    }
    else if (inputnum > number)
    {
        Console.WriteLine("좀 더 작은 숫자를 입력하세요.");
    }
    else
    {
        Console.WriteLine("축하합니다! 숫자를 맞추셨습니다.");
        Console.WriteLine($"시도 횟수 : {count}");
    }
}

2번 문제 : 틱택토 게임 만들기

 

풀이 : 입력된 숫자를 for문을 통해 배열에 저장된 값과 비교을 하여 입력된 숫자와 배열에 저장된 숫자가 같으면 표시하고 다르면 표시가 되지 않는다. 

승패는 누가 먼저 한줄을 채웠는지를 확인하기 위해 if문으로 조건을 세워 비교하여 승패를 정한다.

bool winer = false;
int player = 1;
string choice = "";
string[,] map = new string[3, 3]
{
    {"1", "2", "3" },
    {"4", "5", "6" },
    {"7", "8", "9" }
};

while (!winer)
{
    Console.Clear();
    Console.WriteLine("플레이어 1: X 와 플레이어 2: O");
    Console.WriteLine("\n");

    if (player%2 == 1)
    {
        Console.WriteLine("플레이어1 차례 입니다.\n");
        choice = "x";
    }
    else
    {
        Console.WriteLine("플레이어2 차례 입니다.\n");
        choice = "o";
    }
    Console.WriteLine("\n");

    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            Console.Write(map[i, j] + " ");
        }
        Console.WriteLine("\n"); 
    }
    binggo();

    Console.Write("1 ~ 9 중에서 숫자를 입력해 주세요 : ");
    string input = Console.ReadLine();

    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            if (input == map[i, j])
            {
                map[i, j] = choice;
                player++;
            }
        }
        Console.WriteLine();
    }
}

void binggo(){
    int i = 0;
    for (i = 0; i < 3; i++)
    {
        if (map[i, 0] == map[i, 1] && map[i, 1] == map[i, 2])
        {
            Console.WriteLine($"플레이어{(player%2)+1}님이 승리 했습니다.");
            winer = true;
            break;
        }
        else if (map[0, i] == map[1, i] && map[1, i] == map[2, i])
        {
            Console.WriteLine($"플레이어{(player % 2) + 1}님이 승리 했습니다.");
            winer = true;
            break;
        }
        else if (map[0, 0] == map[1, 1] && map[2, 2] == map[1, 1])
        {
            Console.WriteLine($"플레이어{(player % 2) + 1}님이 승리 했습니다.");
            winer = true;
            break;
        }
        else if (map[0, 2] == map[1, 1] && map[2, 0] == map[1, 1])
        {
            Console.WriteLine($"플레이어{(player % 2) + 1}님이 승리 했습니다.");
            winer = true;
            break;
        }
        else if (map[0, 0] != "1" && map[0, 1] != "2" && map[0, 2] != "3" &&
            map[1, 0] != "4" && map[1, 1] != "5" && map[1, 2] != "6" &&
            map[2, 2] != "7" && map[2, 2] != "8" && map[2, 2] != "9")
        {
            Console.WriteLine("무승부");
            break;
        }
    }
}

문제점 : 중복된 숫자를 입력했을때 다른 숫자를 입력하라는 문구가 표시가 되지 않고 게임이 끝나고 여러번 출력이 됨

 

해결점 : 문제점을 몇시간동안 해결하지 못해 염예찬튜터님께 조언을 받았다. 튜터님께서 for문 밖에서 입력된 숫자를 중복 확인을 해야 한다고 말씀을 하셔서 기존 for문을 통해 배열에 저장된 값을 하니씩 확인하던 방식에서 if문을 통해 입력된 숫자를 판단하여 배열의 위치값을 저장을해서 바로 배열의 저장값을 확인 하는 방식으로 바꾸었다.

 

변경된 코드

bool winer = false;
int player = 1;
int num;
int x = 0; // 배열 좌표 저장 값
int y = 0; // 배열 좌표 저장 값
string choice = "";
string[,] map = new string[3, 3]
{
    {"1", "2", "3" },
    {"4", "5", "6" },
    {"7", "8", "9" }
};

while (!winer)
{
    Console.Clear();
    Console.WriteLine("플레이어 1: X 와 플레이어 2: O");
    Console.WriteLine("\n");

    if (player%2 == 1)
    {
        Console.WriteLine("플레이어1 차례 입니다.\n");
        choice = "x";
    }
    else
    {
        Console.WriteLine("플레이어2 차례 입니다.\n");
        choice = "o";
    }
    Console.WriteLine("\n");

    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            Console.Write(map[i, j] + " ");
        }
        Console.WriteLine("\n"); 
    }
    binggo();

    Console.Write("1 ~ 9 중에서 숫자를 입력해 주세요 : ");
    string input = Console.ReadLine();
    bool res = int.TryParse(input, out num);

    if (res == true)
    {
        if (num >= 1 && num <= 3)
        {
            x = 0;
            y = num - 1;
        }
        else if (num >= 4 && num <= 6)
        {
            x = 1;
            y = num - 4;
        }
        else if (num >= 7 && num <= 9)
        {
            x = 2;
            y = num - 7;
        }

        if (map[x, y] != "x" && map[x, y] != "o")
        {
            map[x, y] = choice;
            player++;
        }
        else
        {
            Console.Write("이미 입력된 숫자입니다 다른 숫자를 골라주세요");
            Console.ReadLine();
        }
    }
    
}

void binggo(){
    int i = 0;
    for (i = 0; i < 3; i++)
    {
        if (map[i, 0] == map[i, 1] && map[i, 1] == map[i, 2])
        {
            Console.WriteLine($"플레이어{(player%2)+1}님이 승리 했습니다.");
            winer = true;
            break;
        }
        else if (map[0, i] == map[1, i] && map[1, i] == map[2, i])
        {
            Console.WriteLine($"플레이어{(player % 2) + 1}님이 승리 했습니다.");
            winer = true;
            break;
        }
        else if (map[0, 0] == map[1, 1] && map[2, 2] == map[1, 1])
        {
            Console.WriteLine($"플레이어{(player % 2) + 1}님이 승리 했습니다.");
            winer = true;
            break;
        }
        else if (map[0, 2] == map[1, 1] && map[2, 0] == map[1, 1])
        {
            Console.WriteLine($"플레이어{(player % 2) + 1}님이 승리 했습니다.");
            winer = true;
            break;
        }
        else if (map[0, 0] != "1" && map[0, 1] != "2" && map[0, 2] != "3" &&
            map[1, 0] != "4" && map[1, 1] != "5" && map[1, 2] != "6" &&
            map[2, 2] != "7" && map[2, 2] != "8" && map[2, 2] != "9")
        {
            Console.WriteLine("무승부");
            break;
        }
    }
}

 

 

문제 1 : 사용자로부터 이름과 나이를 입력 받고 출력하는 코드를 작성하세요

 

풀이 : C#에서 사용자의 입력을 받는 ReaLine()을 사용하여 사용자 입력을 받고 input.Split()을 사용하여 띄여쓰기를 확인해서 문자열과 정수를 따로 저장해서 출력해준다.

Console.Write("이름과 나이를 입력하시오 : ");
string input = Console.ReadLine();

string[] data = input.Split(' ');
string name = data[0];
int age = int.Parse(data[1]);

Console.WriteLine($"이름 : {name} 나이 : {age}");

문제 2 : 사용자로부터 두 수를 입력 받고 간단한 사칙연산 결과를 출력하세요

 

풀이 : 문제 1번처럼 사용자로 부터 숫자를 입력받아 띄어쓰기를 판단하여 두 숫자를 저장하고 산술 연산자를 이용하여 정수들을 계산해 출력해준다.

Console.WriteLine("숫자 두개를 입력하시오");
string input = Console.ReadLine();

string[] number = input.Split(' ');
int num1 = int.Parse(number[0]);
int num2 = int.Parse(number[1]);

int add = num1 + num2;
int sub = num1 - num2;
int mul = num1 * num2;
int div = num1 / num2;

Console.WriteLine($"덧셈 : {add}\n뺄셈 : {sub}\n곱셈 : {mul}\n나눗셈 : {div}");

문제 3 : 섭씨온도를 화씨온도로 변환하는 프로그램을 만들어 출력하세

 

풀이 : 섭씨 온도를 입력 받으면 입력받은 숫자를 실수형을 저장하는 float에 저장을 하여 변환 계산식을 거쳐 출력해준다.

Console.Write("섭씨 온도를 입력하시오 : ");
string num1 = Console.ReadLine();
float Celsius = float.Parse(num1);

float toFahrenheit = Celsius * 1.8f + 32;

Console.WriteLine($"섭씨 : {Celsius} -> 화씨 : {toFahrenheit}");

문제 4 : 사용자의 신장과 체중을 입력받아 BMI지수를 계산하는 코드를 작성하세요.

 

풀이 : 문제 3번과 비슷한 문제로 사용자로 부터 신장과 체중을 입력받아 입력받은 숫자를 실수형인 float에 저장을 하고 BMI 계산식을 거처 BMI 지수를 출력해준다.

Console.Write("신장을 입력하시오 : ");
string num1 = Console.ReadLine();
Console.Write("체중을 입력하시오 : ");
string num2 = Console.ReadLine();

float height = int.Parse(num1);
float weight = int.Parse(num2);

height *= 0.01f;
float bmi = weight / (height * height);

Console.WriteLine(bmi);

오늘 드디어 사전캠프때 부터 기다리던 C# 기초 문법에 대해 강의가 올라왔다. 전에 대학교에서 C++이랑 Python을 배울때도 그랬지만 어떤 코드를 배우든 변수와 자료형은 컴퓨터 언어의 처음이자 컴퓨터와 대화의 첫 발자국입니다.

변수와 자료형을 한글로 비유하면 자음과 모음같은 느낌이다. 이 두가지를 알아야 컴퓨터와 대화가 가능기 때문입니다.

물론 자음과 모음을 안다고 바로 대화를 할 순 없겠지만 이 두가지 조차 모르면 대화를 시도 조차 할 수 없기에 변수와 자료형은 컴퓨터 언어의 시작이자 자음과 모음 같은 것 입니다.

 


자료형 (Data Type)

자료형(Data Type)은 데이터를 식별하는 분류로 데이터의 종류와 크기를 정의합니다.

 

C#에서 가장 많이 사용되는 기본 자료형은 다음과 같습니다.

자료형 .NET 데이터 타입 크기(byte) 범위
byte System.Byte 1 0 ~ 255
int System.Int32 4 -2,147,483,648 ~ 2,147,483,647
long System.Int64 8 -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807
float System.Single 4 ±1.5 × 10^-45 ~ ±3.4 × 10^38
double System.Double 8 ±5.0 × 10^-324 ~ ±1.7 × 10^308
char System.Char 2 유니코드 문자
string System.String   유니코드 문자열
bool System.Boolean 1 true 또는 false

-이러한 기본 자료형을 사용하여 변수를 선언하고 값을 저장할 수 있습니다.

-각각의 자료형은 메모리의 크기와 표현 범위가 다르기 때문에 byte를 이용하여 숫자를 저장한다고 했을때 255번째를 넘는 숫자를 저장이 불가능 함으로 내가 사용할 변수를 세분화하여 필요한 만큼의 메모리의 크기와 표현 범위를 잘 생각해서 자료형 선택을 해야 합니다.

 


변수 (Variable)

변수(Variable)는 데이터(숫자, 문자)를 저장하고 필요에 따라 수정하고 사용하기 위해 할당 받은 공간입니다.


변수명

  1. 키워드 (Keywords)
    • 컴퓨터 언어에는 이미 예약된 단어들이 있기 때문에 변수,메소드, 클래스 등의 이름으로 사용할 수 없습니다.
    • C#의 키워드로는 아래 링크를 참조 하시면 됩니다.
https://learn.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/
  1. 식별자 (Identifiers)
    • 식별자란 변수, 메서드, 클래스, 인터페이스 등에 사용되는 이름을 말합니다. 이 이름은 키워드와 동일하게 사용할 수 없습니다.
    • 식별자를 사용할 때 주의 사항이 있습니다.
      • 첫 문자는 알파벳, 언더바( _ ) 가능
      • 두번째 문자부터는 숫자 가능
      • 대소문자를 구분
      • 키워드와 같은 이름으로 사용 불가
// 사용 가능
int UserCode;
string UserName;
float _itemNum;

// 비추천 (중요 의미 있는 변수명 짓기)
int x1;  // 변수명이 의미를 알기 어려움
string a; // 변수명이 명확하지 않음

// 사용 불가
int 1stNumber;  // 변수명은 숫자로 시작할 수 없음
string my-name; // 변수명에 하이픈(-)을 사용할 수 없음
float total$;   // 변수명에 특수문자($)를 사용할 수 없음
bool null;      // 변수명에 키워드를 사용할 수 없음

형변환 (Type Casting)

형변환이란 변수의 자료형을 즉 데이터 타입을 다른 데이터 타입으로 변환하는 것을 말합니다.

 

  • 명시적(explicit) 형변환
    • 명시적 형변환은 변환할 데이터의 앞에 변환할 자료형을 괄호안에 넣어 형변환을 진행합니다.
int num1 = 10
long num2 = (long)num1;   //int를 long으로 명시적 형번환
                          // [(형변환할 타입)][형변환할 값]

 

  • 암시적 (implicit) 형변
    • 암시적 형변환은 여러가지의 경우가 있습니다
//작은 데이터 타입에서 더 큰 데이터 타입으로 대입하는경우
//byte, short, char 등 작은 데이터 타입에서 
//int, long, float 등 더 큰 데이터 타입으로 대입할 때 암시적 형변환이 발생합니다.
byte num1 = 10;
int num2 = num1;  // byte형에서 int형으로 암시적 형변환

 

//리터럴 값이 대입되는 경우
//C# 컴파일러는 리터럴 값의 데이터 타입을 판별하여 변수에 암시적으로 형변환 합니다
float result = 1;  // 1은 int형 리터럴 값이지만 float형으로 암시적 형변환

 

//정수형과 부동소수점형 간의 연산을 수행하는 경우
//정수형과 부동소수점형의 연산 결과는 부동소수점형으로 변환됩니다.
int num1 = 10;
float num2 = 3.14f;
float result = num1 + num2;  // int형과 float형의 덧셈에서 float형으로 암시적 형변환

 

이렇게 암시적 형변환은 프로그래머가 직접 형변환 코드를 작성하지 않아도 자동으로 형변환을 하므로 코드를 간결하게 작성할 수 있습니다.

하지만, 암시적 형변환이 발생하는 경우 데이터 타입을 신중하게 고려하여 작성해야 합니다. 만약 고려하지 않고 작성을 하게 된다면 오류가 발생함으로 잘 고려하여 작성해야 합니다.

'내일배움캠프 > C# 이론' 카테고리의 다른 글

싱글톤 패턴 (Singleton patton)  (0) 2025.02.11

유니티에서 Silder를 이용해서 오디오 볼륨을 조절하는 기능을 구현해 보았다.

 


우선 GameObject를 만들어 AudioManager라고 이름을 지어주고 AudioManger.cs 스크립트를 만들어 붙여준다.

 

 

AudioManager.cs 스크립트를 열어 코드를 작성해준다.

using UnityEngine;
using UnityEngine.UI;

public class AudioManager : MonoBehaviour
{
    public AudioSource bgmSource;
    public Slider bgmVolumeSlider;


    public AudioClip clip;

    private void Awake()
    {
        bgmSource = GetComponent<AudioSource>();
    }

    void Start()
    {
        bgmVolumeSlider.value = bgmSource.volume;
        bgmSource.clip = this.clip;
        bgmSource.loop = true;
        bgmSource.Play();
    }

    void Update()
    {
        bgmSource.volume = bgmVolumeSlider.value;
    }
}

 

코드 작성을 완료했으면 슬라이더 UI를 추가해 적당한 크기로 설정후 

AudioManager에 Audio Source 컴포넌트, bgm clip, 슬라이더 UI를 붙여준다

 

 

이제 시작버튼을 눌러 잘 작동하는지 확인을 해주면 완성

어제 딕셔너리(Dictionary)에 대해 배웠는데 리스트(List), 배열(Array)와 차이가 궁금하여 구글링도 하고 ChatGPT에 물어보기도하고 김재경 튜터님에게도 물어 보았다.

 

김재경 튜터님의 말씀으로 딕셔너리를 이해를 하려면 리스트와 배열을 알아야한다. 리스트와 배열에서 필요한 A데이터를 찾는다면 리스트와 배열 내부에 있는 데이터들을 순차적으로 비교를 하면서 A데이터를 찾기 때문에 시간이 오래 걸릴 수 있다. 하지만 딕셔너리는 Key값과 Value값이 1대1 대응하기 때문에 Key 값으로 바로바로 필요한 데이터를 빠르게 찾을 수 있다고 하셨다. 

 

 


리스트 (List <T>)

  • 특징
    • 저장 공간 크기가 가변적
    • 내부적으로 배열을 사용하지만, 데이터가 추가되면 자동으로 크기를 늘려 관리
    • 순서를 유지하며 데이터에 접근 가능
  • 장점
    • 저장 공간 크기 가변성 : 데이터 추가/삭제가 자유롭고, 자동으로 크기 조정.
    • 순서 유지 : 데이터 삽입 순서를 보존
    • 유연성 : 검색, 정렬 등 다양한 메서드 제공
  • 단점
    • 중간 삽입/삭제 성능 저하 : 요소를 이동 해야 하므로 O(n)의 시간 소모
    • 탐색 기능 저하 : 특정 값을 찾으려면 순차적으로 탐색 O(n)의 시간 소모
    • 메모리 재할당 비용 : 내부 배열의 크기를 늘릴 때, 새 배열로 복사
  • 사용 예시
    • 대기열 : 사이트에 접속을 위해 대기열을 기다리는 상황 사람들은 순서대로 대기하며, 새로 온 사람은 뒤에 서고 기존 사람은 앞에서 차례대로 접속
    • 장바구니 : 온라인 쇼핑몰에서 장바구니에 상품을 추가/제거 하는 경우 물건

배열 (Array)

  • 특징
    • 고정된 저장 공간 크기 구조로, 메모리를 효율적으로 사용
    • 요소를 순서대로 저장하며, 빠른 인덱스 접근을 제공
  • 장점
    • 빠른 인덱스 접근 : 특정 위치의 데이터를 즉시 읽고 쓸 수 있음 O(1)의 시간 소모
    • 메모리 효율성 : 고정된 크기로 생성되어 추가 메모리를 사용하지 않음
    • 성능 우수 : 단순 구조로 반복 작업이나 연산에서 빠름.
  • 단점
    • 저장 공간 크기 고정 : 생성 시 크기를 지정해야 하며, 이후 변경 불가
    • 데이터 추가/삭제 불편 : 크기를 변경하려면 새로운 배열을 생성하고 데이터를 복사해야 함
    • 택색 성능 저하 : 특정 값을 찾을려면 순차 탐색이 필요 O(n)의 시간 소모
  • 사용 예시
    • 좌석 배치 : 대형 공연장이나 영화과에서 좌석 배치는 고정된 배열로 설정. 한 번 설정된 좌석 수는 변경 되지 않으며, 각 좌석은 고유한 번호로 접근 가능
    • 날씨 데이터 : 7일간의 날씨 데이터를 저장할 때. 한 주 동안의 날씨 예보를 배열로 저장하고, 날짜별 날씨 데이터를 빠르게 찾음

 


딕셔너리 (Dictionary <Tkey, Tvalue>)

  • 특징
    • 키-값 쌍으로 데이터를 저장하며, 키를 통해 값을 빠르게 검색
    • 키는 고유해야 하며 중복 키를 허용하지 않음
  • 장점
    • 빠른 검색 속도 : 키를 사용해 평균 O(1)의 시간 소모
    • 키 기반 데이터 관리 : 데이터의 고유성을 보장하며 관리 용이
    • 유연성 : 다양한 키와 값 타입 지원
  • 단점
    • 메모리 사용량 증가 : 해시 테이블 구조로 인해 추가 메모리 소모
    • 값 기반 검색 비효율 : 값을 통해 키를 찾으려면 순차 탐색 필요 O(n)의 시간 소모
    • 순서 보장 없음 : 기본적으로 삽입 순서가 유지되지 않음(C# 7.3 이하 버)
  • 사용 예시
    • 전화번호부 : 사람들의 이름(키)과 전화번호(값)을 매칭하여 빠르게 찾음. 이름을 검색하면 해당 전화번호가 즉시 나옴
    • 도서관 책 관리 시스템 : 책의 ISBN(국제 표준 도서 번호)번호를 키로 사용하고 책의 제목, 저자, 출판사 등의 정보를 값으로 저장하여 빠르게 책을 검색 가능

비교

데이터 구조 특징 장점 단점
리스트 가변적인 크기, 순서 유지 -데이터 추가/삭제 용이
-데이터 삽입 순서 보존
-유연한 메서드 제공
-탐색/삽입 시 성능 저하
-메모리 재할당 발생 가능
배열 고정된 크기, 빠른 인덱스 접근 -고정된 크기로 메모리 효율적
-빠른 인덱스 접근
-단순 구조로 반복 작업이나 연산 빠름
-크기 변경 불가
-데이터 추가/삭제 불편
-탐색 성능 저하
딕셔너리 키-값 쌍으로 저장, 빠른 검색 -데이터 고유성 보장
-관계 표현 용이
-키 기반으로 빠른 검색
-해시 테이블 구조로 인해 추가 메모리 소모
-값 기반 검색 비효율

 

 

사용 예시 및 추천 사용처

데이터 구조 사용 예시 추천 사용처
리스트 온라인 쇼핑몰 장바구니, 대기열 -동적으로 데이터 추가/삭제가 필요한 경우
-순서가 중요한 경우
배열 좌석 배치, 날씨 데이터 -고정 크기 데이터
-빠른 인덱스 기반 접근이 필요한 경우
딕셔너리 전화번호부, 도서관 책 관리 시스템 -고유 식별자를 통해 빠르게 데이터를 검색해야 하는 경우
-데이터 관계 관리

 

+ Recent posts