使用Void *指针的链接列表

时间:2013-07-03 22:35:55

标签: c++ pointers void-pointers

我想在C / C ++中创建一个通用链表(不使用C ++模板)。 我写了以下简单的程序,它现在工作正常 -

typedef struct node
{
    void *data;
    node *next;
}node;


int main()
{
   node *head = new node();

   int *intdata = new int();
   double *doubledata = new double();

   char *str = "a";
   *doubledata = 44.55;
   *intdata = 10;

   head->data = intdata;

   node *node2 = new node();
   node2->data = doubledata; 
   head->next = node2;

   node *node3 = new node();
   node3->data = str;
   node3->next = NULL;
   node2->next = node3;

   node *temp = head;
   if(temp != NULL)
   {
    cout<<*(int *)(temp->data)<<"\t";
    temp = temp->next;
   }
   if(temp != NULL)
   {
    cout<<*(double *)(temp->data)<<"\t";
    temp = temp->next;
   }
   if(temp != NULL)
   {
    cout<<*(char *)(temp->data)<<"\t";
    temp = temp->next;
   }
   return 0;
}

我的问题是 - 我需要知道我在上面的代码中打印的数据的数据类型。 例如 - 第一个节点是int所以我写 - *(int *)(temp-&gt; data) 第二是双重等等...... 相反,是否存在简单显示数据而不用担心数据类型的通用方法?

我知道你可以通过模板实现这一目标,但是如果我必须在C中做到这一点呢?

谢谢, 基达

5 个答案:

答案 0 :(得分:5)

通用列表的重点是您可以在其中存储任何内容。但你必须要现实......你仍然需要知道你正在使用什么。因此,如果您要在列表中放置混合类型,那么您应该查看使用Variant模式。也就是说,提供多种类型的类型。这是一个简单的变体:

typedef struct Variant
{
    enum VariantType
    {
        t_string,
        t_int,
        t_double
    } type;

    union VariantData
    {
        char*    strVal;
        int      intVal;
        double   doubleVal;
    } data;

} Variant;

然后你可以告诉自己“我正在我的void*列表中存储指向Variants的指针。这就是你在C中的表达方式。我假设当你说”C / C ++“时你的意思是'你'重新尝试编写C代码,但正在使用C ++编译器。不要忘记C和C ++是两种不同的语言,有一些重叠。尽量不要将它们放在一个单词中,就像它们是一种语言一样。

答案 1 :(得分:0)

在C中,实现泛型的唯一方法是使用void*,正如您已经在做的那样。不幸的是,这意味着没有简单的方法来检索链表的元素类型。你只需要知道它们。

答案 2 :(得分:0)

对于不同的数据类型,在内存中解释数据的方式是完全不同的 假设32位内存块有一些数据。当您将其类型化为int或float时,它将显示不同的值,因为它们都存储在不同的协议中。将某些数据保存在由void *类型的变量指向的内存中时,它不知道如何解释其内存块中的数据。因此,您需要对其进行类型转换以指定要读取数据的类型。

答案 3 :(得分:0)

这有点像把所有餐具都放在抽屉里,但不是把刀放在一个槽里,叉子放在另一个槽里,而是把勺子放在第三个槽里,茶匙放在中间的小槽里,我们只是当他们把它们放进去的时候把它们全部放在它们发生的任何地方,然后想知道为什么当你只是伸手去拿东西时,你不知道你会得到什么。

C ++的WHOLE POINT是它允许你声明“用任意内容做事”的模板和类。由于上面的代码使用new,因此它不会编译为C.所以没有必要使它保持一个非描述性指针(甚至首先将数据存储为指针)。

template<typename T> struct node
{
    T data;
    node<T> *next;
    node() : next(0) {};
};

不幸的是,如果你想在同一个列表中存储一组不同类型的数据,它仍然会变得更加混乱。如果你想这样做,你需要节点本身的一些东西来指示你已存储的内容。

自1985年我开始工作(并且可能在我找到工作之前几次)以来,我已经在列表中做了几次这样的事情。很多次,我做过某种“我会存储“std::map之类的任意数据,其名称与某些”内容“相关联。每次我使用这种功能时,都是因为我正在编写类似于编程语言的东西(例如配置脚本,Basic解释器,LisP解释器等),用它来存储可以有不同类型的“变量” (intdoublestring)或类似内容。我在其他地方看到了类似的东西,比如OpenGL在某些地方返回的数据是不同的类型,具体取决于你要求的内容,内部存储必须“知道”类型是什么。

但我所处理的所有链表,二进制树,哈希表等中有99%只包含一件事和一件事。将“任意”事物存储在单个列表中通常没那么有用。

答案 4 :(得分:0)

下面的答案是针对C ++,而不是C. C ++允许你想要的东西,而不是你想要的方式。我实现您的问题的方式是使用虚拟关键字的内置功能。<​​/ p>

这是一个独立的代码示例,无论实际派生类型如何,都会打印出不同的值:

#include <iostream>
#include <list>

class Base
{
public:

    virtual void Print() = 0;
};

class Derived1 : public Base
{
public:

    virtual void Print()
    {
        std::cout << 1 << std::endl; // Integer
    }
};

class Derived2 : public Base
{
public:

    virtual void Print()
    {
        std::cout << 2.345 << std::endl; // Double
    }
};

class Derived3 : public Base
{
public:

    virtual void Print()
    {
        std::cout << "String" << std::endl; // String
    }
};

int main(void)
{
    // Make a "generic list" by storing pointers to a base interface
    std::list<Base*> GenericList;
    GenericList.push_back(new Derived1());
    GenericList.push_back(new Derived2());
    GenericList.push_back(new Derived3());
    std::list<Base*>::iterator Iter = GenericList.begin();
    while(Iter != GenericList.end())
    {
        (*Iter)->Print();
        ++Iter;
    }

    // Don't forget to delete the pointers allocated with new above.  Omitted in example

    return 0;
}

另请注意,这样您就不需要实现自己的链接列表了。标准列表在这里工作得很好。但是,如果您仍想使用自己的列表,而不是存储void *data;,请存储Base *data;。当然,这可能是模板化的,但是你最终会再次达到标准。

阅读polymorphism了解详情。