在C中将Arraylist实现转换为通用

时间:2017-10-17 22:02:18

标签: c arraylist implementation generic-programming universal

我在普通C中实现了ArrayList。

HEADER(arraylist.h):

#pragma once

struct ArrayListImpl;
typedef int LISTTYPE;
typedef struct ArrayListImpl ArrayList;

ArrayList* new_ArrayList(int iSize);
void destroy(ArrayList* list);

int indexOf(ArrayList* list, LISTTYPE element);
void add(ArrayList* list, LISTTYPE element);
void addBefore(ArrayList* list, int index, LISTTYPE element);
void clear(ArrayList* list);
_Bool contains(ArrayList* list, LISTTYPE element);
_Bool removeEl(ArrayList* list, LISTTYPE element);
void removeFrom(ArrayList* list, int index);
void set(ArrayList* list, int index, LISTTYPE element);
int size(ArrayList* list);
void print(ArrayList* list);
void printInfo(ArrayList* list);

实施(arraylist.c):

#include "arraylist.h"
#include <stdlib.h>
#include <stdio.h>

struct ArrayListImpl
{
    int size, buffer, origBuffer;
    LISTTYPE* data;
};

void incBuffer(ArrayList* list)
{
    if (list->size == list->buffer)
    {
        list->buffer = (int)(list->buffer * 1.5 + 1);
        list->data = (LISTTYPE*)realloc(list->data, sizeof(LISTTYPE) * list->buffer);
    }
}

void decBuffer(ArrayList* list)
{
    if (list->size < list->buffer / 2.5 && list->buffer > list->origBuffer)
    {
        list->buffer = max(list->origBuffer, list->buffer / 2);
        list->data=(LISTTYPE*)realloc(list->data, sizeof(LISTTYPE) * list->buffer);
    }
}
void resetBuffer(ArrayList* list)
{
    list->buffer = list->origBuffer;
    list->data = (LISTTYPE*)realloc(list->data, sizeof(LISTTYPE) * list->buffer);
}

ArrayList* new_ArrayList(int buffer)
{
    ArrayList* out;
    out = (ArrayList*)malloc(sizeof out);
    out->size = 0;
    out->buffer = buffer;
    out->origBuffer = buffer;
    out->data = (LISTTYPE*)malloc(buffer * sizeof(LISTTYPE));
    return out;
}

void destroy(ArrayList* list)
{
    free(list->data);
}

int indexOf(ArrayList* list, LISTTYPE element)
{
    for (int i = 0; i < list->size; ++i)
    {
        if (list->data[i] == element)
            return i;
    }
    return -1;
}

void add(ArrayList* list, LISTTYPE element)
{
    incBuffer(list);
    list->data[list->size++] = element;
}

void addBefore(ArrayList* list, int from, LISTTYPE element)
{
    if (from < 0 || from > list->size)
    {
        printf("[ERROR] Trying to add before %d. element of list having size %d\n", from, list->size);
        return;
    }
    incBuffer(list);
    ++list->size;
    for (int i = list->size; i > from; --i)
    {
        list->data[i] = list->data[i - 1];
    }
    list->data[from] = element;
}

_Bool removeEl(ArrayList* list, LISTTYPE element)
{
    int id = indexOf(list, element);
    if (id == -1)
        return 0;
    --list->size;
    for (int i = id; i < list->size; ++i)
    {
        list->data[i] = list->data[i + 1];
    }
    decBuffer(list);
    return 1;
}

void removeFrom(ArrayList* list, int index)
{
    if (index < 0 || index >= list->size)
    {
        printf("[ERROR] Trying to remove %d. element of list having size %d\n", index, list->size);
        return;
    }
    --list->size;
    for (int i = index; i < list->size; ++i)
    {
        list->data[i] = list->data[i + 1];
    }
    decBuffer(list);
}

_Bool contains(ArrayList* list, LISTTYPE element)
{
    return indexOf(list, element) != -1;
}

int size(ArrayList* list)
{
    return list->size;
}

void clear(ArrayList* list)
{
    list->size = 0;
    resetBuffer(list);
}

void set(ArrayList* list, int index, LISTTYPE element)
{
    if (index < 0 || index >= list->size)
    {
        printf("[ERROR] Trying to set %d. element of list having size %d\n", index, list->size);
        return;
    }
    list->data[index] = element;
}

void print(ArrayList* list)
{
    printf("--- ArrayList ---\n");
    for (int i = 0; i < list->size; ++i)
    {
        printf("%d.: %dn", i, list->data[i]);
    }
    printf("\n-------------------\n");
}

void printInfo(ArrayList* list)
{
    printf("--- ArrayList INFO ---\nSize: %d\nElement size : %d\nBuffer : %d\n", list->size, sizeof(int), list->buffer);
}

正如您所看到的,它仅适用于头文件中定义了LISTTYPE类型的数据。我的问题是,如何使其适用于任何类型的数据?所以例如以某种方式将LISTTYPE添加到它的构造函数而不是它的头部。是否有可能这样做,或者至少在普通C中做这样的事情而不是在C ++中做什么?

1 个答案:

答案 0 :(得分:3)

您必须将内部列表管理的问题与客户列表数据的问题分开。您的列表管理数据必须是强类型的,但您对客户列表数据的查看应该是不透明的(void *)。您的界面设计应该保留这种关注点分离。

无需在arraylist.h中声明或定义ArrayListImpl。您的客户不需要了解它。具有ArrayList类型是好的,但如果它只是作为索引值,散列或数据指针(void *)实现的不透明句柄就足够了。基本上,无论您给客户跟踪他们的列表,他们都应该无法从其声明或使用中学习任何实现细节。如果您确实将它们指向内部管理结构,那么它们的视图应该是无效的*。您始终可以从void *转换为内部数据结构。

我建议您重新考虑您的界面。使用ArrayList.h文件编写一些单元测试并让它们进行编译(它们显然不会链接)。从客户的角度了解如何使用您的界面。另外,在头文件中添加注释。我们不应该猜测iSize是指定数组的大小,元素的数量还是元素的大小。如果客户在初始化列表时指定了计数和元素大小,则可能更实用。

一些示例代码:

在ArrayList.h中

#include <stdbool.h>
#include <stdint.h>

// The current implementation of the API defined herein is not thread safe.s

// ArrayList should be treated as an opaque data object, never intended
// to be dereferenced by client code.
typedef void* ArrayList;

// Define default module initialization.
#define ARRAYLIST_DEFAULT_NUM_LISTS 4
#define ARRAYLIST_DEFAULT_INCREMENT_SIZE 8

// InitializeArrayListModule(size_t numLists, size_t incrementSize);
//   Optional module initializer.  The number of lists can grow until memory
//   runs out, but performance may not be optimal and the heap can get 
//   fragmented.  
//   Call this function only if you're going to be allocating more or less 
//   than the default values.
// Arguments:
//   numLists  Number of lists the internal data structures are preallocated 
//   for. Internal
//             default is ARRAYLIST_DEFAULT_NUM_LISTS.
//   incrementSize  Number of additional internal data structures to allocate 
//   when a call to NewArrayList() triggers a realocation of data structures. 
//   Internal default is DEFAULT_INCREMENT_SIZE.
// Returns:
//   true if enough internal data structures can be allocated to support the 
//   specified number of lists.
//   false if allocations failed, the function has been called more than once 
//   or NewArrayList has
//   already been called.
// Notes:
//  1. List management data structures are stored in separate memory from 
//     client's list data.
bool InitializeArrayListModule(size_t numLists, size_t incrementSize);

// NewArrayList(size_t, size_t)
//   The only way to properly initialize an ArrayList object.
// Arguments:
//   initialCapacity Number of initial elements to allocate, must not be 
//   zero.
//   sizeofElements  Size in bytes of each element, must not be zero.
// Returns:
//   A valid ArrayList on success.
//   NULL on failure.
ArrayList NewArrayList(size_t initialCapacity, size_t sizeofElement);

// DestroyArrayList(ArrayList arrayList)
//   The only way to properly destroy an ArrayList object.
// Arguments:
//   arrayList  ArrayList object returned from NewArrayList, or NULL.
// Returns:
//   NULL.
// Example:
//   ArrayList arrayList = NewArrayList(capacity, sizeofElement);
//   arrayList = DestroyArrayList(arrayList);
ArrayList DestroyArrayList(ArrayList arrayList);

// AddArrayListItem(ArrayList, void *item)
//   Copies elementSize bytes from the memory pointed to by item.
// Arguments:
//   arrayList  A valid ArrayList object returned from NewArrayList.
//   element    Pointer to the data to add to the list.
// Returns:
//   true if successful.
bool AddArrayListItem(ArrayList arrayList, void *element);

在UTArrayList.c中

    #include <stdbool.h>
#include <stdio.h>
//#include "ArrayList.h"

void _UTShowTestResult(char *testFuncName, bool result)
{
    if (result)
    {
        printf("%s() passed.\n", testFuncName);
    }
    else
    {
        printf("%s() failed.\n", testFuncName);
    }
}

#define UTShowTestResult(funcName) _UTShowTestResult(#funcName, funcName##())

// In UTArrayList.c
// TODO: Add logging.
#include <limits.h>

// Smoke test: bool InitializeArrayListModule(size_t numLists, size_t incrementSize);
bool UTInitializeArrayListModule()
{
    return InitializeArrayListModule(1, 4);
}

// Smoke test: ArrayList NewArrayList(size_t, size_t).
bool UTNewArrayList()
{
    // Happy path...
    for (size_t capacity = 1; capacity <= (64 * 1024); capacity += 1024)
    {
        for (size_t elementSize = 1; elementSize <= (64 * 1024); elementSize += 1024)
        {
            ArrayList testList = NewArrayList(capacity, elementSize);
            if (NULL == testList)
            {
                return false;
            }
        }
    }

    // TODO: Test that expected failure paths fail gracefully.

    return true;
}

// Smoke test: ArrayList DestroyArrayList(ArrayList arrayList)
bool UTDestroyArrayList()
{
    ArrayList arrayList = NewArrayList(1, 1);

    // Verify works with NULL.
    if (NULL != DestroyArrayList(NULL))
    {
        return false;
    }

    // Verify works with valid arrayList object, but don't let the test overwrite it yet.
    if (NULL != DestroyArrayList(&arrayList))
    {
        return false;
    }

    // Verify works twice on same value.
    arrayList = DestroyArrayList(&arrayList); // The correct way to call DestroyArrayList().
    if (NULL != arrayList)
    {
        return false;
    }

    return true;
}

// Smoke test: bool AddArrayListItem(ArrayList arrayList, void *element)
bool UTAddArrayListItem()
{
    // TODO: Verify list items are correct and remain in proper order over 
    //       list operations, as soon we have an implementation for iterating 
    //       over the list.
    // TODO: Verify items of different sizes can added successfully.
    const char* str1 = "str1";
    ArrayList arrayList = NewArrayList(2, sizeof(char*));
    return AddArrayListItem(arrayList, str1);
}

// ArrayList Unit Test Driver.
int main(int argc, char **argv)
{
    // TODO: As the interface is fleshed out, add unit test calls.
    UTShowTestResult(UTInitializeArrayListModule);
    UTShowTestResult(UTNewArrayList);
    UTShowTestResult(UTDestroyArrayList);
    UTShowTestResult(UTAddArrayListItem);
}

在ArrayList.c中

    #include <stdlib.h>
#include "ArrayList.h"

typedef struct _List 
{
    size_t capacity;
    size_t elementSize;
    size_t count;
    void* data;
} List;

static size_t _listCapacity = ARRAYLIST_DEFAULT_NUM_LISTS;
static size_t _incrementSize = ARRAYLIST_DEFAULT_INCREMENT_SIZE;
static size_t _count = 0;

static List *_lists = NULL;
static List *_nextList = NULL;

// TODO: Add implementations as interfaces and unit tests are added.

static bool _InitializeModule()
{
    // Always fail to initialize twice!
    if (NULL == _lists)
    {
        _lists = calloc(_listCapacity, sizeof(List));
        _nextList = _lists;
    }
    return (_lists != NULL);
}

static bool _ReallocateLists()
{
    List *newLists = calloc(_listCapacity + _incrementSize, sizeof(List));
    if (NULL != newLists)
    {
        for (size_t idx = 0; idx < _listCapacity; idx++)
        {
            newLists[idx] = _lists[idx];
        }
        List *tmp = _lists;
        _lists = newLists;
        free(tmp);
        _nextList = _lists + _listCapacity;
    }
    return (NULL != _lists);
}

bool InitializeArrayListModule(size_t numLists, size_t incrementSize)
{
    if (NULL == _lists)
    {
        _listCapacity = numLists;
        _incrementSize = incrementSize;
    }
    return _InitializeModule();
}

ArrayList NewArrayList(size_t initialCapacity, size_t sizeofElement)
{
    if (NULL == _lists)
    {
        if (!_InitializeModule()) return NULL;
    }

    if (_listCapacity < _count)
    {
        if (!_ReallocateLists()) return NULL;
    }

    List *p = &(_lists[_count]);
    p->capacity = initialCapacity;
    p->elementSize = sizeofElement;
    p->data = calloc(initialCapacity, sizeofElement);

    return p;
}

ArrayList DestroyArrayList(ArrayList arrayList)
{
    List *p = arrayList;    // Convert from void* to List*.
    List *last = _lists + _listCapacity;
    // Sanity checks...
    bool isInRange = (p >= _lists) && (p <= last);
    bool isAligned = 0 == ((p - _lists) % sizeof(List));
    if (isInRange && isAligned)
    {
        free(p->data);
        memset(p, 0, sizeof(List));
    }
    return NULL;
}

bool AddArrayListItem(ArrayList arrayList, void *item)
{
    // TODO: find the list, use similar logic to how _lists is managed, to add this item to that lists data array.
    // HINT: memcpy(itemAddress, item, sizeofElement);
    return false;
}

上面演示了void指针可以用作不透明的数据对象,以保护您的实现不受客户端数据类型和客户端的影响。所提供的单元测试从客户的角度展示了API的使用,并为您提供了试用API并测试您的实现的操场。