배열은 동일한 형식의 여러 변수를 모아놓은 것입니다. 우리가 지금까지 하나의 데이터만 저장했던 변수와 달리, 배열은 여러 데이터를 하나의 이름으로 관리할 수 있게 해줍니다.
배열 선언 및 초기화
배열을 선언할 때는 데이터 형식 뒤에 []
를 붙여서 배열임을 표시합니다. 배열의 크기는 선언 시 또는 생성 시 지정할 수 있습니다.
// 배열 선언 형식
데이터_형식[] 배열_이름 = new 데이터_형식[배열_용량];
// 예시: 정수 5개를 저장할 수 있는 배열 선언 및 생성
int[] scores = new int;
배열의 요소는 0부터 시작하는 인덱스(index)를 사용하여 접근합니다.
scores = 80; // 첫 번째 요소에 값 할당
scores = 74;
scores = 81;
scores = 90;
scores = 34;
배열 초기화 세 가지 방법
배열을 선언과 동시에 초기화하는 방법은 여러 가지가 있습니다.
string[] array1 = new string {"안녕", "Hello", "Halo"};
string[] array2 = new string[] {"안녕", "Hello", "Halo"};
new 데이터_형식[]
까지 생략하고 초기화: 가장 간결한 방법입니다.string[] array3 = {"안녕", "Hello", "Halo"};
배열 요소 접근 및 반복
배열의 요소는 인덱스를 통해 접근하며, foreach
문을 사용하면 배열의 모든 요소를 순회할 수 있습니다.
int[] scores = new int { 80, 74, 81, 90, 34 };
foreach (int score in scores)
{
Console.WriteLine(score); // 각 요소 출력
}
배열의 총 요소 개수는 Length
속성을 통해 얻을 수 있습니다.
int arrayLength = scores.Length;
C# 8.0부터는 ^
연산자를 사용하여 배열의 마지막 요소부터 인덱스를 지정할 수 있습니다. ^1
은 마지막 요소, ^2
는 마지막에서 두 번째 요소와 같습니다.
scores[^1] = 34; // scores[scores.Length - 1] 와 동일
System.Array
클래스 활용
모든 배열은 System.Array
클래스에서 파생됩니다. 이 클래스는 배열을 다루는 데 유용한 여러 메서드를 제공합니다.
Sort()
: 배열의 요소를 오름차순으로 정렬합니다.ForEach()
: 배열의 각 요소에 대해 지정된 액션(메서드)을 수행합니다.BinarySearch()
: 정렬된 배열에서 특정 값의 인덱스를 효율적으로 찾습니다.IndexOf()
: 특정 값의 첫 번째 인덱스를 찾습니다.Rank
: 배열의 차원 수를 반환합니다.FindIndex()
: 특정 조건에 맞는 첫 번째 요소의 인덱스를 찾습니다.TrueForAll()
: 배열의 모든 요소가 특정 조건을 만족하는지 확인합니다.Clear()
: 배열의 요소 범위를 기본값으로 설정합니다.Resize()
: 배열의 크기를 조정합니다. 배열 크기 조정 시 내부적으로 새 배열을 생성하고 기존 요소를 복사하는 방식이므로 성능에 영향을 줄 수 있습니다.Copy()
: 배열의 일부를 다른 배열로 복사합니다.배열 분할 (Array Slicing)
C#에서는 ..
연산자를 사용하여 배열의 특정 범위를 추출할 수 있습니다. 이는 System.Range
객체를 사용하거나 직접 범위를 지정하여 수행할 수 있습니다.
int[] scores = new int { 80, 74, 81, 90, 34 };
int[] sliced = scores[0..3]; // 인덱스 0부터 2까지 (3은 포함되지 않음)
int[] sliced2 = scores[..3]; // 처음부터 인덱스 2까지
int[] sliced3 = scores[1..]; // 인덱스 1부터 끝까지
int[] sliced4 = scores[..]; // 전체 배열 복사
int[] sliced5 = scores[^4..^1]; // 뒤에서 4번째부터 뒤에서 2번째까지
다차원 배열 (Multidimensional Array)
2차원 이상의 배열을 다차원 배열이라고 합니다. 행과 열을 가지는 표 형태의 데이터를 저장할 때 유용합니다.
// 2행 3열 2차원 배열 선언 및 생성
int[,] array = new int;
// 초기화와 동시에 생성
int[,] array2 = new int { {1, 2, 3}, {4, 5, 6} };
// 용량 생략 초기화
int[,] array3 = { {1, 2, 3}, {4, 5, 6} };
다차원 배열 요소는 각 차원의 인덱스를 사용하여 접근합니다.
Console.WriteLine(array2); // 2차원 배열의 0행 2열 요소 출력
3차원 배열은 int[,,] array = new int;
와 같이 선언하며, 초기화 시 중괄호를 중첩하여 사용합니다.
가변 배열 (Jagged Array)
가변 배열은 '들쑥날쑥한' 배열이라는 의미에서 Jagged Array라고 불립니다. 각 요소의 차원이나 크기가 다를 수 있는 배열의 배열입니다. 2차원 배열과 문법이 유사하지만, 각 행이 독립적인 배열 객체를 가리킵니다.
// 3개의 행을 가지는 가변 배열 선언 및 생성 (각 행의 열 개수는 나중에 지정)
int[][] jagged = new int[];
// 각 행(내부 배열) 생성 및 초기화
jagged = new int { 1, 2, 3, 4, 5 };
jagged = new int { 10, 20 };
jagged = new int { 100, 200, 300 };
가변 배열 요소는 jagged[행 인덱스][열 인덱스]
형식으로 접근합니다.
배열은 크기가 고정되어 있어 데이터를 추가하거나 삭제하기 어렵다는 단점이 있습니다. 컬렉션은 이러한 배열의 단점을 보완하여 데이터를 유연하게 관리할 수 있도록 돕는 클래스들의 모음입니다. 다양한 컬렉션 클래스가 제공되며, 각기 다른 특징과 용도를 가집니다.
소스에서는 ArrayList
, Queue
, Stack
, Hashtable
컬렉션을 소개합니다.
ArrayList
: 크기가 동적으로 변하는 배열과 유사한 컬렉션입니다. Add()
로 요소를 추가하고, RemoveAt()
으로 특정 인덱스의 요소를 삭제하며, Insert()
로 특정 위치에 요소를 삽입할 수 있습니다. ArrayList
는 object
형식의 요소를 저장하기 때문에 어떤 형식의 객체든 담을 수 있습니다. 하지만 값을 가져오거나 저장할 때 박싱(Boxing) 및 언박싱(Unboxing) 과정이 발생하여 성능 저하의 원인이 될 수 있습니다.Queue
: FIFO(First-In, First-Out) 구조의 컬렉션입니다. 먼저 들어온 요소가 먼저 나갑니다. Enqueue()
메서드로 요소를 추가하고, Dequeue()
메서드로 요소를 꺼냅니다.Stack
: LIFO(Last-In, First-Out) 구조의 컬렉션입니다. 가장 나중에 들어온 요소가 가장 먼저 나갑니다. Push()
메서드로 요소를 추가하고, Pop()
메서드로 요소를 꺼냅니다.Hashtable
: 키(Key)와 값(Value) 쌍으로 데이터를 저장하는 컬렉션입니다. 키를 통해 값에 빠르게 접근할 수 있습니다.컬렉션 초기화
컬렉션도 배열처럼 선언과 동시에 초기화할 수 있습니다.
// ArrayList 초기화 예시
ArrayList list = new ArrayList() { 11, 22, 33 };
// Hashtable 초기화 예시
Hashtable ht = new Hashtable() { {"하나", 1}, {"둘", 2}, {"셋", 3} };
인덱서는 클래스나 구조체의 인스턴스를 배열처럼 인덱스를 사용하여 접근할 수 있게 해주는 멤버입니다. 이를 통해 사용자 정의 클래스에 배열 접근 문법 (객체[인덱스]
)을 적용할 수 있습니다.
인덱서는 this
키워드와 대괄호 []
안에 매개변수를 정의하여 선언합니다. get
접근자와 set
접근자를 가질 수 있습니다.
class MyList
{
private int[] array; // 내부적으로 배열 사용
public MyList()
{
array = new int; // 초기 배열 크기 3
}
// 인덱서 정의
public int this[int index] // 인덱스 매개변수
{
get // 값을 읽을 때 호출
{
return array[index]; // 내부 배열의 해당 인덱스 값 반환
}
set // 값을 쓸 때 호출
{
// 인덱스가 배열 범위를 벗어나면 배열 크기 조정
if (index >= array.Length)
{
Array.Resize<int>(ref array, index + 1);
Console.WriteLine($"Array Resized : {array.Length}");
}
array[index] = value; // 내부 배열에 값 저장
}
}
// Length 속성 추가 (선택 사항)
public int Length
{
get { return array.Length; }
}
}
위 MyList
예시처럼, 인덱서 set
접근자 안에서 배열의 크기를 자동으로 조정하는 로직을 구현하여 동적 배열처럼 사용할 수 있습니다.
인덱서를 사용하면 다음과 같이 객체에 배열처럼 접근할 수 있습니다.
MyList list = new MyList(); // MyList 객체 생성
list = 1; // 인덱서 set 호출, list.array = 1
list = 2;
list = 3;
list = 5; // 인덱스 4 접근 시 배열 크기 자동 조정 후 값 저장
for (int i = 0; i < list.Length; i++)
{
Console.WriteLine(list[i]); // 인덱서 get 호출하여 값 읽기
}
foreach
문은 기본적으로 배열이나 특정 컬렉션 형식에서 사용할 수 있습니다. 사용자 정의 클래스에서도 foreach
문을 사용하고 싶다면, IEnumerable
인터페이스와 IEnumerator
인터페이스를 구현해야 합니다.
IEnumerable
: GetEnumerator()
메서드를 가집니다. 이 메서드는 컬렉션의 열거자(Enumerator)를 반환합니다.IEnumerator
: Current
속성, MoveNext()
메서드, Reset()
메서드를 가집니다.Current
: 현재 위치의 요소를 반환합니다.MoveNext()
: 다음 요소로 이동합니다. 더 이상 요소가 없으면 false
를 반환합니다.Reset()
: 열거자의 위치를 초기 위치(첫 번째 요소 앞)로 되돌립니다.MyList
클래스에 IEnumerable
및 IEnumerator
를 구현하는 예시는 소스에 자세히 나와 있습니다.
yield return
키워드를 사용하면 IEnumerable
과 IEnumerator
를 직접 구현하는 것보다 훨씬 간단하게 열거자를 만들 수 있습니다. yield return
은 열거자가 다음 요소를 반환할 때마다 실행을 일시 중지하고, 다시 호출되면 중지된 지점부터 실행을 재개합니다.
class MyNumerator
{
int[] numbers = { 1, 2, 3, 4 }; // 예시 데이터
// yield return 사용
public IEnumerator<int> GetEnumerator()
{
yield return numbers; // 첫 번째 요소 반환
yield return numbers; // 두 번째 요소 반환
yield return numbers; // 세 번째 요소 반환
// yield break; // 여기서 멈추면 이 코드는 실행되지 않음
yield return numbers; // 네 번째 요소 반환
}
}
이렇게 yield return
으로 GetEnumerator
메서드를 구현하면, 해당 클래스의 객체는 foreach
문에서 사용할 수 있게 됩니다.
배열, 컬렉션, 인덱서는 C#에서 데이터를 관리하는 강력한 도구들입니다. 각자의 특징을 이해하고 적절하게 활용하면 효율적인 프로그래밍이 가능합니다.