C# 배열과 컬렉션 그리고 인덱서

김민구·2025년 5월 27일
0

C#

목록 보기
20/31

1. 배열 (Array)

배열은 동일한 형식의 여러 변수를 모아놓은 것입니다. 우리가 지금까지 하나의 데이터만 저장했던 변수와 달리, 배열은 여러 데이터를 하나의 이름으로 관리할 수 있게 해줍니다.

배열 선언 및 초기화

배열을 선언할 때는 데이터 형식 뒤에 []를 붙여서 배열임을 표시합니다. 배열의 크기는 선언 시 또는 생성 시 지정할 수 있습니다.

// 배열 선언 형식
데이터_형식[] 배열_이름 = new 데이터_형식[배열_용량];

// 예시: 정수 5개를 저장할 수 있는 배열 선언 및 생성
int[] scores = new int;

배열의 요소는 0부터 시작하는 인덱스(index)를 사용하여 접근합니다.

scores = 80; // 첫 번째 요소에 값 할당
scores = 74;
scores = 81;
scores = 90;
scores = 34;

배열 초기화 세 가지 방법

배열을 선언과 동시에 초기화하는 방법은 여러 가지가 있습니다.

  1. 용량을 명시하고 초기화:
    string[] array1 = new string {"안녕", "Hello", "Halo"};
  2. 용량을 생략하고 초기화: 컴파일러가 초기화 요소의 개수를 보고 자동으로 용량을 결정합니다.
    string[] array2 = new string[] {"안녕", "Hello", "Halo"};
  3. 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[행 인덱스][열 인덱스] 형식으로 접근합니다.

2. 컬렉션 (Collection)

배열은 크기가 고정되어 있어 데이터를 추가하거나 삭제하기 어렵다는 단점이 있습니다. 컬렉션은 이러한 배열의 단점을 보완하여 데이터를 유연하게 관리할 수 있도록 돕는 클래스들의 모음입니다. 다양한 컬렉션 클래스가 제공되며, 각기 다른 특징과 용도를 가집니다.

소스에서는 ArrayList, Queue, Stack, Hashtable 컬렉션을 소개합니다.

  • ArrayList: 크기가 동적으로 변하는 배열과 유사한 컬렉션입니다. Add()로 요소를 추가하고, RemoveAt()으로 특정 인덱스의 요소를 삭제하며, Insert()로 특정 위치에 요소를 삽입할 수 있습니다. ArrayListobject 형식의 요소를 저장하기 때문에 어떤 형식의 객체든 담을 수 있습니다. 하지만 값을 가져오거나 저장할 때 박싱(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} };

3. 인덱서 (Indexer)

인덱서는 클래스나 구조체의 인스턴스를 배열처럼 인덱스를 사용하여 접근할 수 있게 해주는 멤버입니다. 이를 통해 사용자 정의 클래스에 배열 접근 문법 (객체[인덱스])을 적용할 수 있습니다.

인덱서는 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 호출하여 값 읽기
}

4. foreach 가능한 객체 만들기

foreach 문은 기본적으로 배열이나 특정 컬렉션 형식에서 사용할 수 있습니다. 사용자 정의 클래스에서도 foreach 문을 사용하고 싶다면, IEnumerable 인터페이스와 IEnumerator 인터페이스를 구현해야 합니다.

  • IEnumerable: GetEnumerator() 메서드를 가집니다. 이 메서드는 컬렉션의 열거자(Enumerator)를 반환합니다.
  • IEnumerator: Current 속성, MoveNext() 메서드, Reset() 메서드를 가집니다.
    • Current: 현재 위치의 요소를 반환합니다.
    • MoveNext(): 다음 요소로 이동합니다. 더 이상 요소가 없으면 false를 반환합니다.
    • Reset(): 열거자의 위치를 초기 위치(첫 번째 요소 앞)로 되돌립니다.

MyList 클래스에 IEnumerableIEnumerator를 구현하는 예시는 소스에 자세히 나와 있습니다.

yield return 키워드를 사용하면 IEnumerableIEnumerator를 직접 구현하는 것보다 훨씬 간단하게 열거자를 만들 수 있습니다. 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#에서 데이터를 관리하는 강력한 도구들입니다. 각자의 특징을 이해하고 적절하게 활용하면 효율적인 프로그래밍이 가능합니다.

profile
C#, Unity

0개의 댓글