예를 들어 한 반의 학생 이름 목록이나, 게임을 예시로는 인벤토리 단축키 등의 목록이 있다고 생각해보자.
이와 같은 특정 데이터의 집합으로 되어 있는 목록을 묶어서 정리하는 것을 배열이라고 한다.
- 동일한 자료형의 요소들로 구성한 데이터 집합
- 인덱스를 통하여 배열요소(Element)에 접근할 수 있음
- 배열의 처음 요소의 인덱스는 0부터 시작함
- 자료형과 크기를 정하여 생성
- [인덱스]를 사용
- 배열의 Length를 통해 크기를 확인
배열을 선언할 때 해당 내용을 확인하고서 선언한다. 기본적인 형태는
자료형[] 배열이름 = new 자료형[크기]; 으로 선언한다.
기존의 변수 선언과 비교하여 알아보자
// 기존의 변수 선언
string text; // 문자열 저장소 하나 만들기
text = "김민수"; // 저장소에 김민수 저장하기
// 배열의 선언
string[] texts; // 문자열 여러 개 담을 수 있는 저장소 만들기
// -> 몇 개 담을 수 있는지 정해줘야함
texts = new string[75]; // 75개짜리 저장소 만들기
그렇다면 배열은 왜 사용하는 걸까? 배열을 사용하면 편리한 경우를 아래 예시로 보자.
아이템1 : 포션
아이템2 : 표창
아이템3 : 부적
아이템4 : 폭탄
이와 같은 목록이 있다고 가정했을 때, 이를 각각 변수 선언과 배열 선언으로 작성해보자.
// 변수로 선언했을 때
string item1 = "포션";
string item2 = "표창";
string item3 = "부적";
string item4 = "폭탄";
// 배열로 선언했을 때 (1)
string[] items = new string[4]; // 크기 4의 배열 선언
items[0] = "포션"; // 0번째 요소 저장
items[1] = "표창"; // 1번째 요소 저장
items[2] = "부적"; // 2번째 요소 저장
items[3] = "폭탄"; // 3번째 요소 저장
// 배열로 선언했을 때 (2)
string[] items;
items = new string[5]{"포션", "표창", "부적", "폭탄"};
위와 같이 변수로 선언하는 것과 배열로 선언하는 것에서 차이가 난다.
지금은 목록에 4개의 항목밖에 없기 때문에 큰 차이가 나지 않는 것처럼 보일 수도 있지만,
실제 게임에서는 관리해야 할 항목이 100개, 1000개 이상으로 넘어갈 수도 있을 것이다.
이와 같은 상황에서 배열을 사용하는 상황을 생각해보자.
위의 상황에서 변수로 선언한 경우와 배열로 선언한 경우의 출력에서 차이를 보자.
변수로 선언하고 순서대로 출력했을 때는 다음과 같이 출력할 수 있을 것이다.
string item1 = "포션";
string item2 = "표창";
string item3 = "부적";
string item4 = "폭탄";
Console.WriteLine("사용할 아이템을 골라주세요.");
switch (int.Parse(Console.ReadLine()))
{
case 1:
Console.WriteLine("{0} 을 사용했습니다.", item1);
break;
case 2:
Console.WriteLine("{0} 을 사용했습니다.", item2);
break;
case 3:
Console.WriteLine("{0} 을 사용했습니다.", item3);
break;
case 4:
Console.WriteLine("{0} 을 사용했습니다.", item4);
break;
}
switch문을 사용하여 위와 같이 코드를 짰을 때, 상당히 길어진 것을 확인할 수 있다.
또한 이와 같은 경우는 아이템의 총 개수를 확인하거나, 아이템 위치가 변경되는 등의 상황에서 반영하는 게 어렵고 자칫하면 실수가 날 수도 있다.
따라서 이걸 배열로 표현해보도록 하자.
코드를 작성하기에 앞서 배열의 기능 몇 가지 기능 알아보기
- string[i] array 로 해당 배열 값을 가져올 수 있다.
ex) 위의 item 배열에서 item[2] = "부적";
- (배열).Length로 해당 배열 내의 인덱스 개수를 알 수 있다.
ex) 위의 item 배열에서 item.Length = 4;
이와 같은 부분을 적용하여 코드를 작성해보자.
string[] shortCuts = new string[4];
shortCuts[0] = "포션";
shortCuts[1] = "표창";
shortCuts[2] = "부적";
shortCuts[3] = "폭탄";
Console.WriteLine("사용할 아이템을 골라주세요.");
int.TryParse(Console.ReadLine(), out int choice);
if (choice < shortCuts.Length && choice > 0) // 배열의 크기를 넘지 않는 수를 입력할 때 출력됨
{
Console.WriteLine("{0} 을 사용했습니다.", shortCuts[choice - 1]);
}
else // 예외 처리를 위한 안전장치
{
Console.WriteLine("입력이 잘못되었습니다.");
}
변수로 선언하고 코드를 작성했을 때보다 코드가 간결해진 것과 더불어,
아이템이 추가되는 상황에서 배열의 크기를 변경했을 때 반복문의 범위가 배열의 크기로 정해져 있으므로, 일일이 조건식을 수정하지 않아도 되는 장점이 있다.
* 예외사항에 대한 주의 - 위와 같이 예외 사항, 배열 크기를 넘어선 값이 호출되었을 때에 대한 설정을 미리 해 두지 않으면, 게임이 크래시가 발생하는 등의 문제가 발생할 수 있으므로 반드시 설정하도록 한다.
배열에서 사용할 수 있는 함수로, 데이터집합의 처음부터 끝까지 반복할 수 있다.
사용 형태는 아래와 같다.
foreach((자료형) (변수) in (배열이름)
string[] shortCuts = new string[4];
shortCuts[0] = "포션";
shortCuts[1] = "표창";
shortCuts[2] = "부적";
shortCuts[3] = "폭탄";
foreach(string s in shortCuts)
{
Console.WriteLine(s);
}
이를 출력하면 아래와 같이 출력된다
foreach는 특징이 하나 있는데, 읽기만 가능하고 쓰기는 불가능하다는 것이다.
지금만 봐서는 그냥 for문을 쓰면 되지 foreach가 굳이 필요할 것 같아 보이진 않지만, 후에 자료구조에 관한 내용을 배우고 나서 이 기능을 활용할 수 있는 방법을 알아보고자 한다.
아래와 같은 상황을 상황을 생각해 보자.
이와 같은 상황을 배열로 표현하면 다음과 같다.
string[,] students = new string[8, 30]; // 8반, 30명의 학생들
앞서 작성했던 배열은 '1차원 배열'로 정의되는 단순한 배열이었다.
다만 지금 상황과 같이 '8개'의 반에 각각 '30명'의 학생들이 있으니, 이를 2차원 배열로 표시한 것이다. 1차원 이상의 배열을 표시할 때에는 이와 같이 string[] students ~ 로 표시된 부분 안에 쉼표(,)를 사용할 개수만큼 추가하면 된다.
그러면 예시로 이 배열에 학생 이름을 저장해 보자.
students[1, 12] = "김전사"; // 1반의 12번 학생 김전사
students[2, 29] = "이궁수"; // 2반의 29번 학생 이궁수
students[7, 1] = "박도적"; // 7반의 1번 학생 박도적
이와 같은 방식으로 저장할 수 있다.
2차원 이상의 배열의 경우에는 Length로 배열의 크기를 확인할 때에 아래와 같은 방법으로 확인할 수 있다.
Console.WriteLine(students.Length); // 총길이 표시 (총 학생 수)
Console.WriteLine(students.GetLength(0)); // 반이 몇 개인지
Console.WriteLine(students.GetLength(1)); // 한 반의 학생 수가 몇 명인지
이를 출력하면 아래와 같이 출력되는 것을 알 수 있다.
총 길이(총 학생수) : 240명
반 수 : 8반
한 반의 학생 수 : 30명
위와 같이 확인할 수 있다.
다른 예시로 위의 학생 정보를 3차원 배열로 확장시켜보자.
string[,,] students = new string[3, 8, 30]; 3학년, 8반, 30명의 학생들
students[1, 1, 12] = "김전사"; // 1학년 1반의 12번 학생 김전사
students[3, 2, 29] = "이궁수"; // 3학년 2반의 29번 학생 이궁수
students[2, 7, 1] = "박도적"; // 2학년 7반의 1번 학생 박도적
이와 같이 저장할 수 있다.
포켓몬스터 타일맵 - 2차원 배열로 맵의 특정 위치에 무슨 물체가 있는지 선언
여기서 자료형으로 알고 있던 String에 대해 다시 한 번 알아보자.
<string의 불변성(Immutable)>
string은 특징상 다른 기본자료형과 다르게 char의 집합(배열) 으로 이루어져 있다.
따라서, string은 런타임 당시에 크기가 결정되며 그 크기가 일정하지 않다.
이에 string은 다른 기본자료형과 다르게 구조체가 아닌 클래스로 구현되어 있다.
(런타임시 크기를 정할 수 있는 메모리는 힙영역을 사용)
단, 기본자료형과 같이 값형식을 구현하기 위해 string 클래스에 처리를 값형식처럼 동작하도록 구현되어 있다.
이를 구현하기 위해 string 간의 대입이 있을 경우 참조에 의한 주소값 복사가 아닌 깊은 복사를 진행한다.
결과적으로 데이터 자체를 복사하는 값형식으로 사용하지만 힙영역을 사용하기 때문에 string이 설정되면 변경할 수 없도록하는 '불변성'을 가진다.
말이 많이 어렵기는 한데, 요약하자면 string은 참조 타입이면서 불변이라는 것이다.
string은 배열로 이루어져 있으며, 각 글자에 위치가 배정되어 있는 배열이기 때문에 그런 것이다.
따라서 string 문자열이 배열이라는 사실을 알면, 다음과 같은 것들이 가능하다.
// 0 1 2 3 4
string text = "안녕하세요";
Console.WriteLine(text.Length); // 결과 : 5
Console.WriteLine(text[1]); // 결과 : 녕
배열의 특징을 활용하면 원하는 문자만 불러올 수도 있고, 전체 문자 개수도 알 수 있다.
string text = "abcde";
char[] array = text.ToCharArray(); // string을 char 형태로 변환
for (int i = 0; i < array.Length; i++)
{
Console.WriteLine(array[i]); // 출력 - abcde(세로로)
}
array[2] = 'x'; // 세번째 문자열을 x로 변경
for (int i = 0; i < array.Length; i++)
{
Console.WriteLine(array[i]); // 출력 - abxde(세로로)
}
text = new string(array); // string으로 변환
Console.WriteLine(text); // 출력 - abxde(가로로)
혹은 Replace를 사용하여 특정 문자열을 다른 문자열로 변경할 수 있다.
string text = "abcdeaaabbb";
Console.WriteLine(text.Replace('a', 'x')); // a 문자 전부를 x로 전환
string text = "abcde"
Console.WriteLine(text.ToUpper()); // 대문자로
Console.WriteLine(text.ToLower()); // 소문자로