代码之家  ›  专栏  ›  技术社区  ›  Mushy

另一个空洞的话题;我只是想问一下,因为我很困惑

  •  0
  • Mushy  · 技术社区  · 7 年前

    好的,尽管有点混乱,但还是要把关于 void* ,书籍如 The C Programming Language (K&R) The C++ Programming Language (Stroustrup) . 我学到了什么?那个 无效* 是没有推断类型的泛型指针。它需要转换为任何定义的类型并打印 无效* 只需给出地址。

    我还知道什么? 无法取消引用,目前仍是 C/C++ 我从中发现了很多关于它的文章,但很少有人理解它。

    我理解它必须是这样的 *(char*)void* 但是对于一个 generic 指针是我必须知道我需要什么类型才能获取一个值。我是一名Java程序员;我理解泛型类型,但这是我很难解决的问题。

    所以我写了一些代码

    typedef struct node
    {
      void* data;
      node* link;
    }Node;
    
    typedef struct list
    {
       Node* head;
    }List;
    
    Node* add_new(void* data, Node* link);
    
    void show(Node* head);
    
    Node* add_new(void* data, Node* link)
    {
      Node* newNode = new Node();
      newNode->data = data;
      newNode->link = link;
    
      return newNode;
    }
    
    void show(Node* head)
    {
      while (head != nullptr)
      {
          std::cout << head->data;
          head = head->link;
      }
    }
    
    int main()
    {
      List list;
    
      list.head = nullptr;
    
      list.head = add_new("My Name", list.head);
    
      list.head = add_new("Your Name", list.head);
    
      list.head = add_new("Our Name", list.head);
    
      show(list.head);
    
      fgetc(stdin);
    
      return 0;
    }
    

    我稍后会处理内存释放。假设我不了解存储在 无效* ,如何获取值? This 意味着我已经需要知道类型,并且 this 没有揭示 无效* 当我遵循 here 虽然仍然没有理解。

    我为什么期待 无效*

    5 回复  |  直到 7 年前
        1
  •  5
  •   R Sahu    7 年前

    我稍后会处理内存释放。假设我不了解存储在void*中的类型,我如何获取值?

    你不能。你 必须 在取消引用指针之前,请先了解指针可以强制转换到的有效类型。

    1. 如果您能够使用C++17编译器,则可以使用 std::any .
    2. boost::any .
        2
  •  1
  •   Fabien Bouleau    7 年前

    与Java不同,您在C/C++中使用内存指针。没有任何封装。这个 void * 类型表示变量是内存中的地址。任何东西都可以存放在那里。类型如下 int * 你告诉编译器你指的是什么。此外,编译器知道类型的大小(例如 int 无效* .

    简而言之,你是在裸机工作。这些类型是编译器指令,不包含运行时信息。它也不会跟踪您动态创建的对象。它只是内存中的一个段,分配给您最终可以存储的位置 任何东西 .

        3
  •  0
  •   Ben Langhinrichs    7 年前

    使用void*的主要原因是可能会指向不同的事物。因此,我可以传入int*或Node*或任何其他内容。但是除非你知道它的类型或长度,否则你什么都做不了。

    但是如果你知道长度,你可以在不知道类型的情况下处理指向的内存。将其转换为字符*是因为它是一个单字节,所以如果我有一个空*和一些字节,我可以将内存复制到其他地方,或者将其归零。

    此外,如果它是一个指向类的指针,但您不知道它是父类还是继承类,那么您可以假设它是父类还是继承类,并在数据中找到一个标志来告诉您是哪个。但不管怎样,当你想做的远远不止是把它传递给另一个函数时,你需要把它转换成某种东西。char*是最容易使用的单字节值。

        4
  •  0
  •   Swift - Friday Pie    7 年前

    你的困惑源于处理Java程序的习惯。Java代码是虚拟机的一组指令,其中RAM的功能被赋予一种数据库,该数据库存储每个对象的名称、类型、大小和数据。你们现在学习的编程语言应该被编译成CPU指令,其内存组织与底层操作系统相同。C和C++语言使用的现有模型是建立在大多数流行操作系统之上的抽象模型,代码在为该平台和操作系统编译后可以有效地工作。当然,除了C++中著名的RTTI之外,该组织不涉及关于类型的字符串数据。

    事实上,C++库包含大量容器类模板,如果它们是由ISO标准定义的,那么这些模板是可用和可移植的。标准的3/4只是对通常称为STL的库的描述。使用它们比使用裸指针更好,除非您出于某种原因想要创建自己的容器。对于特定任务,仅提供C++17标准 std::any 类,以前存在于boost库中。当然,可以重新实现它,或者在某些情况下,用 std::variant .

        5
  •  0
  •   Yakk - Adam Nevraumont    7 年前

    假设我不了解存储在void*中的类型,我如何获取值

    你没有。

    您可以做的是记录存储在 void* .

    在里面 无效* 用于通过一个抽象层传递指向某个对象的二进制数据块,并在另一端接收它,将其转换回代码知道将要传递的类型。

    void do_callback( void(*pfun)(void*), void* pdata ) {
      pfun(pdata);
    }
    
    void print_int( void* pint ) {
      printf( "%d", *(int*)pint );
    }
    
    int main() {
      int x = 7;
      do_callback( print_int, &x );
    }
    

    在这里,我们忘记了 &x ,传递它 do_callback .

    它稍后传递给代码 在…内 do_回调 或者在其他地方 那就是 无效* 实际上是一个 int* . 因此它将其回溯并将其用作 int .

    这个 无效* 和消费者 void(*)(void*) 无效* 在一个知道它是一个 .


    在C++中,您可以使用 无效*

    假设您想要一个指向任何可打印内容的指针。如果可以的话,有些东西是可以打印的 << 到a std::ostream .

    struct printable {
      void const* ptr = 0;
      void(*print_f)(std::ostream&, void const*) = 0;
    
      printable() {}
      printable(printable&&)=default;
      printable(printable const&)=default;
      printable& operator=(printable&&)=default;
      printable& operator=(printable const&)=default;
    
      template<class T,std::size_t N>
      printable( T(&t)[N] ):
        ptr( t ),
        print_f( []( std::ostream& os, void const* pt) {
          T* ptr = (T*)pt;
          for (std::size_t i = 0; i < N; ++i)
            os << ptr[i];
        })
      {}
      template<std::size_t N>
      printable( char(&t)[N] ):
        ptr( t ),
        print_f( []( std::ostream& os, void const* pt) {
          os << (char const*)pt;
        })
      {}
      template<class T,
        std::enable_if_t<!std::is_same<std::decay_t<T>, printable>{}, int> =0
      >
      printable( T&& t ):
        ptr( std::addressof(t) ),
        print_f( []( std::ostream& os, void const* pt) {
          os << *(std::remove_reference_t<T>*)pt;
        })
      {}
      friend
      std::ostream& operator<<( std::ostream& os, printable self ) {
        self.print_f( os, self.ptr );
        return os;
      }
      explicit operator bool()const{ return print_f; }
    };
    

    我刚才做的是C++中一种叫做“类型擦除”的技术(与Java类型擦除有点类似)。

    void send_to_log( printable p ) {
      std::cerr << p;
    }
    

    Live example .

    在这里,我们创建了一个特殊的“虚拟”界面,用于在一种类型上打印的概念。

    该类型不需要支持任何实际的接口(没有二进制布局要求),它只需要支持某种语法。

    我们为任意类型创建了自己的虚拟调度表系统。

    这在C++标准库中使用。在里面 std::function<Signature> ,和 std::any

    std::任何 无效* 它知道如何销毁和复制其内容,如果您知道类型,则可以将其转换回原始类型。您还可以查询它并询问它是否为特定类型。

    混合 使用上述类型擦除技术,您可以使用任意duck类型的接口创建常规类型(其行为类似于值,而不是引用)。