“如何使数据指向我可以使用的区域?”
我不确定您的意思是不是我将在下面解释的内容(并且不会很短: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 个额外的参数,比如说elemsz
and nelems
,因此函数可以分配elemsz * nelems
字节以将内容复制data
到其中,以防万一data
指向数组,结构或任何其他非原始类型。或者,您可以专门为数组提供额外的DT_ARR
枚举值。你可以自由地做任何最适合你的事情。
在任何情况下,对于DT_PTR
上面的示例,都依赖于调用者list_add_node
来正确分配传递的data
,在一般情况下,这根本不是一件好事。
代码更复杂,但您知道存储在列表中的数据类型。因此,至少对于原始数据类型,您可以添加一个自动转换其输出的打印例程list->dt
(对于非原始数据类型,您应该提供对自定义打印例程的支持,通常通过回调函数)。
您甚至可以将其发挥到极致,将dt
字段从List
移至Node
. 在这种情况下,您在节点中实现了一个异构数据列表,但它变得更加复杂,而且它很少有用(如果有的话)。
所有这些通过 (void *) 指针的 ADT 东西都有严重的性能问题,这就是为什么速度关键的实现会使用(或者如果你愿意的话滥用)预处理器来处理这种东西。