【问题标题】:When to use a void pointer?什么时候使用空指针?
【发布时间】:2009-06-22 05:31:42
【问题描述】:

我了解在 malloc 实现中使用 void 指针。

void* malloc  ( size_t size );

谁能提出其他原因或提供一些在实践中有用的场景。

谢谢

【问题讨论】:

    标签: c++ c pointers


    【解决方案1】:

    void* 使用的一个很好的场景是当您想要实现任何通用 ADT 时,以防您只是不知道它将保留和处理什么数据类型。例如,如下所示的链表:

    typedef struct node_t node;
    struct
    {
        void* data;
        node* prev, next;
    } node_t;
    
    typedef struct list_t list;
    typedef void* (func)(void*) cpy_func;
    typedef void (func)(void*) del_func;
    struct
    {
       node* head, tail, curr;
       cpy_func copy;
       del_func delete;
    } list_t;
    
    initializeLinkedList(cpy_func cpy, del_func del);
    //here you keep going defining an API
    

    例如,您将在此将初始化函数指针传递给其他函数,这些函数将能够将您的数据类型复制到您的列表并在之后释放它。因此,通过使用void*,您可以使您的列表更加通用。

    我认为 void* 保留在 C++ 中只是因为向后兼容,因为在 C++ 中你有更安全和更复杂的方法来实现相同的结果,如模板、仿函数等,而且你不需要使用 malloc 而编程 C++。

    关于 C++,我没有任何具体有用的例子。

    【讨论】:

    • 谢谢 Artem。就是这样。在 C 的情况下,同样的事情可以使用 char* 来实现。我无法想到不能使用 char* 的场景。 (适用于 malloc )。
    • C++ 的习惯是使用模板,标准库提供了很多有用的模板(包括链表)。
    • 使用void* 代替char* 提供了一定的类型安全措施。您是在告诉编译器“我永远不应该被允许取消引用这些指针之一。”
    • @David 在我看来,实现相同的 C++ 习语是 inheritancetemplates
    【解决方案2】:

    如果您正在与 C 代码交互并且需要通过 C++ 对象,但 C 库将只采用通用指针,那么当您检索指针时,您需要将其重新转换为正确的类型。

    Void 指针可能不应该经常使用,但当您尝试使用可处理任意指针且并不真正关心该内存表示什么数据的库函数时,它们会有所帮助。

    【讨论】:

    • 我认为你是对的,C++ 调用 C 用例是当今最常见的 void* 用例。
    【解决方案3】:

    void 指针应该在数据块的内容不重要的任何时候使用。例如,当复制数据时,会复制内存区域的内容,但数据的格式并不重要。

    对于在内存块上操作而无需使用void 指针了解内容的函数,向用户阐明了设计,以便他们知道该函数不关心任何数据格式。 当函数实际上与内容无关时,通常会使用一个编码来使用char * 来处理内存块。

    【讨论】:

      【解决方案4】:

      在 C++ 中,我发现 void* 指针最引人注目的用例是为代码提供在他们已经使用的对象上存储任意“用户数据”的选项。

      假设您编写了一个代表Car 的类,用于在对Car 对象(交通模拟、租车库存等)执行有用操作的软件中。现在假设您发现自己的应用程序想要跟踪Car 的主干的任意内容。存储在后备箱中的详细信息对Car 类并不重要,可以是任何东西——这实际上取决于使用 Car 类的应用程序的目的。输入 void* 指针。

      class Car
      {
          public:
      
              // Existing methods of your Car class
      
              void setContentsOfTrunk(void* contentsOfTrunk);
              void* contentsOfTrunk() const;
      
          private:
      
              void* m_contentsOfTrunk;
      }
      

      现在,任何使用Car 类的应用程序都可以选择将任意数据对象附加到现有的Car 对象,这样就可以从任何具有Car 对象的代码中获取它。主干的内容“与”Car 对象一起“旅行”,无论它在您的代码中的什么位置。

      在这种情况下使用 void* 有两种选择。

      首先是根据主干内容对象的类型来模板化你的类:

      template <class TrunkContentsType>
      class Car
      {
          public:
      
              // Existing methods of your Car class
      
              void setContentsOfTrunk(TrunkContentsType contentsOfTrunk);
              TrunkContentsType contentsOfTrunk() const;
      
          private:
      
              TrunkContentsType m_contentsOfTrunk;
      }
      

      这似乎是不必要的侵入性。主干内容的类型仅对应用程序很重要。使用 Car 对象的算法和数据结构并不关心后备箱里有什么。通过对类进行模板化,您可以强制使用该类的应用程序为主干内容选择一种类型,但在许多情况下,应用程序也不关心主干内容。

      第二种选择是从 Car 派生一个新类,它为主干内容添加一个数据成员和访问器:

      class Car
      {
          public:
      
              // Existing methods of your Car class
              // No methods having anything to do with trunk contents.
      
          private:
      
              // No data member representing trunk contents.
      }
      
      class CarWithTrunkContents
      {
          public:
      
              // Existing methods of your Car class
      
              void setContentsOfTrunk(TrunkContentsType contentsOfTrunk);
              TrunkContentsType contentsOfTrunk() const;
      
          private:
      
              TrunkContentsType m_contentsOfTrunk;
      }
      

      新的CarWithTrunkContents 类是一个特定于应用程序的类,它添加了应用程序需要在汽车上存储行李箱内容的类型的数据成员。这似乎也不必要地重量级。为什么必须派生一个全新的类来添加不影响类行为的额外数据?如果使用Car 类的应用程序想要存储主干内容是相当普遍的,为什么要强制每个应用程序为其特定类型的主干内容派生一个新类?

      最后,虽然我设计的主干内容示例可能生动地描绘了随Car 对象传播的任意主干内容,但实际上您可能会提供一种更通用的机制来将特定于应用程序的数据附加到@987654335 @:

      class Car
      {
          public:
      
              // Existing methods of your Car class
      
              void setUserData(void* userData);
              void* userData() const;
      
          private:
      
              void* m_userData;
      }
      

      这样,应用程序可以附加一个表示行李箱内容的对象,或者一个表示驾驶执照和注册的对象,或者一个表示租赁协议的对象,等等。我见过这种 void* 指针被称为“userData”(即类的用户理解)、“blindData”(即该类对它所承载的对象的内容是盲目的)或“applicationData”(即应用程序定义的类型和用途的数据)。

      【讨论】:

        【解决方案5】:

        了解 void * 和其他 C 主题的好方法是在 iTunes-U 上观看精彩的斯坦福“编程范式”的前半部分。它确实非常奇妙地解释了 void * (C 泛型)和指针!它肯定帮助我更好地学习 C...

        如果您希望能够在函数中接受不同类型的数据,最大的用途之一是使用 void *。 (这里是一个例子:http://142.132.30.225/programming/node87.html

        以下是您可以使用它们的另一个示例:

          int i;
          char c;
          void *the_data;
        
          i = 6;
          c = 'a';
        
          the_data = &i;
          printf("the_data points to the integer value %d\n", *(int*) the_data);
        
          the_data = &c;
          printf("the_data now points to the character %c\n", *(char*) the_data);
        

        如果您不想观看免费的斯坦福课程,我建议您使用谷歌搜索 void pointer 并阅读那里的所有材料。

        【讨论】:

        • 是的,我们明白了,但为什么不直接创建具有适当指针类型的变量呢?为什么要费心去追踪 void* 中存储的数据类型是什么?为什么不直接使用 int* 或 char*?
        • void * 在您想要创建通用函数(接受/返回不同类型的函数)时最有用。您可以拥有一个可以接受 int、double 或 long 的函数,而不必为每个函数都有一个函数!
        【解决方案6】:

        它通常用于数字代码,例如 C 根求解器函数可能如下所示:

        double find_root(double x0, double (*f)(double, void*), void* params)
        {
        /* stuff */
        y = f(x, params);
        /* other stuff */
        }
        

        paramsf 转换为它知道的某个结构,但find_root 不知道。

        【讨论】:

        • 更一般地说,void* 为回调函数和其他各种需要向函数传递参数但不需要知道该参数类型的事情提供了很好的参数。跨度>
        【解决方案7】:

        另一个使用 void * 实现的 C“泛型”示例是标准 qsort 函数:

        void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
        

        您可以对任何类型的数组进行排序:int、long、double、char * 或一些结构指针...

        【讨论】:

          【解决方案8】:

          当您编写需要在多个操作系统上运行并且需要与底层框架 API 完全无关的代码时,空指针很有用。

          例如,OS X、Windows 和 Linux 都有窗口对象的基本概念,但它们都有很大的不同。所以我有通用代码将它们作为 void* 传递,然后是平台特定的实现,将 void* 转换为本机类型(HWND 等)。

          但是,是的,正如其他人在这个帖子中所说的那样,除非必要,否则肯定要避免这种事情。

          【讨论】:

            【解决方案9】:

            在与 C 接口之后,我发现自己只在需要调试/跟踪某些代码并想知道某个指针的地址时使用 void 指针。

            SomeClass * myInstance;
            // ...
            std::clog << std::hex << static_cast< void* >(myInstance) << std::endl;
            

            会打印类似的东西

            0x42A8C410
            

            而且,在我看来,它很好地记录了我正在尝试做的事情(知道指针地址,而不是关于实例的任何内容)

            【讨论】:

            • 你考虑过使用调试器吗?
            【解决方案10】:

            使用 void 指针有很大的优势。 指针变量是存储另一个变量地址的变量。 示例:

            int a;
            int *x= &a;
            

            现在'x'存储整型变量的地址。

            但是这个失败了:

            float f;
            int *x = &f;
            

            因为整数指针变量只能存储整数变量地址。 以同样的方式适用于其他数据类型。

            当你使用 void * pointer 时,它为存储任何 TYPE 变量的地址提供了优势。

            void *pointer = &i;
            void *pointer = &f;
            

            在检索时必须尊重它。

            *((int*)pointer)
            

            所以,小心使用空指针。

            这可能对你有帮助,谢谢。

            【讨论】:

              【解决方案11】:

              void * 确实是一个 C 主义,它允许 C 做一些它在其他情况下无法合理做的事情。

              char * 不能用于任何事情,因为不同的平台可以生成不同类型的指针——char * 的处理方式不一定与void * 相同(甚至大小相同)。

              因此,当数据类型在 C 中未知(或者是多态的或其他动态的)时,void * 允许您生成正确的底层指针类型 - 可以正确指向任何内容的指针类型。

              在 C++ 中,void * 通常不应该出现,除非在以一种或另一种形式与遗留 C 代码交互的上下文中。

              【讨论】:

              • 那么在 c++ 中你用什么来代替 void* 呢?
              • 适当的指针类型或指向父类类型的指针。
              【解决方案12】:

              看看sqlite3_exec()。您启动 SQL 查询并希望以某种方式处理结果(将它们存储到容器中)。您调用 sqlite3_exec() 并将回调指针和 void* 指针传递给您想要的任何对象(包括容器)。当 sqlite3_exec() 运行时,它会为每个检索到的行调用回调并将该 void* 指针传递给它,以便回调可以强制转换指针并执行您想要的任何操作。

              重要的是 sqlite3_exec() 不关心回调做什么以及传递什么指针。 void* 正是针对此类指针的。

              【讨论】:

                【解决方案13】:
                int (*f) (void);
                f =(void*) getprocaddress(dll,"myfunction");
                

                让编译器开心

                【讨论】:

                • 这不会编译。如何让编译器满意还不清楚。
                猜你喜欢
                • 2010-10-14
                • 1970-01-01
                • 2012-02-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多