🔴 오늘의 내용
- 일정과 바뀐 내용
- C# 4주차
- 튜터 님의 강의
- 개인 과제
- 후기 및 내일 일정
1. 일정과 바뀐 내용
어제 계획한 바와 동일하다. 세부한 계획을 짜지는 않았기에 C# 강좌를 보겠다는 계획은 진행하였고 추가적으로 개인 과제를 작성하였다.
2. C# 4주차(5주차는 보긴 했는데..)
오늘 들은 교육의 양은 적지만, 이해하기가 난해하였고 오후부터는 개인 과제를 하는데 어려움을 겪어 진도를 많이 못 나간 느낌이다. 5주차도 슬쩍보니 이해하는데 한 세월 걸릴 거 같아 오늘 주요 내용부터 정리하며 이해해보겠다.
- 인터페이스
- 열거형 Enum
- 예외 처리
- 값형과 참조형
- 박싱과 언박싱
- 델리게이트
- Func, Action
- 람다 및 LINQ
- Nullable
- 문자열 빌더
솔직히 말하자면 오늘 배운 내용을 죄다 적어놓았다. 어제 1~3주차를 들으며 이정도로 어렵다고 생각한 것은 없었는데 이 내용들은 이해하기 어렵다보다는 써보지를 못해서 어떻게 쓰는지 감이 잡히지 않기에 내용을 정리하며 복습해보도록 하자.
2-1. 인터페이스
이름 그대로의 클래스입니다. 작성할 클래스를 미리 선언하는 느낌의 클래스로 지난 내용의 abstract 추상 클래스와 비슷합니다.
interface 를 상속받으면 반드시 그 내부에 있는 코드를 작성해야합니다. 그냥 상속과 다를 거 없지만, 특징으로는 반드시 작성해야한다는 점이 존재하여, 혹여나 필요한데 코드를 빼먹을 일을 방지할 거 같습니다.
public interface IMovable
{
void Move(int x, int y); // 이동 메서드 선언
}
public class Player : IMovable
{
public void Move(int x, int y)
{
// 플레이어의 이동 구현
}
}
public class Enemy : IMovable
{
public void Move(int x, int y)
{
// 적의 이동 구현
}
}
2-2. 열거형 Enum
생각보다 다루는 건 쉬워보이는데 대체 어디에 쓰는지 모르겠어서 한참을 생각한 함수입니다.
단순히 현재 이해한 기능으로는 switch에 들어갈 내용을 세밀하게 적을 수 있어 가독성을 높인다인 거 같습니다.
enum DaysOfWeek
{
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
}
class Program
{
static void Main(string[] args)
{
DaysOfWeek day = DaysOfWeek.Monday;
Console.WriteLine("Today is " + day);
}
}
// 게임 상태
enum GameState
{
MainMenu,
Playing,
Paused,
GameOver
}
// 방향
enum Direction
{
Up,
Down,
Left,
Right
}
// 아이템 등급
enum ItemRarity
{
Common,
Uncommon,
Rare,
Epic
}
2-3. 예외 처리
코드 진행 중에 발생하는 문제가 생기면 이에 반응하여 작동하는 기능입니다.
예시에서는 쓰이지 않았지만, 예를 들면 int 값에 string 값이 들어가면 에러가 나면서 강제 종료가 되는데 이럴 경우 예외처리를 진행하여 int 값을 다시 받도록 하면 문제를 줄일 수도 있지 않을까요?
try
{
int result = 10 / 0; // ArithmeticException 발생
Console.WriteLine("결과: " + result);
}
catch (DivideByZeroException ex)
{
Console.WriteLine("0으로 나눌 수 없습니다.");
}
catch (Exception ex)
{
Console.WriteLine("예외가 발생했습니다: " + ex.Message);
}
finally
{
Console.WriteLine("finally 블록이 실행되었습니다.");
}
2-4. 값형과 참조형
값형은 변수를 값에 직접 저장하는 것으로 int, float, double, bool 등이 이에 해당합니다.
참조형은 변수가 다른 데이터에 대한 참조(메모리 주소)를 저장하는 것으로 메모리 주소된 값이 바뀔 경우, 참조형 변수도 값이 바뀌게 됩니다. 이를 방지하는 방법으로는 new int 등의 new 키워드로 새롭게 생성하면 값형으로 생성이 가능합니다.
2-5. 박싱과 언박싱
값형을 참조형으로 변환하는 것을 박싱, 참조형을 값형으로 바꾸는 것을 언박싱이라고 합니다.
아래 예제와 같이 int, float 등의 기본 데이터 타입을 object를 통해 박싱할 수 있고
object의 값을 int, float 등의 기본 데이터 타입으로 바꾸는 것으로 언박싱 할 수 있습니다.
using System;
class Program
{
static void Main()
{
// 값형
int x = 10;
int y = x;
y = 20;
Console.WriteLine("x: " + x); // 출력 결과: 10
Console.WriteLine("y: " + y); // 출력 결과: 20
// 참조형
int[] arr1 = new int[] { 1, 2, 3 };
int[] arr2 = arr1;
arr2[0] = 4;
Console.WriteLine("arr1[0]: " + arr1[0]); // 출력 결과: 4
Console.WriteLine("arr2[0]: " + arr2[0]); // 출력 결과: 4
// 박싱과 언박싱
int num1 = 10;
object obj = num1; // 박싱
int num2 = (int)obj; // 언박싱
Console.WriteLine("num1: " + num1); // 출력 결과: 10
Console.WriteLine("num2: " + num2); // 출력 결과: 10
}
}
2-6 델리게이트
델리게이트는 메서드(함수)를 참조하는 타입으로 델리게이트를 이용하면 메서드를 매개변수로 전달하거나 변수에 할당할 수 있습니다. 라고 하는데 아마 내일 구현하기 위해 시도해볼 거 같습니다. 클래스 내부에 델리게이트 이벤트를 하나 만들어서 다른 데이터의 값을 불러오고 대입하는 것이 가능한 거 같습니다.
// 델리게이트 선언
public delegate void EnemyAttackHandler(float damage);
// 적 클래스
public class Enemy
{
// 공격 이벤트
public event EnemyAttackHandler OnAttack;
// 적의 공격 메서드
public void Attack(float damage)
{
// 이벤트 호출
OnAttack?.Invoke(damage);
// null 조건부 연산자
// null 참조가 아닌 경우에만 멤버에 접근하거나 메서드를 호출
}
}
// 플레이어 클래스
public class Player
{
// 플레이어가 받은 데미지 처리 메서드
public void HandleDamage(float damage)
{
// 플레이어의 체력 감소 등의 처리 로직
Console.WriteLine("플레이어가 {0}의 데미지를 입었습니다.", damage);
}
}
// 게임 실행
static void Main()
{
// 적 객체 생성
Enemy enemy = new Enemy();
// 플레이어 객체 생성
Player player = new Player();
// 플레이어의 데미지 처리 메서드를 적의 공격 이벤트에 추가
enemy.OnAttack += player.HandleDamage;
// 적의 공격
enemy.Attack(10.0f);
}
2.7 Func, Action
Func과 Action은 델리게이트를 대체하는 미리 정의된 제네릭 형식입니다. Func는 값을 반환하는 메서드를 나타내는 델리게이트입니다. 마지막 제네릭 형식 매개변수는 반환 타입을 나타냅니다. 예를 들어, Func<int, string>는 int를 입력으로 받아 string을 반환하는 메서드를 나타냅니다. Action은 값을 반환하지 않는 메서드를 나타내는 델리게이트입니다. Action은 매개변수를 받아들이지만, 반환 타입이 없습니다. 예를 들어, Action<int, string>은 int와 string을 입력으로 받고, 아무런 값을 반환하지 않는 메서드를 나타냅니다. 쉽게 정리하면 Func는 return 값이 있고 Action은 return 값이 없다고 기억하면 쉬운 거 같습니다. 단 델리게이트의 특성이 있기에 Func와 Action을 활용하면 델리게이트를 보다 쉽게 사용할 수 있을 것입니다.
// Func를 사용하여 두 개의 정수를 더하는 메서드
int Add(int x, int y)
{
return x + y;
}
// Func를 이용한 메서드 호출
Func<int, int, int> addFunc = Add;
int result = addFunc(3, 5);
Console.WriteLine("결과: " + result);
// Action을 사용하여 문자열을 출력하는 메서드
void PrintMessage(string message)
{
Console.WriteLine(message);
}
// Action을 이용한 메서드 호출
Action<string> printAction = PrintMessage;
printAction("Hello, World!");
아래 구조에서는 charcer.Health = 0으로 Health의 프로퍼티 Get에서 저장된 값이 있는지 확인하고 Invoke로 저장된 함수를 발생시키며 캐릭터의 죽음을 알립니다.
// 게임 캐릭터 클래스
class GameCharacter
{
private Action<float> healthChangedCallback;
private float health;
public float Health
{
get { return health; }
set
{
health = value;
healthChangedCallback?.Invoke(health);
}
}
public void SetHealthChangedCallback(Action<float> callback)
{
healthChangedCallback = callback;
}
}
// 게임 캐릭터 생성 및 상태 변경 감지
GameCharacter character = new GameCharacter();
character.SetHealthChangedCallback(health =>
{
if (health <= 0)
{
Console.WriteLine("캐릭터 사망!");
}
});
// 캐릭터의 체력 변경
character.Health = 0;
2.8 람다 및 LINQ
람다란 익명의 메서드를 만드는 방법입니다. 약간 매개변수를 넣어서 활용하는 코드 같기도 합니다.
Calculate calc = (x, y) =>
{
return x + y;
};
Calculate calc = (x, y) => x + y;
아래와 같이 델리게이트를 사용할 수 있다고 하는데 람다식이 없어도 구현이 되지 않을까 싶습니다.
// 델리게이트 선언
public delegate void GameEvent();
// 이벤트 매니저 클래스
public class EventManager
{
// 게임 시작 이벤트
public event GameEvent OnGameStart;
// 게임 종료 이벤트
public event GameEvent OnGameEnd;
// 게임 실행
public void RunGame()
{
// 게임 시작 이벤트 호출
OnGameStart?.Invoke();
// 게임 실행 로직
// 게임 종료 이벤트 호출
OnGameEnd?.Invoke();
}
}
// 게임 메시지 클래스
public class GameMessage
{
public void ShowMessage(string message)
{
Console.WriteLine(message);
}
}
// 게임 실행
static void Main()
{
// 이벤트 매니저 객체 생성
EventManager eventManager = new EventManager();
// 게임 메시지 객체 생성
GameMessage gameMessage = new GameMessage();
// 게임 시작 이벤트에 람다 식으로 메시지 출력 동작 등록
eventManager.OnGameStart += () => gameMessage.ShowMessage("게임이 시작됩니다.");
// 게임 종료 이벤트에 람다 식으로 메시지 출력 동작 등록
eventManager.OnGameEnd += () => gameMessage.ShowMessage("게임이 종료됩니다.");
// 게임 실행
eventManager.RunGame();
}
LinQ는 아래와 같은 구조로 데이터를 쿼리하고 조작하는데 사용한다고 합니다.
var result = from 변수 in 데이터소스
[where 조건식]
[orderby 정렬식 [, 정렬식...]]
[select 식];
- var 키워드는 결과 값의 자료형을 자동으로 추론합니다.
- from 절에서는 데이터 소스를 지정합니다.
- where 절은 선택적으로 사용하며, 조건식을 지정하여 데이터를 필터링합니다.
- orderby 절은 선택적으로 사용하며, 정렬 방식을 지정합니다.
- select 절은 선택적으로 사용하며, 조회할 데이터를 지정합니다.
// 데이터 소스 정의 (컬렉션)
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
// 쿼리 작성 (선언적인 구문)
var evenNumbers = from num in numbers
where num % 2 == 0
select num;
// 쿼리 실행 및 결과 처리
foreach (var num in evenNumbers)
{
Console.WriteLine(num);
}
2.9 Nullable
C#에서 null 값을 가질 수 있는 값형에 대한 특별한 형식을 말합니다.
int?, string? 과 같이 자료형에다가 ?를 붙이면 Null값을 자료형에 넣어주거나 .HasValue를 붙여 함수의 값이 Null인지 확인이 가능합니다.
2.10 문자열 빌더
문자열 빌더(StringBuilder)는 Append(), Insert(), Replace(), Remove() 등 다양한 메서드를 제공하여 문자열에 대한 추가, 삽입, 치환, 삭제 작업을 수행할 수 있습니다.
StringBuilder sb = new StringBuilder();
// 문자열 추가
sb.Append("Hello");
sb.Append(" ");
sb.Append("World");
// 문자열 삽입
sb.Insert(5, ", ");
// 문자열 치환
sb.Replace("World", "C#");
// 문자열 삭제
sb.Remove(5, 2);
// 완성된 문자열 출력
string result = sb.ToString();
Console.WriteLine(result);
3. 튜터 님의 강의
오늘의 강의 내용은 변수와 자료형. 간단히 요약 정리만 해보겠습니다.
- 개발을 시작할 때, 변수와 클래스 생각방법
- 변수 선언 구조
- 멤버 변수, 지역 변수, 전역 변수
3-1. 개발을 시작할 때, 변수와 클래스 생각방법
💡 개발을 할 때 클래스는 뭐고, 변수는 뭘까?
일단 간단하게 말하면 필요할 때 쓰면서 공부하면 쉽다. 예시를 들어보자.
RPG 게임을 만든다고 한다면, 무엇이 필요할까? = 캐릭터? 아이템? 무기?
캐릭터는 무슨 값을 지니고 있을까? = 체력? 공격력?
class Player
{
private int level = 1;
private string PlayerName = "플레이어";
private int exp = 100;
private float power = 10.5f;
}
캐릭터, 아이템, 무기의 공통점은 무엇일까. 그것은 바로 공통된 데이터가 필요한 다른 값들이라는 것이다. 동물에 비유하자면, 같은 종이지만 다른 능력치를 가진 동물들이 있다. 클래스는 이런 비슷한 이들을 하나의 클래스라는 것으로 묶은 것이다.
변수는? 그런 클래스에 들어가는 구분할 것들이다. 동물로 계속 예로 들자면, 동물의 털 색, 동물의 눈동자 모양, 동물의 서식지 등이 있을 것이다.
3-2. 변수 선언 구조
변수는 이와 같이 생성된다. = 접근제한 지정자 / 자료형 / 식별자
접근제한 지정자로는 public, protected, private가 있다. public > protected > private 순으로 접근이 용이하다.
자료형은 워낙 많기에 주로 쓰는 4가지로 분류하자면, 정수형, 실수형, 문자형, 논리형이 존재한다.
식별자는 변수의 이름을 나타내는 것으로 다루는 내용을 알기 쉽게 적는 것이 가독성에 좋다. ex) player
3-3 멤버 변수, 지역 변수, 정적 변수
멤버 변수 | 지역 변수 | 전역 변수 | |
생성 방법 | 클래스 내부에 생성 | 함수 내부에 생성 | static으로 생성 |
접근 제한 | 클래스 내부에서 | 함수 내부에서 | 전역에서(어디서든) |
관리 방법 | 가비지 컬렉션 자동관리 | 메모리 할당, 삭제 | 프로그램 시작 후 계속 존재 |
저장 위치 | 힙 | 스택 | 데이터 |
class Example
{
public static int staticInt = 10; //정적 변수
int Member; //멤버 변수
void Test()
{
int local; //지역 변수
}
}
4. 개인 과제
솔직히 글을 쓰는 지금도 오늘 하루종일 뭐한건가 싶고 더 빨리 더 잘할 수 있지 않을까 후회는 되지만,
오늘 버전 별로 나누며 개인 과제를 작성한 내용을 아래에 첨부했습니다.
- ver1. 아는 내용 활용하기
- ver2. 모르는 내용 활용해보기
- ver2_branch 모르는 내용과 아는 내용 섞기
4-1. ver1. 아는 내용 활용하기
가장 먼저 필요한 기능들을 static void로 선언하여 파트별로 나누어 작성하였습니다.
아래에 내용은 그 중 Village와 관련된 파트의 내용입니다.
static void Village() // 겹치는 구문이 있는데 클래스로 상속 받으면 처리하기 편할듯
{
Console.WriteLine("스파르타 마을에 오신 여러분 환영합니다.");
Console.WriteLine("이곳에서 던전으로 들어가기전 활동을 할수 있습니다.");
Console.Write("\n");
//Enum으로 나중에 변환 가능할 듯. villageList
Console.WriteLine("1. 상태 보기");
Console.WriteLine("2. 인벤토리");
Console.WriteLine("3. 상점");
Console.Write("\n");
Console.WriteLine("원하시는 행동을 입력해주세요.");
while (true)
{
Console.Write(">> ");
int check = int.Parse(Console.ReadLine());
switch (check)
{
case 1:
Console.WriteLine("1. 상태보기를 선택하셨습니다.");
Console.Write("\n");
Status();
break;
case 2:
Console.WriteLine("2. 인벤토리를 선택하셨습니다.");
Console.Write("\n");
Inventory();
break;
case 3:
Console.WriteLine("3. 상점을 선택하셨습니다.");
Console.Write("\n");
Shop();
break;
default:
Console.WriteLine("값을 다시 입력해주십시오");
Console.Write("\n");
continue;
}
return;
}
}
4-1. ver2. 모르는 내용 활용하기
ver1. 버전에서는 Item을 배열로 정리하기 위해 몇 가지 동작을 구현하지 않았었습니다.
ver2. 버전에서 enum 값과 자동 프로퍼티, 생성자, 오버라이딩 등을 활용하여 코드를 작성해보았습니다.
enum StatType
{
HP = 1, //체력
ATK, //공격력
DEF, //방어력
SPD //속도
}
//아이템 클래스
public class Item
{
public string itemName { get; private set; }
public int itemStatType { get; private set; }
public int stat { get; private set; }
public string itemHistory { get; private set; }
public bool isEquip = false;
//Item의 값을 참조가 아닌 값형으로 저장하기 위해서는
//new Item으로 재선언할 필요가 있는데 데이터를 쉽게 저장하기 위해 선언 함수를 만들어주었다.
public Item(Item item)
{
itemName = item.itemName;
itemStatType = item.itemStatType;
stat = item.stat;
itemHistory = item.itemHistory;
}
public Item(string newItemName, int newItemStatType, int newStat, string newItemHistory)
{
itemName = newItemName;
itemStatType = newItemStatType;
stat = newStat;
itemHistory = newItemHistory;
}
public void Answer()
{
Console.WriteLine($"{itemName}");
}
}
4-2. ver2_branch 모르는 내용과 아는 내용 섞기
ver1. 버전에서 구현하지 않은 것과 추가로 수정한 것을 ver2에서 구현했으니 그것을 합쳐보았습니다.
문제 : 기존에는 static void로 선언되어 외부 함수들이 Main 내부 변수를 사용할 수 없었습니다.
해결 방법 : static void로 선언되었던 클래스는 void 클래스로 Main 클래스 내부에 생성함으로써 Main 내부에 정의된 변수들을 문제없이 사용할 수 있었습니다.
void Village()
{
Console.WriteLine("[[스파르타 마을]]에 오신 여러분 환영합니다.");
Console.WriteLine("이곳에서 던전으로 들어가기전 활동을 할수 있습니다.");
Console.Write("\n");
//Enum으로 나중에 변환 가능할 듯. villageList
Console.WriteLine("1. [상태 보기]");
Console.WriteLine("2. [인벤토리]");
Console.WriteLine("3. [상점]");
Console.Write("\n");
Console.WriteLine("원하시는 행동을 입력해주세요.");
int check = 0;
while (true)
{
Console.Write(">> ");
check = int.Parse(Console.ReadLine());
switch (check)
{
case 1:
Console.WriteLine(">> [상태보기]를 선택하셨습니다.");
Console.Write("\n");
place = PlaceType.Status;
return;
break;
case 2:
Console.WriteLine(">> [인벤토리]를 선택하셨습니다.");
Console.Write("\n");
place = PlaceType.Inventory;
return;
break;
case 3:
Console.WriteLine(">> [상점]을 선택하셨습니다.");
Console.Write("\n");
place = PlaceType.Shop;
return;
break;
default:
Console.WriteLine("값을 다시 입력해주십시오");
continue;
}
}
}
문제 2 : 클래스에 Player와 관련되어 있으나, 굳이 없어도 되는 코드를 잡다하게 넣어놨었습니다.
해결 방법 : 클래스에 정말 필요한 기능을 제외하고는 외부에서 내부의 코드를 가져다 쓰도록 설정했습니다.
public class Player
{
public string name { get; set; } //이름
public int[] stats { get; private set; } = new int[4]
{ 10, 10, 10, 10 }; //HP, ATK, DEF, SPD
private int[] itemStats = new int[4] //장착 아이템 능력치
{ 0, 0, 0, 0 };
public List<Item> inventoryItem = new List<Item>(); //인벤토리 아이템 공간 확보
public void ItemAdd(Item item)
{
inventoryItem.Add(item);
}
public void Equip(Item item) //장비 착용
{
if (item.isEquip = false) //장비를 착용하지 않았다면,
{
itemStats[item.itemStatType] += item.stat; //해당 능력치를 플레이어에게 부여한다.
item.isEquip = true;
}
else
{
itemStats[item.itemStatType] -= item.stat;
item.isEquip = false;
}
}
}
5. 후기 및 내일 일정
후기라서 맨 아래에 내용을 표기하긴 했지만 글을 작성할 때 제일 먼저 적는 곳.. ㅋㅋ
오늘은 어제 배운 내용도 소화하지 못할 뿐더러 오늘 더한 내용을 들어서 머리가 터질 거 같네요 😀 그리고 개인 과제.. 생각보다도 모르는 기능을 사용해본다는 건 두려움과 함께 걱정이 많아지네요.
다음은 영상을 계속 돌려보며 코드를 마저 짜보면서 모르는 내용을 메모장이나 Notion에 정리하여 튜터 님께 질문을 하면 좋을듯합니다. 질문으로 궁금증을 푸는 것도 좋지만 결국은 스스로 공부하는 것이기에 가능하면 스스로 완벽한 코드를 짤 수 있으면 좋겠습니다.. 흐..
'유니티 스파르타 캠프 2주차' 카테고리의 다른 글
Get, Set 프로퍼티 문제와 Visual Studio 이슈 (0) | 2025.04.18 |
---|---|
개인 과제 개발일지 ver.6 (0) | 2025.04.17 |
개인 과제 개발 일지 (ver3 -> ver 5까지) (2) | 2025.04.16 |
Unity C# 기초 문법 중 어려운 내용 간단 정리 (2) | 2025.04.14 |