泛型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)」