C#과 유니티로 만드는 MMORPG 게임 개발 시리즈 강의를 수강하며 객체지향 파트에서 배우고 느낀 점을 정리해보았다.
- 배열의 기초
class Program
{
static void Main(string[] args)
{
// <배열>
int a;
int[] scores = new int[5]; // scores라는 배열은 int 형식의 데이터를 5개 가지고 있다는 뜻
// 0 1 2 3 4
scores[0] = 10; // 배열에 값 할당
scores[1] = 20;
for(int i = 0; i < scores.length; i++) // 이미 할당된 scores의 length가 5이므로,
// 이에 벗어나는 범위(ex. 10)의 범위를 for문에서 읽고자 하면 오류가 발생한다.
// 오류를 방지하기 위해 length 키워드를 사용하고 범위 내에서만 작동하도록 한다.
{
Console.WriteLine(scores[i]); // 10, 20, 0, 0, 0이 출력 됨
// 0, 1번째 인덱스는 값이 할당되어 있으나 이후로는 할당값이 없으므로
// 모두 기본값인 0을 출력하게 됨
}
}
}
- 배열을 선언하는 또 다른 방법은 다음과 같다. 이 경우에는 미리 할당된 배열 크기만큼 반드시 값이 할당되어야 하므로 10, 20까지만 할당하고 나머지는 비워두는 0번 방식과 동일하게 사용할 수 없다.
int[] scores = new int[5]; // 0번 방법, scores라는 배열은 int 형식의 데이터를 5개 가지고 있다는 뜻
scores[0] = 10; // 배열에 값 할당
scores[1] = 20;
int[] scores = new int[5] { 10, 20, 30, 40, 50 }; // 1번 방법
int[] scores = new int[] { 10, 20, 30, 40, 50 }; // 2번 방법
int[] scores = { 10, 20, 30, 40, 50 }; // 3번 방법
- 만약 동적으로 배열 값을 할당하고 싶을 때는 2번 방법처럼 new int[5]에 있는 [] 값을 비워두는 방법이 있다.
- 2번 방법의 약식인 3번 방법을 사용해도 결과는 동일하다.
- 강사님이 주로 사용한다고 언급한 방법은 1번 방법이다. 배열을 동적할당한다는 것을 명시적으로 나타낼 수 있기 때문.
- 배열 오류를 막기 위한 foreach문의 활용
for(int i = 0; i < 5; i++)
{
Console.WriteLine(scores[i]); // 10, 20, 0, 0, 0이 출력 됨
}
foreach (int score in scores) // for문에서 가독성과 간결성을 높인 foreach문
// 인덱스 오류를 방지할 수 있음
{
Console.WriteLine(score); // for문과 동일하게 10, 20, 0, 0, 0이 출력 됨
}
- 배열의 복사
class Program
{
static void Main(string[] args)
{
// <배열 : 복사>
int[] scores = new int[5] { 10, 20, 30, 40, 50 };
int[] scores2 = scores; // 위에 선언한 배열의 값의 원본을 2에 할당함
scores2[0] = 9999; // 원본 값을 할당했기 때문에 scores와 scores2 둘다
// 0번 인덱스의 할당 값이 9999가 됨
for(int i = 0; i < 5; i++)
{
Console.WriteLine(scores[i]); // 9999, 20, 30, 40, 50이 출력됨
}
foreach (int score in scores)
{
Console.WriteLine(score); // for문과 동일하게 출력됨
}
}
}
- 연습문제
class Program
{
static int GetHighestScore(int[] scores) // 제일 높은 값을 리턴하는 함수
{
int maxValue = 0;
foreach (int score in scores)
{
if (score > maxValue)
maxValue = score;
}
return maxValue;
}
static int GetAverageScore(int[] scores) // 배열 값의 평균을 리턴하는 함수
{
int sum = 0;
if (scores.Length == 0) // 배열의 크기가 0인 경우 그대로 실행하면
// 오류가 발생하기 때문에 연산 이전에 미리 예외처리해줌
{
return 0;
}
foreach (int score in scores)
{
sum += score;
}
return sum / scores.Length;
}
static int GetIndexOf(int[] scores, int value) // 스코어 변수에서 원하는 값이 있는지 체크하고
// 해당하는 인덱스 반환, 없으면 -1 반환하는 함수
{
for (int i = 0; i < scores.Length; i++) // 인덱스를 반환하는게 목표이므로 for문을 사용함
{
if(scores[i] == value)
{
return i;
}
}
return -1;
}
static void Sort(int[] scores) // 크기 순서대로 배열을 정렬해주는 함수
{
for (int i = 0; i < scores.Length; i++)
{
// [i~scores.length-1]의 범위 내에서 제일 작은 숫자가 있는 index를 찾는다.
int minIndex = i;
for (int j = i; j < scores.Length; j++)
{
if (scores[j] < scores[minIndex])
minIndex = j;
}
// 현재 위치와 제일 작은 값이 있는 위치를 swap 해줌
int temp = scores[i];
scores[i] = scores[minIndex];
scores[minIndex] = temp;
}
}
static void Main(string[] args)
{
// <배열 : 복사>
int[] scores = new int[5] { 10, 30, 40, 20, 50 };
int hightestScore = GetHighestScore(scores);
Console.WriteLine(hightestScore);
int avgScore = GetAverageScore(scores);
Console.WriteLine(avgScore);
int index = GetIndexOf(scores, 15);
Console.WriteLine(index);
Sort(scores);
}
}
- ✔ 소팅의 경우 실무에서 직접 구현할 일은 없지만 면접 시에 기본적인 소팅 알고리즘을 4개 정도 출제 되니 관련해서 추가적인 공부가 필요하다.
- 다차원 배열
class Program
{
class Map
{
// 1은 갈 수 없는 곳, 0은 갈 수 있는 곳이라고 가정하고 맵 생성
int[,] tiles = {
{ 1, 1, 1, 1, 1},
{ 1, 0, 0, 0, 1},
{ 1, 0, 0, 0, 1},
{ 1, 0, 0, 0, 1},
{ 1, 1, 1, 1, 1}
};
public void Render()
{
ConsoleColor defaultColor = Console.ForegroundColor;
// 콘솔 출력 텍스트의 색깔이 변하지 않도록 전경색을 저장해줌
for (int y = 0; y < tiles.GetLength(1); y++)
// 왼쪽에서 오른쪽으로 읽기 때문에 (1)은 [여기,] 를 의미
{
for (int x = 0; x < tiles.GetLength(0); x++) // (0)은 [,여기]를 의미
{
if (tiles[y, x] == 1) // 한 줄의 마지막 부분이면
Console.ForegroundColor = ConsoleColor.Cyan;
else
Console.ForegroundColor = ConsoleColor.Magenta;
Console.Write('\u25cf');
}
Console.WriteLine(); // 한 줄의 표현이 끝났으므로 구분하기 위해 줄을 한 칸 띄움
}
Console.ForegroundColor = defaultColor;
}
}
static void Main(string[] args)
{
/* // <다차원 배열>
// 하나는 층, 하나는 호수를 나타낸다고 생각하면 된다.
// 다차원 배열을 선언하려면 ','를 사용한다.
// 읽을 때는 오른쪽에서 왼쪽 순서이며
// 오른쪽은 1차원의 크기, 왼쪽이 2차원의 크기를 의미한다.
// 2F [ . . . ]
// 1F [ . . . ]
int[,] arr = new int[2, 3] { { 1, 2, 3 }, { 1, 2, 3 } }; // 초기화 문법
// 1차원 배열과 마찬가지로 선언시 앞 부분의 값은 생략이 가능하다.
// int[,] arr = new int[,] { { 1, 2, 3 }, { 1, 2, 3} };
arr[0, 0] = 1; // 값 세팅, 접근하는 방법
arr[1, 0] = 1;
*/
Map map = new Map();
map.Render();
}
}
- 가변 배열
- 활용되는 경우가 거의 없긴 하지만, 특수하게 사용하게 될 수 있으므로 알아두자.
class Program
{
// < 다차원 배열 : 가변 배열(배열의 배열, 특수한 경우)>
static void Main(string[] args)
{
int[,] map = new int[2, 3]; // 특정 행 또는 열이 특수하게 더 많거나 적은 경우
// [ . . . ]
// [ . . . . . . . ]
// [ . . . ]
int[][] a = new int[3][];
a[0] = new int[3];
a[1] = new int[6];
a[2] = new int[2];
a[0][0] = 1;
}
}
- List
- 배열의 경우 생성시에 이미 크기가 정해져있기 때문에, 원래 설정해둔 크기 이상의 데이터를 담을 수 없다.
- 이를 해결하기 위해 배열의 크기를 확신할 수 있다면 여유롭게 1000개쯤 최대값을 선언할 수도 있지만, 실질적으로 사용하는 공간이 5개 정도 밖에 없다면 남은 995개만큼의 공간에 대한 메모리 낭비가 될 수 있다.
- 이러한 문제를 해결하기 위해 필요에 따라 배열의 크기를 동적으로 할당할 수 있는 List라는 것이 존재한다.
- List를 사용하기 위해서는 using System.Collections.Generic;를 선언해주어야 한다.
class Program
{
static void Main(string[] args)
{
// <List ; 동적 배열>
List<int> list = new List<int>();
// <> 안에는 사용할 리스트의 타입을 기입한다.
// 이러한 것을 Generic타입이라고 하며, 어떤 타입이든 담을 수 있게 해준다.
// List는 일종의 class이기 때문에, 참조 형태라서 사용시에 new 키워드를 사용해야한다.
for (int i = 0; i < 5; i++)
{
list.Add(i); // 리스트의 맨 끝에 새로운 데이터를 밀어 넣어준다.
}
list[0] = 1; // 이러한 방식으로 리스트에 접근 할 수 있다.
// 단, 리스트가 비어있는 상태로 list에 접근하면
// 오류가 발생하므로 반드시 확인해주어야 한다.
}
}
- 리스트는 사용시에 배열과 동일한 방법으로 접근할 수 있다.
class Program
{
static void Main(string[] args)
{
// <List의 사용>
List<int> list = new List<int>();
for (int i = 0; i < 5; i++)
list.Add(i);
for (int i = 0; i < 5; i++)
Console.WriteLine(list[i]);
foreach(int num in list)
Console.WriteLine(num);
}
}
- 리스트의 삽입
class Program
{
static void Main(string[] args)
{
List<int> list = new List<int>();
for (int i = 0; i < 5; i++)
list.Add(i);
// <List의 삽입>
// 맨 끝 부분에 데이터를 추가하는 add와 달리 특정 인덱스에
// 원하는 값을 할당하고 싶을 때 사용할 수 있는 방법
list.Insert(2, 999); // 2번째 인덱스에 999를 할당함
// 배열의 구성이 [ 0 1 999 2 3 4 ] 로 바뀜 됨
for (int i = 0; i < list.Count; i++)
Console.WriteLine(list[i]);
foreach(int num in list)
Console.WriteLine(num);
}
}
- 리스트의 삭제 : Remove와 RemoveAt, Clear 함수를 사용하여 리스트의 데이터를 삭제할 수 있다.
1. Remove
Remove는 bool 값을 리턴하기 때문에 디버깅을 해보면 true가 반환됨을 알 수 있다.
또한 Remove() 함수에 아이템으로 3을 할당했기에 리스트 내부에 '3' 이 삭제되고 나머지 값만 남아있음을 알 수 있다.
2. RemoveAt
RemoveAt은 void 타입의 함수이기 때문에 반환 값이 없으며, 지정된 인덱스에 저장된 데이터 값을 삭제한다.
위의 코드에서 0번째 인덱스를 삭제하였기 때문에 배열의 Count는 4로 줄어들고, 1번째 인덱스에 든 값부터 0번째 배열에 할당되어 하나씩 자릿수가 줄었음을 알 수 있다.
3. Clear
list.Clear(); // 리스트의 내용을 전체 삭제한다.
지정된 리스트의 내용을 모두 삭제하여 초기화한다.
- 리스트 사용시 주의점과 단점
리스트를 중간에 삽입/삭제하는 것은 효율적이지 않다.
해당 작업을 수행할 때 반드시 나머지 배열의 값을 복사하여 이동시키고 빈 공간에 할당해야 하기 때문에 시간복잡도면에서 적합하지 않기 때문이다.
또한, 강사님이 짚어주신 바로는 아래에 서술할 문제 때문에 경우에 따라 list를 무작정 사용하는 것은 문제가 된다.
MMORPG를 만든다고 가정했을 때, 필드에 몬스터와 플레이어가 매우 많다.
이때 각종 생명체를 구분하기 위해 대부분 ID를 부여하여 식별자로 활용하게 된다.
예를 들어 103이라는 ID를 가진 몬스터를 공격하고 싶을 때는
10 103 -> 이런 식으로 공격이란 네트워크 요청에 해당하는 아이디 10과 함께 몬스터의 아이디인 103 넣어주게 된다.이런 상황에서 만약 리스트로 모든 것을 관리하고 있다면 해당 데이터를 찾는 것이 문제가 된다.
만약 몬스터 리스트의 count가 100만개라고 하면 이 중에서 103번에 해당하는 몬스터를 찾으려면 처음부터 끝까지 루프를 돌면서 모든 몬스터를 하나씩 체크하여 103번인지 아닌지를 판단하는 방법 밖에 없다. 즉, 매우 비효율적
이를 해결하기 위해서는 특징 key를 사용하여 value를 찾는 방법인 Dictionary가 가장 적합하다.
- Dictionary
- Dictionary는 List와 달리 Key와 Value가 한쌍으로 존재한다는 특징이 있다.
- Key를 사용하여 특정 데이터의 Value를 매우 빠르게 찾을 수 있다는 장점이 있다.
- 단, 데이터를 얻기 위해서는 반드시 Key를 사용하여야 하며 반대로 Value만 알고 있는 경우 해당 Value값에 대한 Key값은 알 수 없다.
- 딕셔너리 값 할당
- 딕셔너리 값 사용
- 딕셔너리 값 삭제
- 딕셔너리의 구조 : HashTable
: HashTable에 대한 예시
ex) 아주 큰 박스에 공이 들어있는 경우 [ ......... ] 1만개(1~1만개의 데이터 존재)
특정 공을 찾기 위해서 공을 하나씩 꺼내보는 것이 아니라,
아주 큰 박스 하나가 아니라 박스를 여러개(1천개쯤)로 쪼개어
1에서 1만 숫자 사이의 데이터를 잘 배분하여 10개씩 집어넣는다.
[11~20] [] [] [] [] -> 이렇게 1천개의 상자 만듦 -> 찾기가 더 수월함!
777번 공을 찾고자 한다 -> 10개씩 나눴으므로 777번째 상자에 공이 있음을 알 수 있음.
리스트를 하나씩 순회하는 것보다 훨씬 데이터 찾기가 수월해지지만,
메모리 상으로는 여러개의 박스를 준비해야하므로 큰 손해를 보게 된다.
결국 해시테이블을 사용하는 방법은 메모리를 내주고 성능을 취하는 방법이라고 할 수 있다.
+) 딕셔너리와 HashTable에 대해서는 전에도 공부한적이 있기 때문에 그 때의 자료를 추가하여 별도의 포스팅으로 남기는 것이 좋겠다.
'C#' 카테고리의 다른 글
220817 Interface (인터페이스) (0) | 2022.08.17 |
---|---|
[C#] 일반화(Generic) (0) | 2022.08.16 |
220718 C# 연습문제 풀이 (0) | 2022.07.18 |
[C#] 문법 정리 (0) | 2022.07.17 |
220716 C# 람다식 (0) | 2022.07.16 |