같은 규격을 가진 데이터를 차곡차곡 빈틈없이 이어져 있다
Insert, Remove 함수는 비용이 많이 든다. (메모리 사이즈를 다시 지정해 주어야 하기 때문에)
[ ] 연산자는 한번에 바로 접근이 가능하다.
TArray 유형은 두가지 프로퍼티로 정의 됩니다. (Element Type, Allocator) Eelement Type 은 배열에 저장되는 오브젝트 유형입니다. TArray 는 소위 동질성 컨테이너로, Element 모두 같은 Type이어야 합니다. Allocator 는 꽤 자주 생략되지만 기본값은 1 입니다. 메모리에 오브젝트가 레이아웃 되는 방식과 배열에 Element 를 추가하기 위해 배열을 키울 방식을 정의합니다. TArray 는 값 유형으로, int32 나 float 와 같은 다른 내장형과 비슷하게 취급해야 합니다. 확장을 염두해 두지 않았기에, TArray 인스턴스를 new 및 delete 로 생성 또는 소멸 시키는 것은 좋지 않습니다. 엘리먼트는 값 유형이기도 해서 배열을 소유합니다. TArray 의 소멸은 곧 거기 들어있는 앨리먼트의 소멸로 이어집니다. TArray 변수를 이용해 새로운 TArray 변수를 만들면, 그 앨리먼트를 새 변수에 복사하며, 공유된 상태는 없습니다.
Element Type 은 int32, FString, TSharedPtr 등과 같이 보통의 C++ 값 규칙에 따라 복사 및 소멸 가능한 값 유형은 어떤것이든 가능합니다. Allocator 가 지정되지 않았으니, TArray 는 기본 힙 기반 Allocator 를 사용합니다. 이 시점에서는 아직 할당된 메모리가 없습니다.
TArray<int32> IntArray;
TArray<UObject*> ObjectArr = {NewObject<???>(), NewObject<???>(), NewObject<???>()};
// 내부 순회
for( const auto ObjectElem : ObjectArr)
{
// ...
}
// 한번에 채우기
InitArray.Init(10,5) // {10,10,10,10,10}
// 배열 끝에 채우기
TArray<FString> StrArr;
StrArr.Add(TEXT("Hello")); // 엘리먼트유형의 인스턴스를 (TArry 밖에서 선언된 엘리먼트) 배열에 복사(또는 이동)합니다
StrArr.Emplace(TEXT("World")); // 지정한 인수를 사용하여, Element Type 과 동일한 인스턴스를 TArray 내부에서 생성합니다. 좀더 효율적이다. 복사하는 불필요한 절차를 피할수 있다.
// Emplace 가 Add 보다 효율이 더 좋지만, 가독성은 Add 가 좋다.
// For 문의 경우에서 int 같이 primitive 유형은 사소하지만 크기가 큰 구조체 같은경우 Emplace 를 고려하는것이 권장됩니다.
// 다수의 Element 한번에 추가
FString Arr[] = {TEXT("of"), TEXT("Tomorrow")};
StrArr.Append(Arr, ARRAY_COUNT(Arr));
// StrArr == {"Hello", "World", "of", "Tomorrow"};
// 검색 후 추가하기 ( 새로운 앨리먼트만 추가하는 경우 ) // 이경우 TSet 이 좋다.
StrArr.AddUnique(TEXT("!"));
// StrArr == {"Hello", "World", "of", "Tomorrow", "!"};
StrArr.AddUnique(TEXT("!"));
// StrArr == {"Hello", "World", "of", "Tomorrow", "!"}; // 이미 있는 값이라 추가되지 않는다
// 삽입. 단일 엘리먼트나 배열사본을, 주어진 인덱스에 추가시킵니다.
StrArr.Insert(TEXT("Brave"),1); // 1번 인덱스에 삽입
// StrArr == {"Hello", "Brave", "World", "of", "Tomorrow", "!"};
// 배열 크기 설정하기. SetNum 으로 설정한 크기가 , 현재 배열크기보다 크게 설정한다면, 기본생성자의 Element Type 으로 새로 만듭니다.
StrArr.SetNum(8);
// StrArr == {"Hello", "Brave", "World", "of", "Tomorrow", "!","",""};
// 배열 크기 설정하기. SetNum 으로 설정한 크기가 , 현재 배열크기보다 작게 설정한다면, 설정한 크기만큼 요소를 삭제합니다.
Str.SetNum(6);
// StrArr == {"Hello", "Brave", "World", "of", "Tomorrow", "!"};
// 그외 할당함수들
/*
AddUninitialized
초기화되지 않은 상태로 요소를 추가할 때 사용합니다.
이 방법은 성능상 이점이 있지만, 추가된 요소는 초기화 되지 않으므로 적절히 초기화 해줘야 합니다.
*/
TArray<uin8> MyArray;
int32 NumToAdd = 50; // 추가할 요소의 수
MyArray.AddUninitialized(NumToAdd);
// 필요시 요소를 초기화
for(int32 i = 0; i < NumToAdd ; ++i)
{
MyArray[i] = 0; // 예시 : 모든 요소를 0으로 초기화
}
/*
Reserve
배열의 용량을 미리 할당하여 여러번의 메모리 할당을 피할 수 있습니다.
배열의 크기는 변하지 않지만, 메모리를 미리 확보할 수 있습니다.
*/
Tarray<uint8> MyArray;
int32 ExpectedSize = 200; // 예상되는 최대 크기
MyArray.Reserve(ExpectedSize);
여러가지 방법이 있으나 c++ 의 범위기반 for 기능을 사용하는것을 추천합니다.
FString JoinedStr;
// 범위기반 for
for(auto& Str : StrArr)
{
JoinedStr += Str;
JoinedStr += Text(" ");
}
// JoinedStr == "Hello Brave World of Tomorrow ! ";
// 인덱스 기반 for
for(int32 Index = 0; Index != StrArr.Num(); ++Index)
{
JoinedStr += StrArr[Index];
JoinedStr += TEXT(" ");
}
// Iterator 를 사용한 For
// 세밀한 제어가 가능하다
// CreateIterator() 를 사용하면 일기 쓰기 가능
// CreateConstIterator() 를 사용하면 읽기 전용
for (auto Iter = StrArr.CreateConstIterator(); Iter; ++Iter)
{
JoinedStr += Iter;
JoinedStr += TEXT(" ");
}
operator[] 을 통해 입력한 인덱스에 해당하는 배열 Element 에 바로 접근할 수 있습니다. 또한 operator 는 레퍼런스를 반환 하므로, 배열이 const 가 아니라는 가정하에 배열 내 Element 를 변형시키는데 사용할 수도 있습니다.
FString Elem1 = StrArr[1];
// Elem1 == "of";
StrArr[3] == StrArr[3].ToUpper();
// StrArr == ["!","of","Brave","HELLO","World","Tomorrow"];
IsValidIndex() 함수를 사용하여 특정 인덱스에 대한 값이 유효한지 확인할 수 있습니다. 이는 유효하지 않은 인덱스에 접근하여 런타임중 에러가 발생하는것을 막아줍니다.
StrArr.IsValidIndex(-1); // false;
StrArr.IsValidIndex(0); // true;
StrArr.IsValidIndex(5); // true;
StrArr.IsValidIndex(6); // false;
Num 함수를 사용해서 배열에 몇개의 Element 가 있는지 확인할 수 있습니다.
int32 Count = StrArr.Num();
배열 메모리에 대한 직접접근을 위해, GetData 함수를 사용할 수 있습니다. 배열 내 Element 에 대한 포인터를 반환합니다. (배열명 은 배열의 주소를 가지고 있으니, 배열명 이라고도 볼 수 있겟다.) 이 포인터는 배열에 대한 변형 연상이 적용되기 전에만 유효 합니다. 컨테이너(여기선 TArray)가 const 인 경우, 반환되는 포인터 역시 const 입니다.
int32 Int32Arr[];
int32* Int32ArrPointer;
Int32Arr == Int32ArrPointer == TArray<int32>.GetData();
FString* StrPtr = StrArr.GetData();
// StrPtr[0] == "!";
// StrPtr[1] == "of";
// ...
// StrPtr[5] == "Tomorrow";
// StrPtr[6] // undefined behavior
FString ElemEnd = StrArr.Last(); // "Tomorrow"
FString ElemEnd0 = StrArr.Last(0); // "Tomorrow"
FString ElemEnd1 = StrArr.Last(1); // "World"
FString ElemTop = StrArr.Top(); // "Tomorrow"
배열요소 제거하는 방법도 여러가지가 있습니다.
TArray<int32> IntArr;
for(int32 i = 0 ; i <= 10 ; i++)
{
IntArr.Add(i); // {0,1,2,3,4,5,6,7,8,9,10} 이 들어갈 것이다.
}
// 조건에 맞는 요소를 모두 제거합니다.
IntArr.RemoveAll([](int32 Elem){
return Elem % 2 == 0; // 즉 짝수만 (true) 제거한다.
}
)
여러가지 정렬방식이 있지만, 사용하는 알고리즘에 따라 사용하는 정렬방식이 나뉩니다.
Element Type 이 가지고 있는
FString 의 경우 대소문자 구분없이 사전식 비교를 합니다.
// 일반정렬 . Element Type 이 가진 < 연산자를 사용하여 비교합니다.
StrArr.Sort();
// StrArr == {"!", "Brave", "Hello", "of", "Tomorrow", "World"}
// 일반정렬. 연산방법을 지정해 줄 수도 있습니다. 람다함수(익명함수) 사용
// StrArr.Sort([](){})
StrArr.Sort([](const FString& A, const FString& B){
return A.Len() < B.Len(); // 길이를 비교하여 참 거짓을 반환하도록, true 를 반환한다면 순서를 그대로, false 라면 순서를 바꿉니다.
})
// 힙정렬 . 람다표현식을 제공해도 되고 안해도 되고, 사용법은 Sort 와 같다.
StrArr.HeapSort([](const FString& A, const FString& B){
return A.Len() < B.Len();
})
// StrArr == {"!", "Brave", "Hello", "of", "Tomorrow", "World"}
// 병합정렬 . 람다표현식을 제공해도 되고 안해도 되고, 사용법은 Sort 와 같다.
StrArr.StableSort([](const FString& A, const FString& B){
return A.Len() < B.Len();
})
// StrArr == {"!", "Brave", "Hello", "of", "Tomorrow", "World"}
TArray<int32> Int32ArrCompare;
int32 CArray[] = {1,3,5,7,9,2,4,6,8,10};
// AddUninitialized 기존요소를 수정하지 않고 배열 끝에 초기화 되지 않은 Element 를 생성하는 함수
// 나중에 구성할 개체에 대한 메모리를 할당하는데 사용할 수 있다.
Int32ArrCompare.AddUninitialized(10)
// TArray 가 관리하고 있는 배열에 직접 접근하여, Memcpy 를 통해 배열의 정보 빠르게 복사
FMemory::Memcpy(Int32ArrCompare.GetData(), CArray, sizeof(int32)*ArrayNum);
알고리즘 라이브러리를 사용하여 합계 구하기
#include "Algo/Accumulate.h"
{
// 일반적으로는 for 구문으로 구한다
TArray<int32> IntArr = {1,2,3,4,5,6,7,8,9,10};
int32 Sum = 0;
for(const Int& Int32Elem : Int32Arr)
{
Sum += Int32Elem;
}
ensure(Sum == 55); // 맞는지 체크
// 하지만 알고리즘 라이브러리를 사용해서 구할수도 있다.
int32 SumByAlgo = Algo::Accumulate(Int32Arr, 0); // Accumulate 함수는 합을 구하는 알고리즘. 인수로 배열과 초기값을 넣어준다.
ensure(Sum == SumByAlgo); // 둘다 55 로 같다.
}