3

我有一个结构如下:

typedef struct Node {
    void* data;
    unsigned long id;
    NodePtr next;
    NodePtr prev;
} Node;

它是链表 ADT 中的一个节点。根据节点需要在数据中保存的内容,我有 2 个不同的构造函数。一个构造函数使用:

NodePtr TempNode;
TempNode = malloc( sizeof(Node) );
/* Other stuff */
TempNode->data = newList();
return (TempNode);

这似乎可以让我通过返回 (List->current->data) 访问该列表,其中 current 是 List Struct 中的节点指针

但是,我想制作一个版本的构造函数,其中 (data) 指向一个 int。我读过我可以通过执行以下操作来做到这一点

void* ptr;
int x = 0;
*((int*)ptr) = x;

但是按照我的构造函数的设置方式,这意味着我必须做这样的事情吗?

*((int*)TempNode->data) = 1; // data starts at 1

这是行不通的。我对C很陌生,所以我不太了解这些术语。我读到取消引用(使用 -> 符号?)不能用 void* 完成,但它似乎在我的构造函数列表版本中工作正常。如何重写我的其他构造函数以将此 void* 转换为 int?

4

3 回答 3

2

我强烈反对这样做,但如果你真的想使用void *成员来保存整数,你可以这样做:

Node *constructor_int(int n)
{
    Node *tmp = malloc(sizeof(*tmp));
    /* Other stuff */
    tmp->data = (void *)n;
    return(tmp);
}

这涉及最少数量的强制转换,并避免了类型相对大小的大多数问题。

显而易见的、合乎逻辑的方法是为data成员分配一个整数以指向:

Node *constructor_int(int n)
{
    Node *tmp = malloc(sizeof(*tmp));
    /* Other stuff */
    tmp->data = malloc(sizeof(int));
    *(int *)temp->data = n;
    return(tmp);
}

您只需要记住释放两个内存分配。

代码还应该在使用结果之前检查内存分配是否成功。

于 2013-05-19T22:00:16.633 回答
1

让我们谈谈这个

当你做类似的事情时

NodePtr TempNode;
TempNode = malloc( sizeof(Node) );

您已要求图书馆为您提供一些足够大的动态存储Node。该内存的初始值是未定义的,因此现在指针TempNode->data可以指向任何地方,并且可能不指向保留供您使用的内存。

当你这样做

TempNode->data = newList();

你给指针一个(大概,只要newList()做一些合法和合理的事情)有效值;

相反,如果你这样做

*((int*)TempNode->data) = 1;

你指示编译器

  1. 视为TempNode->Data指向 int 的指针,
  2. 取消引用它和
  3. 将另一端的每个内存的值设置为1(请注意,您从来没有设置data自己,只是它指向的任何位置)

但你不知道它指向什么!取消引用它是未定义的行为,严格来说是一件坏事。

您始终负责确保不取消引用指针,除非它指向您有权使用的内存。

于 2013-05-19T21:59:52.357 回答
1

如何使数据指向我可以使用的区域?”

我不确定您的意思是不是我将在下面解释的内容(并且不会很短:P),但是如果您要问如何区分存储在 中的数据的类型Node->data,那么使用该实现不能。

您让最终程序员记住他存储在列表中的数据类型(顺便说一句,这不是一件坏事......相反,这是常态)。换句话说,您相信最终程序员会在打印Node->data.

如果出于某种原因您希望为您的列表提供更受管理的 API,您可以在List结构中再添加一个字段,用于识别存储在列表中的数据的类型。

例如...

enum DataType {
    DT_INVALID = 0,
    DT_PTR
    DT_CHAR,
    DT_INT,
    DT_FLOAT,
    DT_DOUBLE,
    ...
    /* not a data-type, just their total count */
    DT_MAX
};
#define DT_IS_VALID(dt)    ( (dt) > DT_INVALID && (dt) < DT_MAX )

typedef struct List List;
struct List {
    enum DataType dt;
    Node  *head;
};

当然,您可以自由地支持比我在enum上面列出的数据类型更少或更多的数据类型(甚至是自定义数据类型,比如字符串,或者根据您的项目您认为合适的任何数据类型)。

所以首先你需要一个列表的构造函数(或初始化程序),像这样......

List *new_list( enum DataType dt )
{
    List *ret = NULL;

    if ( !DT_IS_VALID(dt) )
        return NULL;

    ret = malloc( sizeof(List) );
    if ( NULL == ret )
        return NULL;

    ret->dt = dt; 
    ret->head = NULL;

    return ret;
}

并实例化它,这样说...

int main( void )
{
    List *listInt = new_list( DT_INT );
    if ( NULL == list ) {
        /* handle failure here */
    }

现在您已经将预期的数据类型存储到列表元数据中,您可以自由选择如何实现Node构造函数。例如,一个通用的可能看起来像这样......

int list_add_node( List *list, const void *data )
{
    Node *node = NULL;
    size_t datasz = 0;

    /* sanity checks */
    if ( !list || !data || !DT_IS_VALID(list->dt) )
        return 0;  /* false */

    node = malloc( sizeof(Node) );
    if ( NULL == node )
        return 0; /* false */

    /* when data points to mem already reserved say for an array (TRICKY) */
    if ( DT_PTR == list->dt ) {  
        node->data = data;
    }
    /* when data points to mem reserved for a primitive data-type */
    else {
        datasz = dt_size( list->dt );  /* implement dt_size() according to your supported data-types */
        node->data = malloc( datasz );
        if ( NULL == node->data ) {
            free( node );
            return 0; /* false */
        }
        memcpy(node->data, data, datasz );
    }

    /* add here the code dealing with adding node into list->head */
    ...
    return 1; /* true */
}

对于DT_PTR(我在示例中标记为TRICKY),实现不同的Node构造函数更安全,可能接受 2 个额外的参数,比如说elemszand nelems,因此函数可以分配elemsz * nelems字节以将内容复制data到其中,以防万一data指向数组,结构或任何其他非原始类型。或者,您可以专门为数组提供额外的DT_ARR枚举值。你可以自由地做任何最适合你的事情。

在任何情况下,对于DT_PTR上面的示例,都依赖于调用者list_add_node来正确分配传递的data,在一般情况下,这根本不是一件好事。

代码更复杂,但您知道存储在列表中的数据类型。因此,至少对于原始数据类型,您可以添加一个自动转换其输出的打印例程list->dt(对于非原始数据类型,您应该提供对自定义打印例程的支持,通常通过回调函数)。

您甚至可以将其发挥到极致,将dt字段从List移至Node. 在这种情况下,您在节点中实现了一个异构数据列表,但它变得更加复杂,而且它很少有用(如果有的话)。

所有这些通过 (void *) 指针的 ADT 东西都有严重的性能问题,这就是为什么速度关键的实现会使用(或者如果你愿意的话滥用)预处理器来处理这种东西。

于 2013-05-20T00:55:51.133 回答