2017-09-27 94 views
0

好吧,虽然关于void*The C Programming Language (K&R)The C++ Programming Language (Stroustrup)这样的书籍的详细资料Stack很混乱。我学到了什么? void*是一个没有推断类型的泛型指针。它需要转换为任何已定义的类型,并且打印void*只是产生地址。另一个void *话题;我只是不得不问,因为我很困惑

我还知道什么? void*不能被解除引用,并且迄今为止仍然是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; 
} 

后,我会处理的内存释放。假设我不知道存储在void*中的类型,我该如何获得该值? This意味着我已经需要知道类型,并且this没有透露关于void*的一般性质,而我遵循什么是here虽然仍然没有理解。

为什么我期待void*合作,编译器会自动抛出隐藏在堆或堆栈的某个寄存器中的类型?

+1

我认为'void *'的使用是:Window的CWnd类可以容纳'void * data'。它不知道这些数据是什么,也不关心。它对这个'data'没有做任何事情,它只是对我有用。同时,我的代码可以将'thingamabob'存储到这个'data'中。我的代码假设'data'始终保存'thingamabob',这很好,因为我的代码是访问这个'data'成员的唯一代码。我的代码“知道”这个类型,但是“CWnd”没有。 –

回答

5

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

你不行。您必须知道在取消引用它之前知道指针可以投射到的有效类型。

以下是使用泛型类型几个选项:

  1. 如果你能使用C++编译器17,可以使用std::any
  2. 如果您能够使用boost库,则可以使用boost::any
+0

有没有办法在c或C++中练习'纯粹'泛型? – Mushy

+2

@穆什,不,不是核心语言。有尝试以['std :: any'](http://en.cppreference.com/w/cpp/utility/any)和['boost :: any'](http ://www.boost.org/doc/libs/1_61_0/doc/html/any.html) –

+1

@RSahu在C++ 17中有'std :: any' – Swift

1

与Java不同,您正在使用C/C++中的内存指针。没有任何封装。 void *类型表示变量是内存中的地址。任何东西都可以存储在那里。使用类似int *的类型,您可以告诉编译器您所指的是什么。除了编译器知道类型的大小(比如说int的4个字节),并且在这种情况下(粒度/内存对齐),地址将是4的倍数。最重要的是,如果您为编译器提供类型,它将在编译时执行一致性检查。没有。这不会发生在void *

简而言之,您正在使用裸机。这些类型是编译器指令,不包含运行时信息。它也不跟踪你动态创建的对象。它只是内存中的一个分区,您可以最终存储什么东西

0

使用void *的主要原因是可能指向不同的东西。因此,我可能传入一个int *或Node *或其他东西。但是除非你知道类型或长度,否则你无法做任何事情。

但是,如果你知道长度,你可以在不知道类型的情况下处理指向的内存。使用它作为char *是因为它是单个字节,因此如果我有void *和多个字节,我可以将内存复制到其他地方,或者将其归零。

此外,如果它是一个指向类的指针,但不知道它是父类还是继承类,那么您可以假设一个并在数据中找出一个标志,告诉您哪一个标志。但是不管怎么样,当你想要做的事情远远超过将它传递给另一个功能时,你需要将它作为某种东西。 char *只是使用的最简单的单字节值。

0

您习惯于处理Java程序而导致您的困惑。 Java代码是为虚拟机设置的指令,其中RAM的功能被赋予一种数据库,该数据库存储每个对象的名称,类型,大小和数据。你现在正在学习的编程语言是为了编译成CPU的指令而编写的,与底层操作系统具有相同的内存组织。 C和C++语言使用的现有模型是在大多数流行操作系统的基础上构建的一些抽象方式,代码将在为该平台和操作系统编译后有效地工作。当然,组织不涉及有关类型的字符串数据,除了C++中着名的RTTI。

对于你的情况RTTI不能直接使用,除非你会在你的裸指针上创建一个包装器来存储数据。

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

0

假设我已经没有存储在void *的类型,我该如何摆脱

你没价值的理解。

你可以做的是记录存储在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泰德YPE,传递给do_callback

它以后传给代码do_callback或其他地方知道void*实际上是一个int*。所以它将它重新用作int

void*和消费者void(*)(void*)被耦合。上面的代码是“证明是正确的”,但证明不在于类型系统;相反,这取决于我们只在知道它是int*的情况下使用void*的事实。


在C++中,您可以同样使用void*。但你也可以看中。

假设你想要一个指向任何可打印的指针。如果它可以是<<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::anyvoid*知道如何销毁和复制其内容,如果知道该类型,则可以将其转换回原始类型。你也可以查询它并询问它是否是特定的类型。

std::any与上述type-erasure techinque混合让您可以使用任意鸭型接口创建常规类型(表现为类似值而不是引用)。