对于家庭作业,更简单的方法是使用union
数据类型:
struct node {
union {
char *s
int i;
} data;
struct node *left;
struct node *right;
};
并创建两组函数,一组用于管理整数值,另一组用于管理字符串值:
void insertIntNode(struct node *root, struct node *newNode)
{
if (newNode->data.i < root->data.i)
if (root->left != NULL)
insertIntNode(root->left, newNode);
else
root->left = newNode;
else
if (root->right != NULL)
insertIntNode(root->right, newNode);
else
root->right = newNode;
}
void insertWordNode(struct node *root, struct node *newNode)
{
if (strcmp(root->data.s, newNode->data.s) < 0)
if (root->left != NULL)
insertWordNode(root->left, newNode);
else
root->left = newNode;
else
if (root->right != NULL)
insertWordNode(root->right, newNode);
else
root->right = newNode;
}
请记住,您需要对单词数据进行一些额外的内存管理:
struct node *createWordNode(char *str)
{
struct node *r = malloc(sizeof *r);
if (r)
{
r->data.s = malloc(strlen(str) + 1);
if (r->s)
strcpy(r->data.s, str);
r->left = r->right = NULL;
}
return r;
}
void destroyWordNode(struct node **n)
{
free((*n)->data.s);
free(*n);
*n = NULL;
}
更灵活的方法是使用 avoid *
指向您的数据项,然后将所有类型感知操作(分配、赋值、比较、显示等)委托给隐藏在一组函数指针后面的其他函数。例如:
struct node {
void *data;
struct node *left;
struct node *right;
};
struct node *newNode(void *data, void *(*copy)(const void *))
{
struct node *n = malloc(sizeof *n);
if (n)
{
n->left = n->right = NULL;
n->data = copy(data);
}
return n;
}
void insert(struct node *root, struct node *newNode,
int (*compare)(const void *, const void *))
{
if (compare(newNode->data, root->data) < 0)
if (root->left != NULL)
insert(root->left, newNode, compare);
else
root->left = newNode;
else
if (root->right != NULL)
insert(root->right, newNode);
else
root->right = newNode;
}
在上面的示例中,为节点data
元素分配内存和比较两个data
元素的细节被委托给其他函数,指向这些函数的指针作为参数传递给列表管理函数。这样你就可以编写一个newNode
和insert
函数,但它能够处理任意节点数据类型。因此,对于您的整数树,您可以编写如下函数
void *copyInt(const void *data)
{
const int *src = data;
int *dst = malloc(sizeof *dst);
if (dst)
{
*dst = *src;
}
return dst;
}
int compareInt(const void *lhs, const void *rhs)
{
const int *ilhs = lhs;
const int *irhs = rhs;
if (*ilhs < *irhs)
return -1;
else if (*ilhs == *irhs)
return 0;
else
return 1;
}
然后你会打电话newNode
给insert
喜欢
void insertIntValue(struct node *root, int value)
{
struct node *n = newNode(&value, copyInt);
if (n)
insert(root, n, compareInt);
}
这种方法的最大缺点是您将类型安全性扔到窗外并进入迎面而来的交通;因为我们正在使用void *
一切。编译器将无法为我们捕获类型不匹配。没有什么可以阻止您将错误的复制或比较函数传递给特定类型的通用例程。
insertIntValue
这给我们带来了第二个缺点——您仍然需要为您想要支持的每种数据类型(insertFloatValue
、insertStringValue
、insertMyObnoxiousDataTypeValue
等)以及所有委托编写一个类型感知接口(例如上面的函数)。部分是为了避免类型安全问题,部分是因为我们的“通用”函数确实不是为了直接调用而设计的。例如,该newNode
函数需要一个指针作为第一个参数,这意味着您不能编写类似
struct node *n = newNode(10, copyInt);
或者
struct node *n = newNode(3.14159, copyDouble);
IOW,您不能将文字作为第一个参数传递;你必须传递一个对象的地址。
第三个主要缺点是您最终会进行大量内存管理,这很痛苦。您必须创建输入的副本;否则,您最终会将相同的指针值(传递给 的指针值newNode
)分配给树中的每个节点。每个都malloc
必须有一个匹配free
项,否则您最终会泄漏大量内存。您必须遵守如何分配和解除分配数据项的规定。
坦率地说,用 C 语言构建健壮的通用容器是一件非常痛苦的事情。这样做的唯一真正原因是您可以真正体会到 C++ 中的模板以及 Java 和 C# 中的泛型的价值。