【问题标题】:Linked List using Void* pointers使用 Void* 指针的链表
【发布时间】:2013-07-03 22:35:54
【问题描述】:

我想在 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->data) 第二个是双倍的,依此类推... 相反,是否有任何通用的方法可以简单地显示数据而不用担心数据类型?

我知道您可以使用模板来实现这一点,但如果我只能在 C 中执行此操作呢?

谢谢, 凯达

【问题讨论】:

  • 啊,C/C++,没有可怕的模板和充满就业机会的神奇语言......
  • C 中void * 的全部意义在于您知道类型。在不知道类型的情况下,您无法明智地“显示数据”。你甚至不知道要显示多少数据。
  • 为什么不使用boost::any
  • 不可能是C; C 中没有 new 运算符。
  • 同意Kerrek SB。这种代码产生了大量的就业机会。主要是在修复错误。

标签: c++ pointers void-pointers


【解决方案1】:

通用列表的全部意义在于您可以在其中存储任何内容。但是你必须现实一点……你仍然需要知道什么你在里面放什么。因此,如果您要将混合类型放入列表中,那么您应该考虑使用 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++ 是两种不同的语言,它们有一些重叠。尽量不要把它们放在一个词中,就好像它们是一种语言一样。

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 其实也有可能使用宏来实现具有特定类型的“节点”。
    • 但是您必须为要放入链接列表的每种不同类型创建一个新宏。
    • @MatsPetersson 但这与为每个特定指针类型键入强制转换相同,只是看起来更好。
    • 不,我的意思是你可以做一个链表声明,它接受一个类型作为参数,并通常存储该类型的列表 - 一旦声明了列表,所有列表项都包含你是什么期待(例如 intdoublestring)。但是,当然,你不能在同一个列表中存储一堆不同的东西——但无论如何,这在大多数情况下都是一个非常无用的功能,因为你实际上需要知道你存储的是什么,然后进行转换以检索它。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      这有点像把所有的餐具都放在抽屉里,但不是把刀放在一个槽里,叉子放在另一个槽里,勺子放在第三个槽里,茶匙放在中间的小槽里,我们只是将它们全部插入它们在将它们放入时碰巧降落的任何地方,然后想知道为什么当您将手伸进去并捡起东西时,您不知道会得到什么。

      C++ 的全部意义在于它允许您声明“使用任意内容执行操作”的模板和类。由于上面的代码使用了new,它不会编译为C。所以让它持有一个非描述性的指针(或者甚至首先将数据存储为指针)是没有意义的。

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

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

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

      但是,在我研究过的所有链表、二叉树、哈希表等中,99% 只包含一件事。在一个列表中存储“任意”的东西通常没有那么有用。

      【讨论】:

        【解决方案5】:

        下面的答案是针对 C++ 而不是 C。C++ 允许你想要的东西,而不是你想要的方式。我解决问题的方法是使用 virtual 关键字的内置功能。

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

        #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以了解更多信息。

        【讨论】:

          猜你喜欢
          • 2016-01-08
          • 2014-05-30
          • 1970-01-01
          • 2017-03-09
          • 2013-12-18
          • 2012-02-07
          • 2015-09-22
          • 1970-01-01
          • 2021-07-22
          相关资源
          最近更新 更多