[Unity] 유니티(C#) 자료구조 - 배열(Array)

조재훈·2024년 6월 24일

개요

지금까지 유니티를 사용하면서 여러 자료구조를 사용해서 개발해왔지만 한번도 이걸 정리해서 글로 써본적도 없이 대충 개념만 파악했는데 이번 기회에 제대로 정리해서 좀 더 이유를 알고 자료구조를 쓰려고 한다

먼저 첫 번째로 배열에 대해 알아보자

개념

동일한 타입의 변수들을 저장할 수 있는 자료구조

메모리 공간에 연속적으로 데이터를 저장하기 때문에 메모리 사용을 최적화할 수 있고 배열의 첫 번째 요소의 주소와 각 데이터 타입의 크기만 알면, 특정 요소에 접근할 수 있다

배열의 각 요소는 특정 인덱스로 참조할 수 있는데 이를 이용해 반복문 등을 이용한 순차 접근과 특정 인덱스를 이용한 임의 접근이 모두 가능하다(배열의 크기를 벗어나는 인덱스로 참조하면 에러)

1차원 뿐만 아니라 2차원, 3차원까지 다차원 배열을 사용해 표현할 수 있어 이미지 처리, 그래픽 등에서 계산에 유용하게 쓰인다

유니티에서는 System.Array가 흔히 알고 있는 배열을 가리킨다

선언과 초기화

// 선언 : dataType[] arrayName;
int[] intArray;		// 초기화는 안 된 상태(null), 이대로 intArray를 참조하면 에러

// 초기화 : 배열 이름 = new 데이터타입[크기]로 초기화해주는데 이때 배열의 크기가 고정된다
intArray = new int[5];

// 선언 + 초기화
int[] intArray = new int[10];

배열을 위와 같이 초기화하면 초기에 들어있는 값은 무엇일까?

for (int i = 0; i < 5; i++)
	Debug.Log(intArray[i]);

  • 따로 코드를 작성하지 않고 초기화만 진행 시 모든 요소가 0으로 초기화된다
    • C#에서는 배열을 초기화할 때 해당 데이터 타입의 기본값으로 초기화가 됨(C#의 기본값 초기화 규칙)
  • 사용자가 지정하고 싶은 값으로 초기화하고 싶을때는?
    • 반복문을 사용해 모든 요소를 순회하면서 초기화하기
      for(int i = 0; i < intArray.Length; i++)
      	intArray[i] = i;
    • Array.Fill 메서드 사용(C# 7.0 이상)
      Array.Fill(intArray, 99);
      • 배열의 크기에 맞게 두 번째 인수로 지정한 값으로 초기화가 된다
    • 초기화 블록 사용
      intArray = new int[]{1, 2, 3, 4, 5, 6};
      // int[] intArray = {1, 2, 3, 4, 5, 6};
      • 따로 배열의 크기를 지정하지 않아도 블록안의 수만큼 크기가 지정된다
      • 물론 대괄호 안에 크기를 지정해줘도 되는데 블록 수와 일치하지 않으면 에러가 난다
      • 배열의 크기가 클 때는 되도록 쓰지 말자(다 입력하기 귀찮잖아)
    • 인스펙터에서 초기화 가능(public이나 SerializeField 사용하는 경우)
      • 따로 코드로 초기화하지 않아도 인스펙터에서 +/-를 누르고 요소를 편집해도 사용할 수 있다

배열 활용

배열 크기 구하기

반복문 등에서 배열의 크기를 구하고 싶을 때 [배열명].Length를 통해 배열의 길이를 얻어온다

Debug.Log(intArray.Length);
// 5

배열 복사

한 배열을 다른 배열에 복사하고 싶을 때 사용하는 것은 Array.Copy 함수이다

int intArray = new int[]{1, 2, 3, 4, 5};
int intArray2 = new int[5];
Array.Copy(intArray, intArray2, intArray2.Length);

for (int i = 0; i < intArray2.Length; i++)
	Debug.Log(intArray2[i]);

// 1 2 3 4 5 

사이즈를 얼만큼 복사할지 3번째 매개변수로 나타낼때 만약 위에서 4를 입력하면 1, 2, 3, 4, 0이 출력된다

배열 정렬

자료구조를 사용하다보면 정렬이 꼭 필요할 때가 온다. 그럴 때를 대비해 미리 연습해두자
숫자형 타입인 int, float 등은 정렬할 때 알아서 오름차순으로 정렬이 된다

int[] intArray2 = { 4, 2, 3, 1, 5 };
Array.Sort(intArray2);
for (int i = 0; i < intArray2.Length; i++)
	Debug.Log(intArray2[i]);

// 기본적으로 오름차순 정렬이되어 1 2 3 4 5 출력
// 내림차순은 뒤에 나올 Reverse를 통해 정렬한 다음 뒤집어주기

근데 개발하다보면 정렬을 숫자형 타입을 하는 일은 많지 않고 사용자가 정의한 클래스를 정렬해야 할 때가 온다. 클래스에 있는 특정 변수의 값을 기준으로 정렬하는 방법이 있는데 찾아본 바로 방법이 두 가지이다

  1. 클래스에 ICompare<T> 인터페이스를 상속받아 정렬 기준 함수를 선언하기
  2. 간단한 정렬이라면 람다 함수를 이용하기

내가 실제로 프로젝트에서 사용한 예제인데 다음과 같이 정렬했다

Array.Sort(allyFormation, (character1, character2) => {
	if(character1 == null && character2 == null)
    	return 0;
    else if(character1 == null)
    	return 1;
    else if(character2 == null)
    	return -1;
        
    return character1.RowOrder.CompareTo(character2.RowOrder);
});

캐릭터 클래스들이 있는 배열에서 RowOrder 값을 기준으로 정렬하는 람다 함수인데
사용자 클래스가 하나라도 null일 때 RowOrder 값을 비교하면 null 참조 에러가 나기 때문에 앞에서 조건문으로 null인 상황을 예외처리 해줘야 한다

캐릭터 두 개가 다 null이면 동등을 뜻하는 0을 반환하고 양수 값(1)을 반환하면 character2가 character1보다 앞에 있어야 함을 나타내고 음수 값을 반환하면 character1이 앞에 있어야 됨을 나타낸다

그 결과 null인 인스턴스들은 모두 뒤로 밀려나고 null이 아닌 인스턴스들은 RowOrder 값을 기준으로 오름차순 정렬된다

배열 반전

배열의 모든 요소들을 뒤집어서 순서를 바꾼다

Array.Reverse(intArray);

Array.IndexOf

배열의 특정 인덱스를 반환한다. 만약 요소가 없다면 -1을 반환함

int[] intArray = {1, 2, 3, 4, 5};
Debug.Log(Array.IndexOf(6);		// 6번째 인덱스 값을 불러오기
// 배열의 크기가 5이니까 6번째 인덱스 값은 없다. 그러므로 -1을 반환

배열 참조 시 null 에러를 방지하기 좋은 함수

Array.Clear

배열의 모든 인덱스를 기본값으로 초기화하는 함수

Clear(Array array, int index, int length);

array 배열의 index부터 시작해 index + length까지 기본값으로 초기화시킨다. index + length가 array의 length보다 크면 에러

Array.Resize

배열의 크기를 변경하는 함수. 새로운 배열을 생성해 기존 배열의 요소를 복사한 후 그 배열 반환

Array.Resize(ref intArray2, 10);
Debug.Log(intArray2.Length);	// 10

크기가 변경되고 나머지 부분은 기본값인 0으로 채워짐

Array.Exits

배열 내 특정 조건을 만족하는 요소가 있는 지 확인

int[] intArray = {1, 2, 3, 4, 5};
Debug.Log(Array.Exists(intArray2, value => value == 3));	// true

Array.Find && Array.FindAll

두 함수 모두 배열 내 특정 조건을 만족하는 요소를 찾는 함수

Find 함수는 조건을 만족하는 첫 번째 요소를 찾아 반환하고 FindAll은 조건을 만족하는 모든 요소를 배열로 만들어 반환한다

Debug.Log(Array.Find(intArray2, value => value >= 3));		// 3
Debug.Log(Array.FindAll(intArray2, value => value > 3).Length);		// 2

Array.BinarySearch

정렬된 배열에서 사용자가 이진 검색을 따로 구현할 필요 없이 이진 검색 알고리즘을 사용할 수 있는 함수이다. 정렬되어 있다는 가정하에 사용하면 순차 검색보다 좋은 성능을 만들 수 있을 것 같다

Debug.Log(Array.BinarySearch(intArray2, 4));	// 3

{1, 2, 3, 4, 5}에서 4에 해당하는 인덱스를 반환한다

Array.ForEach

for 문 등을 작성할 필요없이 배열의 각 요소를 순회하면서 특정 작업을 할 수 있는 함수
보통 람다문을 이용해 간단한 작업을 거침

Array.ForEach(intArray2, value => value++);

이렇게 하고 intArray2를 출력해보면 어떻게 될까?

아무런 변화없이 1, 2, 3, 4, 5가 출력된다. 그 이유는 foreach가 배열의 복사본을 사용하기 때문이다. 값의 변경 없이 무언가를 수행하는 함수만 실행할 때 써보자

언제 쓰면 좋을까

  1. 배열의 크기가 고정되어 있을 때
  2. 데이터가 자주 변경되지 않을 경우에
  3. 대량의 데이터를 다룰 때 연속된 메모리 블럭에 저장되어 있어서 메모리 사용량을 최소화할 수 있음
  4. 특정 데이터에 자주 접근하는 경우
profile
나태지옥

0개의 댓글