天天看點

【UE4 C++ 基礎知識】<5> 容器——TArray概述建立添加元素疊代排序查詢移除元素運算符堆Slack原始記憶體其他參考

概述

  • TArray 是UE4中最常用的容器類。其速度快、記憶體消耗小、安全性高。
  • 其設計時未考慮擴充問題,是以建議在實際操作中勿使用 建立(new) 和 删除(delete) 建立或銷毀 TArray 執行個體
  • TArray元素為數值類型時,被銷毀時其中的元素也将被銷毀。若在另一TArray中建立TArray變量,其元素将複制到新變量中,且不會共享狀态。

建立

TArray<int32> IntArray;
           

添加元素

  • init

     填充多個相同值
    IntArray.Init(10, 5); //==>[10,10,10,10,10]
               
  • Add

     可添加重複元素,添加時會建立臨時變量再複制
  • AddUnique

     不可添加重複元素
  • Emplace

     添加時不會建立臨時變量,性能好于Add
    TArray<FString> StrArr;
    StrArr.Add(TEXT("Hello"));
    IntArray.AddUnique(TEXT("Hello"));
    StrArr.Emplace(TEXT("World")); //==>["Hello","World"]
               
  • Append

     可一次性添加其他 TArray 中的多個元素,,或者指向正常C數組的指針及該數組的大小
    FString Arr[] = { TEXT("of"), TEXT("Tomorrow") };
    StrArr.Append(Arr, ARRAY_COUNT(Arr)); //==>["Hello","World","of","Tomorrow"]
               
  • Insert

     在給定索引處添加單個元素或元素數組的副本
    StrArr.Insert(TEXT("Brave"), 1); //==>["Hello","Brave","World","of","Tomorrow","!"]
               
  • SetNum

     函數可直接設定數組元素的數量。
    • 如新數量大于目前數量,則使用元素類型的預設構造函數建立元素
    • 如新數量小于目前數量,SetNum 将移除元素。
    StrArr.SetNum(8); //==>["Hello","Brave","World","of","Tomorrow","!","",""]
    
    StrArr.SetNum(6); //==>["Hello","Brave","World","of","Tomorrow","!"]
               

疊代

  • 範圍-for(ranged-for)

    功能
    FString JoinedStr;
    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(" ");
    }
               
  • 疊代器

    函數 CreateIterator 和 CreateConstIterator 可分别用于元素的讀寫和隻讀通路
    for (auto It = StrArr.CreateConstIterator(); It; ++It)
    {
    	JoinedStr += *It;
    	JoinedStr += TEXT(" ");
    }
               

排序

  • Sort

    StrArr.Sort(); //==>["!","Brave","Hello","of","Tomorrow","World"]
               
  • 二進制謂詞提供不同的排序語意
    StrArr.Sort([](const FString& A, const FString& B){
    		return A.len() < B.len();
    	}); //按字元串長度排序, ==>["!","of","Hello","Brave","World","Tomorrow"]
               
  • HeapSort

    無論帶或不帶二進制謂詞,均可用于執行對排序。是否選擇使用它則取決于特定資料和與 Sort 函數之間的排序效率對比。和 Sort 一樣,HeapSort 并不穩定
    StrArr.HeapSort([](const FString& A, const FString& B) {
    	return A.Len() < B.Len();
    }); //==>["!","of","Hello","Brave","World","Tomorrow"]
               
  • StableSort

     可在排序後保證等值元素的相對排序。StableSort 作為歸并排序實作
    StrArr.StableSort([](const FString& A, const FString& B) {
    	return A.Len() < B.Len();
    }); //==>["!","of","Brave","Hello","World","Tomorrow"]
               

查詢

  • Num

     查詢元素數量
    int32 Count = StrArr.Num();  // Count == 6
               
  • GetData

     函數傳回指向數組中元素的指針,該操作直接通路數組記憶體。

    僅在數組存在且未執行更改數組的操作時,此指針方有效。僅 StrPtr 的首個 Num 指數才可被解除引用

    FString* StrPtr = StrArr.GetData();
    // StrPtr[0] == "!"
    // StrPtr[1] == "of"
    // ...
    // StrPtr[5] == "Tomorrow"
    // StrPtr[6] - undefined behavior
               
  • GetTypeSize 擷取單個元素的大小
    uint32 ElementSize = StrArr.GetTypeSize(); // ElementSize == sizeof(FString)
               
  • []

     索引運算符擷取元素,傳回的是一個引用,可用于操作數組中的元素(假定數組不為常量):
    FString Elem1 = StrArr[1]; // Elem1 == "of"
    
    StrArr[3] = StrArr[3].ToUpper(); //==>["!","of","Brave","HELLO","World","Tomorrow"]
               
  • 使用 IsValidIndex 函數詢問容器,可确定特定索引是否有效(0≤=索引<Num())
    bool bValidM1 = StrArr.IsValidIndex(-1);// bValidM1 == false
    bool bValid0  = StrArr.IsValidIndex(0); // bValid0  == true
    bool bValid5  = StrArr.IsValidIndex(5); // bValid5  == true
    bool bValid6  = StrArr.IsValidIndex(6); // bValid6  == false
               
  • Last

     函數從數組末端反向索引,索引預設為零。
  • Top

     傳回最後一個元素,不接受索引
    FString ElemEnd  = StrArr.Last();  // ElemEnd  == "Tomorrow"
    FString ElemEnd0 = StrArr.Last(0); // ElemEnd0 == "Tomorrow"
    FString ElemEnd1 = StrArr.Last(1); // ElemEnd1 == "World"
    FString ElemTop  = StrArr.Top();   // ElemTop  == "Tomorrow"
               
  • Contains

     查詢是否包含特定元素
    bool bHello   = StrArr.Contains(TEXT("Hello"));   // bHello   == true
    bool bGoodbye = StrArr.Contains(TEXT("Goodbye")); // bGoodbye == false
               
  • ContainsByPredicate

     查詢是否包含與特定謂詞比對的元素
    bool bLen5 = StrArr.ContainsByPredicate([](const FString& Str){
    	return Str.Len() == 5;
    }); // bLen5 == true
    
    bool bLen6 = StrArr.ContainsByPredicate([](const FString& Str){
    	return Str.Len() == 6;
    }); // bLen6 == false
               
  • Find

     确定元素是否存在并傳回找到的首個元素的索引
    int32 Index;
    if (StrArr.Find(TEXT("Hello"), Index)){  
    	// Index == 3
    }  
    
    int32 Index2 = StrArr.Find(TEXT("Hello"));    // Index2 == 3
    int32 IndexNone  = StrArr.Find(TEXT("None")); // IndexNone == INDEX_NONE(實質上是-1)
               
  • FindLast

     确定元素是否存在并傳回找到的最末元素的索引
    int32 IndexLast;
    if (StrArr.FindLast(TEXT("Hello"), IndexLast)){
    	// IndexLast == 3, because there aren't any duplicates
    }
    
    int32 IndexLast2 = StrArr.FindLast(TEXT("Hello")); // IndexLast2 == 3
               
  • IndexOfByKey

     傳回首個比對到的元素的索引;如果沒有找到元素,則傳回INDEX_NONE
    • IndexOfByKey 工作方式相似,但允許元素與任意對象進行對比。通過Find函數進行的搜尋開始前,參數将被實際轉換為元素類型(此例中的FString)。使用IndexOfByKey ,則直接對”鍵”進行對比,以便在鍵類型無法直接轉換到元素類型時照常進行搜尋。
    • IndexOfByKey 可用于運算符 == (ElementType、KeyType)存在的任意鍵類型;然後這将被用于執行比較。
    int32 Index = StrArr.IndexOfByKey(TEXT("Hello")); // Index == 3
               
  • IndexOfByPredicate

     函數用于查找與特定謂詞比對的首個元素的索引;如未找到,同樣傳回特殊 INDEX_NONE 值:
    int32 Index = StrArr.IndexOfByPredicate([](const FString& Str){
    	return Str.Contains(TEXT("r"));
    }); // Index == 2
               
  • FindByKey

     可以将元素和任意對象對比,并傳回首個比對到的元素的指針,如果未比對到,則傳回nullptr
    auto* OfPtr  = StrArr.FindByKey(TEXT("of"))); // OfPtr  == &StrArr[1]
    auto* ThePtr = StrArr.FindByKey(TEXT("the"))); // ThePtr == nullptr
               
  • FindByPredicate

     的使用方式和IndexOfByPredicate相似,不同的是,它的傳回值是指針,而不是索引
    auto* Len5Ptr = StrArr.FindByPredicate([](const FString& Str){
    	return Str.Len() == 5;
    }); // Len5Ptr == &StrArr[2]
    
    auto* Len6Ptr = StrArr.FindByPredicate([](const FString& Str){
    	return Str.Len() == 6;
    }); // Len6Ptr == nullptr
               
  • FilterByPredicate

     函數可擷取與特定謂詞比對的元素數組
    auto Filter = StrArray.FilterByPredicate([](const FString& Str){
    	return !Str.IsEmpty() && Str[0] < TEXT('M');
    });
               

移除元素

  • Remove

     函數族用于移除數組中的元素。
    TArray<int32> ValArr;
    int32 Temp[] = { 10, 20, 30, 5, 10, 15, 20, 25, 30 };
    ValArr.Append(Temp, ARRAY_COUNT(Temp)); //==>[10,20,30,5,10,15,20,25,30]
    
    ValArr.Remove(20); //==>[10,30,5,10,15,25,30]
               
  • RemoveSingle

     也可用于擦除數組中的首個比對元素。
    ValArr.RemoveSingle(30); //==>[10,5,10,15,25,30]
               
  • RemoveAt

     函數也可用于按照從零開始的索引移除元素。

    可使用 

    IsValidIndex

     确定數組中的元素是否使用計劃提供的索引,将無效索引傳遞給此函數會導緻運作時錯誤:
    ValArr.RemoveAt(2); // 移除下标為2的元素, ==>[10,5,15,25,30]
    ValArr.RemoveAt(99); // 引發錯誤,越界
               
  • RemoveAll

     也可用于函數移除與謂詞比對的元素。
    ValArr.RemoveAll([](int32 Val) {
    	return Val % 3 == 0; }); //移除為3倍數的所有數值, ==> [10,5,25]
               
  • RemoveSwap

    RemoveAtSwap

     、 

    RemoveAllSwap

    移動過程存在開銷。如不需要剩餘元素排序,可使用 RemoveSwap、RemoveAtSwap 和 RemoveAllSwap 函數減少此開銷。此類函數的工作方式與其非交換變種相似,不同之處在于其不保證剩餘元素的排序,是以可更快地完成任務:
    TArray<int32> ValArr2;
    for (int32 i = 0; i != 10; ++i)
    	ValArr2.Add(i % 5); //==>[0,1,2,3,4,0,1,2,3,4]
    
    ValArr2.RemoveSwap(2); //==>[0,1,4,3,4,0,1,3]
    
    ValArr2.RemoveAtSwap(1); //==>[0,3,4,3,4,0,1]
    
    ValArr2.RemoveAllSwap([](int32 Val) {
    	return Val % 3 == 0; }); //==>[1,4,4]
               
  • Empty

     函數移除數組中所有元素
    ValArr2.Empty(); //==>[]
               
  • Reset

     與Empty函數類似,該函數将不釋放記憶體。
    ValArr2.Reset (); //==>[]
               

運算符

數組是正常數值類型,可使用标準複制構造函數或指派運算符進行複制。由于數組嚴格擁有其元素,複制數組的操作是深層的,是以新數組将擁有其自身的元素副本

TArray<int32> ValArr3;
ValArr3.Add(1);
ValArr3.Add(2);
ValArr3.Add(3);

auto ValArr4 = ValArr3; // ValArr4 == [1,2,3];
ValArr4[0] = 5;         // ValArr4 == [5,2,3]; ValArr3 == [1,2,3];
           
  • +=

     運算符 可替代Append函數進行數組連接配接
    ValArr4 += ValArr3; //==>[5,2,3,1,2,3]
               
  • MoveTemp

     函數可将一個數組中的内容移動到另一個數組中,源數組将被清空
    ValArr3 = MoveTemp(ValArr4);  // ValArr3 == [5,2,3,1,2,3]; ValArr4 == []
               
  • ==

     運算符和 

    !=

     運算符可對數組進行比較。

    元素的排序很重要:隻有元素的順序和數量相同時,兩個數組才被視為相同

    TArray<FString> FlavorArr1;
    FlavorArr1.Emplace(TEXT("Chocolate"));
    FlavorArr1.Emplace(TEXT("Vanilla")); // FlavorArr1 == ["Chocolate","Vanilla"]
    
    auto FlavorArr2 = FlavorArr1;        // FlavorArr2 == ["Chocolate","Vanilla"]
    
    bool bComparison1 = FlavorArr1 == FlavorArr2;  // bComparison1 == true
    
    for ( auto& Str : FlavorArr2 )
    {
    	Str = Str.ToUpper();
    } // FlavorArr2 == ["CHOCOLATE","VANILLA"]
    
    bool bComparison2 = FlavorArr1 == FlavorArr2; // bComparison2 == true,因為FString的對比忽略大小寫
    
    Exchange(FlavorArr2[0], FlavorArr2[1]); // FlavorArr2 == ["VANILLA","CHOCOLATE"]
    
    bool bComparison3 = FlavorArr1 == FlavorArr2; // bComparison3 == false,因為兩個數組内的元素順序不同
               

TArray 擁有支援二叉堆資料結構的函數。堆是一種二叉樹,其中父節點的排序等于或高于其子節點。作為數組實作時,樹的根節點位于元素0,索引N處節點的左右子節點的指數分别為2N+1和2N+2。子節點彼此間不存在特定排序。

  • Heapify

     函數可将現有數組轉換為堆。此會重載為是否接受謂詞,無謂詞的版本将使用元素類型的 

    運算符&lt;

     确定排序:

    樹中的節點按堆化數組中元素的排序從左至右、從上至下讀取。

    注意:數組在轉換為堆後無需排序。排序數組也是有效堆,但堆結構的定義較為寬松,同一組元素可存在多個有效堆。

    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]
               
【UE4 C++ 基礎知識】<5> 容器——TArray概述建立添加元素疊代排序查詢移除元素運算符堆Slack原始記憶體其他參考
  • HeapPush

     函數可将新元素添加到堆,對其他節點進行重新排序,以對堆進行維護
    HeapArr.HeapPush(4); // HeapArr == [1,2,4,3,4,5,8,10,7,9,6]
               
【UE4 C++ 基礎知識】<5> 容器——TArray概述建立添加元素疊代排序查詢移除元素運算符堆Slack原始記憶體其他參考
  • HeapPop

     和 

    HeapPopDiscard

     函數用于移除堆的頂部節點。

    這兩個函數的差別在于前者引用元素的類型來傳回頂部元素的副本,而後者隻是簡單地移除頂部節點,不進行任何形式的傳回。兩個函數得出的數組變更一緻,重新正确排序其他元素可對堆進行維護

    int32 TopNode;
    HeapArr.HeapPop(TopNode); // TopNode == 1; HeapArr == [2,3,4,6,4,5,8,10,7,9]
               
【UE4 C++ 基礎知識】<5> 容器——TArray概述建立添加元素疊代排序查詢移除元素運算符堆Slack原始記憶體其他參考
  • HeapRemoveAt

     将删除數組中給定索引處的元素,然後重新排列元素,對堆進行維護
    HeapArr.HeapRemoveAt(1); // HeapArr == [2,4,4,6,9,5,8,10,7]
               
【UE4 C++ 基礎知識】<5> 容器——TArray概述建立添加元素疊代排序查詢移除元素運算符堆Slack原始記憶體其他參考
  • HeapTop

     函數可檢視堆的頂部節點,無需變更數組
    int32 Top = HeapArr.HeapTop();  // Top == 2
               
  • 以下較為底層

Slack

因為數組的尺寸可進行調整,是以它們使用的是可變記憶體量。為避免每次添加元素時需要重新配置設定,配置設定器通常會提供比需求更多的記憶體,使之後進行的Add調用不會因為重新配置設定而出現性能損失。同樣,删除元素通常不會釋放記憶體。

  • 容器中現有的元素數量和下次配置設定之前可添加的元素數量之差成為Slack
  • 預設建構的數組不配置設定記憶體,slack初始為0。
  • GetSlack

     函數即可找出數組中的slack量,相當于Max() - Num()
  • Max

     函數可擷取到容器重新配置設定之前數組可儲存的最大元素數量。
  • 配置設定器确定重新配置設定後容器中的Slack量。是以 Slack 不是常量。
    TArray<int32> SlackArray;
    // SlackArray.GetSlack() == 0
    // SlackArray.Num()      == 0
    // SlackArray.Max()      == 0
    
    SlackArray.Add(1);
    // SlackArray.GetSlack() == 3
    // SlackArray.Num()      == 1
    // SlackArray.Max()      == 4
    
    SlackArray.Add(2);
    SlackArray.Add(3);
    SlackArray.Add(4);
    SlackArray.Add(5);
    // SlackArray.GetSlack() == 17
    // SlackArray.Num()      == 5
    // SlackArray.Max()      == 22
               
  • 雖然無需管理Slack,但可管理Slack對數組進行優化,以滿足需求。

    例如,如需要向數組添加大約100個新元素,則可在添加前確定擁有可至少存儲100個新元素的Slack,以便添加新元素時無需配置設定記憶體。上文所述的 

    Empty

     函數接受可選Slack參數
    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);
    SlackArray.Add(3);
    // SlackArray.GetSlack() == 0
    // SlackArray.Num()      == 3
    // SlackArray.Max()      == 3
               
  • Reset

     函數與Empty函數類似,不同之處是若目前記憶體配置設定已提供請求的Slack,該函數将不釋放記憶體。但若請求的Slack較大,其将配置設定更多記憶體:
    SlackArray.Reset(0);
    // SlackArray.GetSlack() == 3
    // SlackArray.Num()      == 0
    // SlackArray.Max()      == 3
    SlackArray.Reset(10);
    // SlackArray.GetSlack() == 10
    // SlackArray.Num()      == 0
    // SlackArray.Max()      == 10
               
  • Shrink

     函數可移除所有Slack。此函數将把配置設定重新調整為所需要的大小,使其儲存目前的元素序列,而無需實際移動元素
    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
    // SlackArray.Max()      == 4
               

原始記憶體

  • 本質上而言,TArray 隻是配置設定記憶體的包裝器。直接修改配置設定的位元組和自行建立元素即可将其用作包裝器,此操作十分實用。Tarray 将盡量利用其擁有的資訊進行執行,但有時需降低一個等級。
  • 利用以下函數可在較低級别快速通路 TArray 及其資料,但若利用不當,可能會導緻容器無效和未知行為。在調用此類函數後(但在調用其他正常函數前),可決定是否将容器傳回有效狀态。
  • AddUninitialized

     和 

    InsertUninitialized

     函數可将未初始化的空間添加到數組。兩者工作方式分别與 Add 和 Insert 函數相同,隻是不調用元素類型的構造函數。若要避免調用構造函數,建議使用此類函數。類似以下範例的情況中建議使用此類函數,其中計劃用 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]
               
    也可使用此功能保留計劃自行建構對象所需記憶體
    TArray<FString> UninitStrs;
    UninitStrs.Emplace(TEXT("A"));
    UninitStrs.Emplace(TEXT("D"));
    UninitStrs.InsertUninitialized(1, 2); // 第一個參數指明插入開始位置的索引,第二個參數指明插入幾個元素
    new ((void*)(UninitStrs.GetData() + 1)) FString(TEXT("B")); // GetData()傳回數組頭指針
    new ((void*)(UninitStrs.GetData() + 2)) FString(TEXT("C"));
    // UninitStrs == ["A","B","C","D"]
               
  • AddZeroed

     和 

    InsertZeroed

     的工作方式相似,不同點是會将添加/插入的空間位元組清零
    struct S
    {
    	S(int32 InInt, void* InPtr, float InFlt)
    		:Int(InInt)
    		, Ptr(InPtr)
    		, Flt(InFlt)
    	{
    	}
    	int32 Int;
    	void* Ptr;
    	float Flt;
    };
    TArray<S> SArr;
    SArr.AddZeroed();
    // SArr == [{ Int:0, Ptr: nullptr, Flt:0.0f }]
               
  • SetNumUninitialized

     和 

    SetNumZeroed

     函數的工作方式與 SetNum 類似,不同之處在于新數量大于目前數量時,将保留新元素的空間為未初始化或按位歸零。與 AddUninitialized 和 InsertUninitialized 函數相同,必要時需将新元素正确建構到新空間中
    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);
    // SArr == [
    //   { Int:0, Ptr: nullptr,    Flt:0.0f  },
    //   { Int:5, Ptr:0x12345678, Flt:3.14f },
    //   { Int:2, Ptr:0x87654321, Flt:2.72f },
    //   { Int:0, Ptr: nullptr,    Flt:0.0f  },
    //   { Int:0, Ptr: nullptr,    Flt:0.0f  }
    // ]
               

應謹慎使用"Uninitialized"和"Zeroed"函數族。如函數類型包含要建構的成員或未處于有效按位清零狀态的成員,可導緻數組元素無效和未知行為。此類函數适用于固定的數組類型,例如FMatrix和FVector。

其他

  • BulkSerialize

     函數是序列化函數,可用作替代 

    運算符&lt;&lt;

    ,将數組作為原始位元組塊進行序列化,而非執行逐元素序列化。如使用内置類型或純資料結構體等淺顯元素,可改善性能。
  • CountBytes

     和 

    GetAllocatedSize

     函數用于估算數組目前記憶體占用量。

    CountBytes

     接受 

    FArchive

    ,可直接調用 

    GetAllocatedSize

    。此類函數常用于統計報告。
  • Swap

     和 

    SwapMemory

     函數均接受兩個指數并交換此類指數上的元素值。這兩個函數相同,不同點是 

    Swap

     會對指數執行額外的錯誤檢查,并斷言索引是否超出範圍。

參考

TArray:虛幻引擎中的數組

繼續閱讀