我有一个结构如下:
typedef struct Node {
void* data;
unsigned long id;
NodePtr next;
NodePtr prev;
} Node;
它意味着是链表ADT中的节点。我有2个不同的构造函数,具体取决于Node需要在数据中保存的内容。一个构造函数使用:
NodePtr TempNode;
TempNode = malloc( sizeof(Node) );
/* Other stuff */
TempNode->data = newList();
return (TempNode);
这似乎可以让我通过返回(List-> current->数据)来访问该列表,其中current是List Struct中的Node指针
但是,我想创建一个构造函数的版本,其中(data)指向一个int。我已经读过,我可以通过以下方式做到这一点
void* ptr;
int x = 0;
*((int*)ptr) = x;
但是我的构造函数设置方式,这意味着我必须做这样的事情?
*((int*)TempNode->data) = 1; // data starts at 1
这不起作用。我对C很新,所以我不太懂术语。我读到解除引用(使用 - >符号?)不能用void *来完成,但它似乎在我的构造函数的列表版本中工作正常。如何重写我的其他构造函数以将此void *转换为int?
答案 0 :(得分: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);
}
你必须记住释放两个内存分配。
在使用结果之前,代码还应检查内存分配是否成功。
答案 1 :(得分:1)
我们来谈谈这个
当您执行类似
的操作时NodePtr TempNode;
TempNode = malloc( sizeof(Node) );
你已经要求图书馆重新设置一些足够Node
的动态存储空间。该内存的初始值是未定义的,所以现在指针TempNode->data
可以指向任何地方,并且可能没有指向保留供您使用的内存。
当你这样做时
TempNode->data = newList();
你给指针一个(大概是newList()
做一些合法且合理的)有效值;
如果你做了
*((int*)TempNode->data) = 1;
指示编译器
TempNode->Data
视为指向int的指针,1
(注意,在任何时候都没有设置data
本身,只要它指向的是什么)但你不知道它指向的是什么! 取消引用它是未定义的行为,严重是坏事。
除非指向指向您有权使用的内存,否则您始终有责任确保不取消引用指针。
答案 2 :(得分:1)
“如何将数据指向我可以使用的区域?”
我不确定你的意思是否我将在下面解释(并且它不会简短:P),但如果你问如何区分{{1中存储的数据的类型然后用那个实现你不能。
你把它留给最终程序员来记住他存储在列表中的数据类型(这不是坏事btw ..相反,它是常态)。换句话说,你相信最终程序员在打印Node->data
时会应用适当的演员。
如果由于某种原因您希望为列表提供更多托管API,则可以在Node->data
结构中添加一个字段,以标识列表中存储的数据类型。
例如......
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 */
}
(我在示例中标记为 TRICKY ),实现不同的DT_PTR
构造函数更安全,可能接受2个额外的参数,让我们说{ {1}}和Node
,因此函数可以分配elemsz
个字节用于将nelems
内容复制到它们中,以防elemsz * nelems
指向数组,结构或任何内容其他非原始类型。或者,您可以专门为数组提供额外的data
枚举值。你可以自由地做任何最适合你的事。
在任何情况下,对于data
,上面的示例依赖于DT_ARR
的调用者正确分配了传递的DT_PTR
,并且在一般情况下,这是而不是强大>一件好事。
代码更复杂,但您知道列表中存储的数据类型。因此,至少可以添加的原始数据类型表示根据list_add_node
自动转换其输出的打印例程(对于非原始数据类型,您应该通过回调函数提供对自定义打印例程的支持)。
您甚至可以将其发挥到极致,并将data
字段从list->dt
移至dt
。在这种情况下,您在节点中实现了异构数据的列表,但它变得更加复杂,而且很少有用(如果有的话)。
所有这些ADT东西通过(void *)指针都存在严重的性能问题,这就是为什么速度关键实现会利用(或者如果你愿意的话滥用)预处理器而不是这种东西。