A classe de contêiner mais simples na Unreal Engine é TArray. TArray é responsável pela propriedade e organização de uma sequência de outros objetos (chamados de elementos) do mesmo tipo. Como uma TArray é uma sequência, seus elementos têm uma ordem bem definida e suas funções são usadas para manipular deterministicamente esses objetos e sua ordem.
TArray
TArray é a classe de contêiner mais comum na Unreal Engine. Ela é rápida, eficiente em memória e segura. Os tipos TArray são definidos por duas propriedades: tipo de elemento e um alocador opcional.
O tipo de elemento é o tipo dos objetos que serão armazenados na matriz. TArray é um contêiner homogêneo. Ou seja, todos os elementos são do mesmo tipo. Não é possível armazenar elementos de tipos diferentes em uma única TArray.
O alocador é omitido com frequência, e o padrão será um apropriado para a maioria dos casos de uso. Ela define como os objetos são dispostos na memória e como a matriz deve crescer para acomodar mais elementos. Há vários alocadores diferentes que você pode usar se decidir que o comportamento padrão não é para você, ou pode criar o seu próprio. Falaremos mais sobre isso depois.
TArray é um tipo de valor, o que significa que deve ser tratado como qualquer outro tipo integrado, como int32 ou float. Ele não foi projetada para ser estendida, e criar ou destruir instâncias de TArray com new e delete não é uma prática recomendada. Os elementos também são tipos de valor, e a matriz os possui. A destruição de uma TArray resultará na destruição de todos os elementos que ela ainda contém. Criar uma variável TArray a partir de outra copiará seus elementos para a nova variável. Não há um estado compartilhado.
Como criar e preencher uma matriz
Para criar uma matriz, defina-a assim:
TArray<int32> IntArray;Isso cria uma matriz vazia projetada para conter uma sequência de inteiros. O tipo de elemento pode ser qualquer tipo de valor copiável e destrutível de acordo com as regras normais de valor de C++, como int32, FString, TSharedPtr e assim por diante. Como nenhum alocador foi especificado, a TArray usará o alocador padrão baseado em heap. Nenhuma memória foi alocada até agora.
TArray (como muitos contêineres da Unreal Engine) assume que o tipo do elemento é deslocável de forma trivial, significando que os elementos podem ser movidos com segurança de uma posição na memória para outra copiando bytes brutos.
TArrays podem ser preenchidas de várias maneiras. Uma maneira é com a função Init, que preencherá uma matriz com várias cópias de um elemento:
IntArray.Init(10, 5);
// IntArray == [10,10,10,10,10]As funções Add e Emplace podem criar elementos no final da matriz:
TArray<FString> StrArr;
StrArr.Add (TEXT("Hello"));
StrArr.Emplace(TEXT("World"));
// StrArr == ["Hello","World"]O alocador da matriz fornece memória conforme necessário quando novos elementos são adicionados à matriz. O alocador padrão adiciona memória suficiente para vários elementos novos sempre que o tamanho atual da matriz é excedido. Add e Emplace fazem a mesma coisa, mas com uma diferença sutil:
Add(ouPush) copiará (ou moverá) uma instância do tipo de elemento para a matriz.Emplaceusará os argumentos fornecidos para construir uma nova instância do tipo de elemento.
No caso de TArray<FString>, Add criará uma FString temporária a partir do literal da string e moverá o conteúdo dessa FString temporária para uma nova FString dentro do contêiner. Já Emplace criará uma FString diretamente usando o literal da string. O resultado final é o mesmo, mas Emplace evita a criação de uma variável temporária, o que geralmente é indesejável para tipos de valor não triviais como FString.
Em geral, Emplace é preferível a Add, pois evita a criação de variáveis temporárias desnecessárias no local de chamada que são copiadas ou movidas para o contêiner. Como regra geral, use Add para tipos triviais e Emplace nos outros casos. Emplace nunca será menos eficiente que Add, mas Add pode ser mais fácil de ler.
Append adiciona vários elementos de uma vez de outra TArray ou de um ponteiro para uma matriz C regular e o tamanho dessa matriz:
FString Arr[] = { TEXT("of"), TEXT("Tomorrow") };
StrArr.Append(Arr, ARRAY_COUNT(Arr));
// StrArr == ["Hello","World","of","Tomorrow"]AddUnique só adiciona um elemento ao recipiente se não houver um elemento equivalente. A equivalência é verificada usando o operador == do tipo de elemento:
StrArr.AddUnique(TEXT("!"));
// StrArr == ["Hello","World","of","Tomorrow","!"]
StrArr.AddUnique(TEXT("!"));
// StrArr is unchanged as "!" is already an elementInsert, como Add, Emplace e Append, adiciona um único elemento ou uma cópia de uma matriz de elementos em um determinado índice:
StrArr.Insert(TEXT("Brave"), 1);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!"]A função SetNum pode definir diretamente o número de elementos da matriz, com novos elementos sendo criados usando o constructor padrão do tipo de elemento se o novo número for maior que o atual:
StrArr.SetNum(8);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!","",""]SetNum também removerá elementos se o novo número for menor que o atual. Informações mais detalhadas sobre a remoção de elementos serão fornecidas posteriormente:
StrArr.SetNum(6);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!"]Iteração
Existem várias maneiras de iterar sobre os elementos da sua matriz, mas a maneira recomendada é usar a funcionalidade ranged-for do C++:
FString JoinedStr;
for (auto& Str : StrArr)
{
JoinedStr += Str;
JoinedStr += TEXT(" ");
}
// JoinedStr == "Hello Brave World of Tomorrow ! "A iteração regular baseada em índice também é possível:
for (int32 Index = 0; Index != StrArr.Num(); ++Index)
{
JoinedStr += StrArr[Index];
JoinedStr += TEXT(" ");
}Por fim, as matrizes também têm seu próprio tipo de iterador para mais controle sobre a iteração. Há duas funções chamadas CreateIterator e CreateConstIterator que podem ser usadas para acesso de leitura/gravação ou somente leitura aos elementos, respectivamente:
for (auto It = StrArr.CreateConstIterator(); It; ++It)
{
JoinedStr += *It;
JoinedStr += TEXT(" ");
}Classificação
Para classificar as matrizes, basta chamar a função Sort:
StrArr.Sort();
// StrArr == ["!","Brave","Hello","of","Tomorrow","World"]Aqui, os valores são classificados por meio do operator< do tipo de elemento. No caso da FString, é uma comparação lexicográfica que não diferencia maiúsculas de minúsculas. Um predicado binário também pode ser implementado para fornecer semântica de ordenação diferente, como esta:
StrArr.Sort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
});
// StrArr == ["!","of","Hello","Brave","World","Tomorrow"]Agora, as strings estão classificadas de acordo com o tamanho. Observe como as três strings com o mesmo comprimento ("Hello", "Brave" e "World") mudaram de ordem em relação às suas posições anteriores na matriz. Isso ocorre porque Sort é instável, e a ordem relativa de elementos equivalentes (as strings são equivalentes aqui porque o predicado só compara o comprimento) não é garantida. Sort é implementada como uma classificação rápida.
A função HeapSort, com ou sem um predicado binário, pode ser usada para realizar uma classificação de heap. O uso ou não dessa função dependerá dos seus dados específicos e da eficiência de classificação em comparação com a função Sort. Como Sort, HeapSort não é estável. Se tivéssemos usado HeapSort em vez de Sort acima, este seria o resultado (o mesmo, neste caso):
StrArr.HeapSort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
});
// StrArr == ["!","of","Hello","Brave","World","Tomorrow"]Por fim, StableSort pode ser usada para garantir a ordem relativa de elementos equivalentes após a classificação. Se tivéssemos chamado StableSort em vez de Sort ou HeapSort acima, o resultado seria este:
StrArr.StableSort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
});
// StrArr == ["!","of","Brave","Hello","World","Tomorrow"]Ou seja, "Brave", "Hello" e "World" permanecem na mesma ordem relativa após terem sido classificados lexicograficamente. StableSort é implementada como uma classificação de mesclagem.
Consultas
Podemos perguntar à matriz quantos elementos ela contém usando a função Num:
int32 Count = StrArr.Num();
// Count == 6Se precisar de acesso direto à memória da matriz, talvez para interoperabilidade com uma API ao estilo C, você pode usar a função GetData para retornar um ponteiro para os elementos na matriz. Esse ponteiro só é válido enquanto a matriz existir e antes de qualquer operação de mutação ser feita à matriz. Apenas os primeiros índices Num de StrPtr são desreferenciáveis:
FString* StrPtr = StrArr.GetData();
// StrPtr[0] == "!"
// StrPtr[1] == "of"
// ...
// StrPtr[5] == "Tomorrow"
// StrPtr[6] - undefined behaviorSe o contêiner for constante, o ponteiro retornado também será constante.
Você também pode perguntar ao contêiner qual é o tamanho dos elementos:
uint32 ElementSize = StrArr.GetTypeSize();
// ElementSize == sizeof(FString)Para recuperar elementos, você pode usar o operator[] de indexação e passar um índice base zero para o elemento desejado:
FString Elem1 = StrArr[1];
// Elem1 == "of"Passar um índice inválido — menor que 0 ou maior ou igual a Num() — causará um runtime error. Você pode perguntar ao contêiner se um índice específico é válido usando a função IsValidIndex:
bool bValidM1 = StrArr.IsValidIndex(-1);
bool bValid0 = StrArr.IsValidIndex(0);
bool bValid5 = StrArr.IsValidIndex(5);
bool bValid6 = StrArr.IsValidIndex(6);
// bValidM1 == false
// bValid0 == true
// bValid5 == true
// bValid6 == falseoperator[] retorna uma referência, portanto também pode ser usado para modificar os elementos dentro da matriz, desde que sua matriz não seja constante:
StrArr[3] = StrArr[3].ToUpper();
// StrArr == ["!","of","Brave","HELLO","World","Tomorrow"]Como a função GetData, operator[] retornará uma referência constante se a matriz for constante. Você também pode indexar do final da matriz para invertido usando a função Last. O padrão do índice é zero. A função Top é sinônimo de Last, mas não usa um índice:
FString ElemEnd = StrArr.Last();
FString ElemEnd0 = StrArr.Last(0);
FString ElemEnd1 = StrArr.Last(1);
FString ElemTop = StrArr.Top();
// ElemEnd == "Tomorrow"
// ElemEnd0 == "Tomorrow"
// ElemEnd1 == "World"
// ElemTop == "Tomorrow"Podemos perguntar à matriz se ela contém um determinado elemento:
bool bHello = StrArr.Contains(TEXT("Hello"));
bool bGoodbye = StrArr.Contains(TEXT("Goodbye"));
// bHello == true
// bGoodbye == falseOu perguntar à matriz se ela contém um elemento que corresponde a um predicado específico:
bool bLen5 = StrArr.ContainsByPredicate([](const FString& Str){
return Str.Len() == 5;
});
bool bLen6 = StrArr.ContainsByPredicate([](const FString& Str){
return Str.Len() == 6;
});
// bLen5 == true
// bLen6 == falsePodemos encontrar elementos usando a família de funções Find. Para verificar se um elemento existe e retornar seu índice, usamos Find:
int32 Index;
if (StrArr.Find(TEXT("Hello"), Index))
{
// Index == 3
}Isso define Index como o índice do primeiro elemento encontrado. Se houver elementos duplicados e quisermos encontrar o índice do último elemento, usamos a função FindLast:
int32 IndexLast;
if (StrArr.FindLast(TEXT("Hello"), IndexLast))
{
// IndexLast == 3, because there aren't any duplicates
}Ambas as funções retornam um booleano para indicar se um elemento foi encontrado ou não, além de gravar o índice desse elemento em uma variável quando for encontrado.
Find e FindLast também podem retornar um índice de elementos diretamente. Isso acontecerá se você não passar o índice como um argumento explícito. Isso pode ser mais sucinto do que a função acima, e a função que você usa depende do que melhor se adapta às suas necessidades ou estilo específicos.
Se nenhum elemento for encontrado, o valor especial INDEX_NONE será retornado:
int32 Index2 = StrArr.Find(TEXT("Hello"));
int32 IndexLast2 = StrArr.FindLast(TEXT("Hello"));
int32 IndexNone = StrArr.Find(TEXT("None"));
// Index2 == 3
// IndexLast2 == 3
// IndexNone == INDEX_NONEIndexOfByKey funciona de maneira semelhante, mas permite a comparação dos elementos com um objeto arbitrário. Com as funções Find, o argumento é convertido no tipo de elemento (FString, nesse caso) antes do início da pesquisa. Com IndexOfByKey, a chave é comparada diretamente, permitindo pesquisas mesmo quando o tipo de chave não é conversível diretamente no tipo de elemento.
IndexOfByKey funciona para qualquer tipo de chave para o qual existe operator==(ElementType, KeyType). IndexOfByKey retornará o índice do primeiro elemento encontrado ou INDEX_NONE se nenhum elemento for encontrado:
int32 Index = StrArr.IndexOfByKey(TEXT("Hello"));
// Index == 3A função IndexOfByPredicate encontra o índice do primeiro elemento que corresponde ao predicado especificado, retornando novamente o valor especial INDEX_NONE se nenhum for encontrado:
int32 Index = StrArr.IndexOfByPredicate([](const FString& Str){
return Str.Contains(TEXT("r"));
});
// Index == 2Em vez de retornar índices, podemos retornar ponteiros aos elementos que encontramos. FindByKey funciona como IndexOfByKey, comparando os elementos a um objeto arbitrário, mas retornando um ponteiro ao elemento encontrado. Se não encontrar um elemento, retornará nullptr:
auto* OfPtr = StrArr.FindByKey(TEXT("of")));
auto* ThePtr = StrArr.FindByKey(TEXT("the")));
// OfPtr == &StrArr[1]
// ThePtr == nullptrVocê pode usar FindByPredicate como IndexOfByPredicate, mas ele retorna um ponteiro em vez de um índice:
auto* Len5Ptr = StrArr.FindByPredicate([](const FString& Str){
return Str.Len() == 5;
});
auto* Len6Ptr = StrArr.FindByPredicate([](const FString& Str){
return Str.Len() == 6;
});
// Len5Ptr == &StrArr[2]
// Len6Ptr == nullptrPor fim, você pode recuperar uma matriz de elementos que correspondem a um predicado específico com a função FilterByPredicate:
auto Filter = StrArray.FilterByPredicate([](const FString& Str){
return !Str.IsEmpty() && Str[0] < TEXT('M');
});Remoção
Você pode apagar elementos da matriz usando a família de funções Remove. A função Remove remove todos os elementos considerados iguais ao elemento fornecido, de acordo com a função operator== do tipo de elemento. Por exemplo:
TArray<int32> ValArr;
int32 Temp[] = { 10, 20, 30, 5, 10, 15, 20, 25, 30 };
ValArr.Append(Temp, ARRAY_COUNT(Temp));
// ValArr == [10,20,30,5,10,15,20,25,30]
ValArr.Remove(20);
// ValArr == [10,30,5,10,15,25,30]Você também pode usar RemoveSingle para apagar o primeiro elemento correspondente na matriz. Isso é útil se você sabe que a matriz pode conter duplicatas e deseja apagar apenas uma ou como uma otimização se souber que a matriz só pode conter um elemento correspondente:
ValArr.RemoveSingle(30);
// ValArr == [10,5,10,15,25,30]Também podemos remover elementos pelo índice baseado em zero usando a função RemoveAt. Você pode usar IsValidIndex para verificar se a matriz tem um elemento com o índice que você planeja fornecer, pois passar um índice inválido para essa função causará um runtime error:
ValArr.RemoveAt(2); // Removes the element at index 2
// ValArr == [10,5,15,25,30]
ValArr.RemoveAt(99); // This will cause a runtime error as
// there is no element at index 99Também podemos remover elementos que correspondem a um predicado usando a função RemoveAll. Por exemplo, a remoção de todos os valores múltiplos de 3:
ValArr.RemoveAll([](int32 Val) {
return Val % 3 == 0;
});
// ValArr == [10,5,25]Em todos os casos, quando os elementos foram removidos, os elementos seguintes foram embaralhados em índices menores, pois nunca pode haver lacunas na matriz.
O processo de embaralhar tem uma sobrecarga. Se não houver interesse na ordem em que os elementos restantes são deixados, essa sobrecarga pode ser reduzida usando as funções RemoveSwap, RemoveAtSwap e RemoveAllSwap, que funcionam como as variantes sem troca, exceto que não garantem a ordem dos elementos restantes, permitindo que concluam suas tarefas mais rapidamente:
TArray<int32> ValArr2;
for (int32 i = 0; i != 10; ++i)
ValArr2.Add(i % 5);
// ValArr2 == [0,1,2,3,4,0,1,2,3,4]
ValArr2.RemoveSwap(2);
// ValArr2 == [0,1,4,3,4,0,1,3]
ValArr2.RemoveAtSwap(1);
// ValArr2 == [0,3,4,3,4,0,1]
Por fim, a função Empty removerá tudo da matriz:
ValArr2.Empty();
// ValArr2 == []Operadores
Matrizes são tipos de valor regulares e, como tal, podem ser copiadas pelo constructor padrão ou pelo operador de atribuição. Como as matrizes são proprietárias dos seus elementos, copiar uma matriz é profundo e, portanto, a nova matriz terá sua própria cópia dos elementos:
TArray<int32> ValArr3;
ValArr3.Add(1);
ValArr3.Add(2);
ValArr3.Add(3);
auto ValArr4 = ValArr3;
// ValArr4 == [1,2,3];
ValArr4[0] = 5;
// ValArr3 == [1,2,3];
// ValArr4 == [5,2,3];Como alternativa à função Append, você pode concatenar matrizes com operator+=:
ValArr4 += ValArr3;
// ValArr4 == [5,2,3,1,2,3]TArray também é compatível com semântica de movimento, que pode ser invocada usando a função MoveTemp. Após uma movimentação, a matriz de origem ficará vazia:
ValArr3 = MoveTemp(ValArr4);
// ValArr3 == [5,2,3,1,2,3]
// ValArr4 == []Matrizes podem ser comparadas usando operator== e operator!=. A ordem dos elementos é importante. Duas matrizes apenas serão iguais se tiverem o mesmo número de elementos na mesma ordem. Os elementos são comparados usando o seu próprio operator==:
TArray<FString> FlavorArr1;
FlavorArr1.Emplace(TEXT("Chocolate"));
FlavorArr1.Emplace(TEXT("Vanilla"));
// FlavorArr1 == ["Chocolate","Vanilla"]
auto FlavorArr2 = Str1Array;
// FlavorArr2 == ["Chocolate","Vanilla"]
bool bComparison1 = FlavorArr1 == FlavorArr2;
// bComparison1 == true
Heap
TArray tem funções compatíveis com uma estrutura de dados de heap binários. Um heap é um tipo de árvore binário em que qualquer nó pai é equivalente ou ordenado antes de todos os nós filho. Quando implementado como uma matriz, o nó-raiz da árvore está no elemento 0 e os índices dos nós filho esquerdo e direito de um nó no índice N são 2N+1 e 2N+2, respectivamente. Os filhos não estão em uma ordem específica em relação uns aos outros.
Qualquer matriz existente pode ser transformada em um heap chamando a função Heapify. Isso é sobrecarregado para receber um predicado ou não, onde a versão não predicada usará o operator< do tipo do elemento para determinar a ordenação:
TArray<int32> HeapArr;
for (int32 Val = 10; Val != 0; --Val)
{
HeapArr.Add(Val);
}
// HeapArr == [10,9,8,7,6,5,4,3,2,1]
HeapArr.Heapify();
// HeapArr == [1,2,4,3,6,5,8,10,7,9]Esta é uma visualização da árvore:
Os nós na árvore podem ser lidos da esquerda para a direita e de cima para baixo como a ordem dos elementos na matriz heapified. Observe que a matriz não é necessariamente classificada após ser transformada em um heap. Embora uma matriz classificada também seja um heap válido, a definição da estrutura de heap é flexível o suficiente para permitir vários heaps válidos para o mesmo conjunto de elementos.
Novos elementos podem ser adicionados ao heap por meio da função HeapPush, reorganizando outros nós para manter o heap:
HeapArr.HeapPush(4);
// HeapArr == [1,2,4,3,4,5,8,10,7,9,6]As funções HeapPop e HeapPopDiscard são usadas para remover o nó superior do heap. A diferença entre as duas é que a primeira usa uma referência a um tipo de elemento para retornar uma cópia do elemento superior, e a segunda simplesmente remove o nó superior sem retorná-lo. Ambas as funções resultam na mesma alteração na matriz, e o heap é mantido novamente ao reordenar outros elementos de forma adequada:
int32 TopNode;
HeapArr.HeapPop(TopNode);
// TopNode == 1
// HeapArr == [2,3,4,6,4,5,8,10,7,9]HeapRemoveAt removerá um elemento da matriz em um determinado índice e reorganizará os elementos para manter o heap:
HeapArr.HeapRemoveAt(1);
// HeapArr == [2,4,4,6,9,5,8,10,7]HeapPush, HeapPop, HeapPopDiscard e HeapRemoveAt só devem ser chamadas quando a estrutura já for um heap válido, como após uma chamada Heapify, qualquer outra operação de heap ou a manipulação manual da matriz em um heap.
Cada uma dessas funções, incluindo Heapify, pode usar um predicado binário opcional para determinar a ordem dos elementos do nó no heap. Por padrão, as operações de heap usam o operator< do tipo de elemento para determinar a ordem. Ao usar um predicado personalizado, é importante usar o mesmo predicado em todas as operações de heap.
Por fim, o nó superior do heap pode ser inspecionado usando HeapTop sem alterar a matriz:
int32 Top = HeapArr.HeapTop();
// Top == 2Slack
Como matrizes podem ser redimensionadas, elas usam uma quantidade variável de memória. Para evitar a realocação todas as vezes que elementos são adicionados, os alocadores costumam fornecer mais memória do que foi solicitado. Dessa forma, chamadas Add futuras não prejudicarão o desempenho para realocação. Da mesma forma, remover elementos geralmente não libera memória. Isso deixa a matriz com elementos slack, que são slots de armazenamento de elementos pré-alocados que não estão em uso no momento. A quantidade de slack em uma matriz é definida como a diferença entre o número de elementos armazenados na matriz e o número de elementos que essa matriz poderia armazenar com a quantidade de memória alocada.
Como uma matriz construída por padrão não aloca memória, o slack será inicialmente zero. Você pode descobrir quanto slack existe em uma matriz usando a função GetSlack. O número máximo de elementos que a matriz pode conter antes da realocação do contêiner pode ser obtido pela função Max. GetSlack é equivalente à diferença entre Max e Num:
TArray<int32> SlackArray;
// SlackArray.GetSlack() == 0
// SlackArray.Num() == 0
// SlackArray.Max() == 0
SlackArray.Add(1);
// SlackArray.GetSlack() == 3
// SlackArray.Num() == 1
// SlackArray.Max() == 4
A quantidade de slack em um contêiner após uma realocação é decidida pelo alocador. Portanto, os usuários não devem esperar que o slack permaneça constante.
Embora o gerenciamento do slack não seja obrigatório, você pode usá-lo a seu favor para fornecer dicas de otimização à matriz. Por exemplo, se você sabe que está prestes a adicionar 100 elementos à matriz, certifique-se de ter um slack de pelo menos 100 antes de adicionar, para que a matriz não precise alocar memória ao adicionar os novos elementos. A função Empty, mencionada acima, usa um argumento slack opcional:
SlackArray.Empty();
// SlackArray.GetSlack() == 0
// SlackArray.Num() == 0
// SlackArray.Max() == 0
SlackArray.Empty(3);
// SlackArray.GetSlack() == 3
// SlackArray.Num() == 0
// SlackArray.Max() == 3
SlackArray.Add(1);
SlackArray.Add(2);
Há uma função Reset que funciona de forma semelhante a Empty, mas não liberará memória se o slack solicitado já estiver fornecido pela alocação atual. No entanto, ela alocará mais memória se o slack solicitado for maior:
SlackArray.Reset(0);
// SlackArray.GetSlack() == 3
// SlackArray.Num() == 0
// SlackArray.Max() == 3
SlackArray.Reset(10);
// SlackArray.GetSlack() == 10
// SlackArray.Num() == 0
// SlackArray.Max() == 10Por fim, você pode remover todo o slack com a função Shrink, que redimensionará a alocação para o tamanho mínimo necessário para manter os elementos atuais. Shrink não afeta os elementos na matriz:
SlackArray.Add(5);
SlackArray.Add(10);
SlackArray.Add(15);
SlackArray.Add(20);
// SlackArray.GetSlack() == 6
// SlackArray.Num() == 4
// SlackArray.Max() == 10
SlackArray.Shrink();
// SlackArray.GetSlack() == 0
// SlackArray.Num() == 4
Memória bruta
TArray é apenas um agrupador de memória alocada. Pode ser útil fazer isso modificando os bytes da alocação e criando os elementos por conta própria. TArray sempre tentará fazer o melhor possível com as informações que tem, mas às vezes pode ser necessário descer para um nível inferior.
As funções a seguir fornecem acesso rápido e de baixo nível a TArray e aos dados que ela contém. No entanto, se usadas de forma inadequada, podem colocar o contêiner em estados inválidos e causar um comportamento indefinido. Cabe a você retornar o contêiner a um estado válido após invocar essas funções, mas antes de qualquer outra função regular ser chamada.
As funções AddUninitialized e InsertUninitialized adicionarão um espaço não inicializado à matriz. Eles funcionam como as funções Add e Insert, respectivamente, mas não fazem a chamada do constructor do tipo de elemento. Isso pode ser útil para evitar chamar constructors. Você pode fazer isso em casos como o exemplo a seguir, em que planeja sobrescrever todo o struct com uma chamada Memcpy:
int32 SrcInts[] = { 2, 3, 5, 7 };
TArray<int32> UninitInts;
UninitInts.AddUninitialized(4);
FMemory::Memcpy(UninitInts.GetData(), SrcInts, 4*sizeof(int32));
// UninitInts == [2,3,5,7]Você também pode usar essa funcionalidade para reservar memória para objetos que planeja construir por conta própria:
TArray<FString> UninitStrs;
UninitStrs.Emplace(TEXT("A"));
UninitStrs.Emplace(TEXT("D"));
UninitStrs.InsertUninitialized(1, 2);
new ((void*)(UninitStrs.GetData() + 1)) FString(TEXT("B"));
new ((void*)(UninitStrs.GetData() + 2)) FString(TEXT("C"));
// UninitStrs == ["A","B","C","D"]AddZeroed e InsertZeroed funcionam de maneira semelhante, mas também zeram os bytes do espaço adicionado/inserido:
struct S
{
S(int32 InInt, void* InPtr, float InFlt)
: Int(InInt)
, Ptr(InPtr)
, Flt(InFlt)
{
}
int32 Int;
void* Ptr;
Também existem as funções SetNumUninitialized e SetNumZeroed, que funcionam como SetNum. No entanto, se o novo número for maior que o atual, o espaço para os novos elementos não inicializará ou será zerado bit a bit, respectivamente. Assim como com as funções AddUninitialized e InsertUninitialized, certifique-se de garantir que os novos elementos sejam construídos corretamente no novo espaço se precisarem ser:
SArr.SetNumUninitialized(3);
new ((void*)(SArr.GetData() + 1)) S(5, (void*)0x12345678, 3.14);
new ((void*)(SArr.GetData() + 2)) S(2, (void*)0x87654321, 2.72);
// SArr == [
// { Int: 0, Ptr: nullptr, Flt: 0.0f },
// { Int: 5, Ptr: 0x12345678, Flt: 3.14f },
// { Int: 2, Ptr: 0x87654321, Flt: 2.72f }
// ]
SArr.SetNumZeroed(5);
Use as famílias de funções "Uninitialized" e "Zeroed" com cuidado. Se um tipo de elemento incluir um membro que precisa de construção, ou que não tem um estado bit a bit válido zerado, isso poderá resultar em elementos de matriz inválidos e comportamento indefinido. Essas funções são muito úteis em matrizes de tipos que provavelmente nunca mudarão, como FMatrix ou FVector.
Variado
A função BulkSerialize é uma função de serialização que pode ser usada como um operator<< alternativo para serializar a matriz como um bloco de bytes brutos, em vez da serialização por elemento. Isso pode melhorar o desempenho com elementos triviais, como um tipo integrado ou um struct de dados simples.
As funções CountBytes e GetAllocatedSize são usadas para estimar quanta memória está sendo usada pela matriz no momento. CountBytes usa um FArchive e GetAllocatedSize pode ser chamada diretamente. Essas funções costumam ser usadas para relatórios de estatísticas.
As funções Swap e SwapMemory usam dois índices e trocarão o valor dos elementos nesses índices. Eles são equivalentes, mas Swap faz uma verificação adicional de erros nos índices e ativa se os índices estiverem fora de alcance.