「遊戲引擎Mojoc」(8)C實現泛型ArrayList

泛型ArrayList是基礎的數據結構,然而在C的標準庫裏並沒有提供,所以需要自己手動實現一個。Mojoc的ArrayList實現經過了實踐的測試,達到了穩定和高效的狀態,源碼ArrayList.h

本文主要介紹實現思路和一些特性,Mojoc的泛型ArrayList主要完成了以下幾個功能:

  • 存取放任意數據類型。
  • 動態內存擴展。
  • 針對棧數據與堆數據不同的存儲策略。
  • 一組簡潔的數據操作接口。
  • 利用宏進行了接口簡化和特殊調用。
  • 一些高效優化的數據操作策略。

ArrayList的數據

typedef struct
{
    /**
     * 手動設置內存擴展策略
     */
    int                increase;

    /**
     * 數組元素類型大小
     */
    int                elementTypeSize;

    /**
     * 元素個數
     */
    int                size;

    /**
     * 元素數據內存,使用Array結構存儲
     */
    Array(elementType) elementArray[1];
}
ArrayList;

typedef struct
{
    /**
     * 內存指針
     */
    void* data;

    /**
     * 元素個數
     */
    int   length;
}
Array;
  • ArrayList 使用 Array結構來存放數據的內存。所以,Array的Length其實是Capacity,而ArrayList的Size是元素的個數。

  • 實現泛型的策略是保存元素類型的大小elementTypeSize,這樣在遍歷的時候,就可以根據不同類型,進行數據的檢索。

  • elementArray負責存儲數據。

  • 使用了realloc來進行內存的擴充,所以elementArray的data指針是會變化的。increase是擴展內存的大小,這裏並沒有用一般百分比擴容的策略。

ArrayList的構建

// 堆上創建
ArrayList* (*Create)(int elementTypeSize);
// 棧初始化
void (*Init)(int elementTypeSize, ArrayList* outArrayList);

// 堆上創建,帶有元素個數
ArrayList* (*CreateWithSize)(int elementTypeSize, int size);
// 棧上初始化,帶有元素個數
void (*InitWithSize)(int elementTypeSize, int size, ArrayList* outArrayList);

// 堆上創建,帶有容量
ArrayList* (*CreateWithCapacity)(int elementTypeSize, int capacity);
// 棧上初始化,帶有容量
void (*InitWithCapacity)(int elementTypeSize, int capacity, ArrayList* outArrayList);
  • 這裏提供了3種策略,2種形式的構建。其中,堆上創建就是malloc內存數據,棧上初始化在於ArrayList是棧對象,比如ArrayList list[1]; 這樣的構造形式。或是初始化在其它struct類型中的嵌入ArrayList。Init前綴的函數提過了初始化的功能。

  • elementTypeSize就是泛型中的類型,比如 sizeof(int), sizeof(struct) 等等。

ArrayList的泛型形式

#define ArrayList(elementType) ArrayList

這個簡單的宏定義,在實踐中卻有不可思議的效果,對於明確泛型使用提供了直覺上的便利性和可讀性。elementType是一個形式,被替換爲不存在,這樣就模擬了泛型語法中<>的可讀性。

// 棧上 int 類型
ArrayList(int)  list[1]; Init(sizeof(int), list);
// 堆上 int 類型
ArrayList(int)* list = Create(sizeof(int));

struct Obj
{
   ArrayList(float)  list[1];
   ArrayList(float)* otherList;
};

ArrayList的數據操作

// 添加元素空間,並返回元素地址
void* (*GetAdd)(ArrayList* arrayList);
// 插入元素空間,並返回元素地址
void* (*GetInsert)(ArrayList* arrayList, int index);
// 拷貝元素,並返回元素地址
void* (*Add)(ArrayList* arrayList, void* elementPtr);
//插入元素,並返回地址
void* (*Insert)(ArrayList* arrayList, int index, void* elementPtr);
...

一旦確定了elementTypeSize,所有的操作都會基於elementTypeSize。從而實現了不同類型,一套數據類型的操作。當然,也會提供一套宏,來簡化API的調用。

#define AArrayList_GetAdd(arrayList, elementType) \
    (*(elementType*) AArrayList->GetAdd(arrayList))

#define AArrayList_GetInsert(arrayList, index, elementType) \
    (*(elementType*) AArrayList->GetInsert(arrayList, index))

#define AArrayList_Add(arrayList, element) \
    AArrayList->Add(arrayList, &(element))

#define AArrayList_Insert(arrayList, index, element) \
    AArrayList->Insert(arrayList, index, &(element))

...

ArrayList的一些特別的操作

// 彈出一個元素,實現高效的模擬了Stack
void* (*Pop)(ArrayList* arrayList, void* defaultElementPtr);
// 高效的刪除一個範圍的元素
void (*RemoveRange)(ArrayList* arrayList, int fromIndex, int toIndex);
// 刪除一個元素,並用最後一個元素填充。避免了元素移動,但會打亂元素次序
void (*RemoveByLast)(ArrayList* arrayList, int index);
// 利用realloc的特點,釋放動態增長的多餘空間 
void (*Shrink)(ArrayList* arrayList);

// 初始化常量ArrayList
#define AArrayList_Init(elementType, increase) \
    {                                          \
        increase,                              \
        sizeof(elementType),                   \
        0,                                     \
        {                                      \
            NULL,                              \
            0,                                 \
        },                                     \
    }

/**
 * 構建一個固定容量的ArrayList
 */
#define AArayList_InitFix(elementType, capacity, size, ...) \
    {                                                       \
        0,                                                  \
        sizeof(elementType),                                \
        size,                                               \
        AArray_Init(elementType, capacity, __VA_ARGS__),    \
    }

還有很多就不一一列舉了,很多API的設定,都是在實踐中需求的,爲了效率,爲了優化,爲了提高性能特別增加的。

ArrayList的實現

源碼的實現,ArrayList.c,可以看到原理很簡單,實現清晰明確,這是工程實踐的結果。如果曾經手動實現過泛型ArrayList,在來看這份代碼實現,就能感受到簡潔的力量,沒有多餘的直接完成目的。

如果用這個代碼去寫些功能,用的過程中就會發現,所有想要的功能盡在其中,而邊界檢查效率穩定性都面面俱到。

ArrayList的刪除操作

關於刪除操作,需要多說一些。刪除,需要移動元素。但有一個小技巧,就是倒序遍歷刪除。

for (int i = arrayList.size - 1; i > -1; i--)
{
    // 刪完繼續往前走,其它不用操心。
    Remove(arrayList[i]);
}

但這裏還有一個更高效的函數,如果不在意元素順序的話。

for (int i = arrayList.size - 1; i > -1; i--)
{
    // 這裏會刪掉元素,使用最後一個元素填充,無需移動其它元素
    RemoveByLast(arrayList, i);
}

最後

Mojoc的泛型ArrayList的實現,只使用了C語言的標準庫,可以拿出來獨立使用。更多的使用示例請看scottcgi/Mojoc的源代碼,全局搜索ArrayList。


「ArrayList(T)」

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章