Wann verwendet man einen Void-Zeiger?

Lesezeit: 8 Minuten

Benutzeravatar von Mac13
Mac13

Ich verstehe die Verwendung des void-Zeigers für die Malloc-Implementierung.

void* malloc  ( size_t size );

Kann jemand andere Gründe vorschlagen oder einige Szenarien angeben, in denen es in der Praxis nützlich ist.

Vielen Dank

Benutzeravatar von Artem Barger
Artem Barger

Ein gutes Szenario void* Verwenden Sie es, wenn Sie generische ADTs implementieren möchten, falls Sie einfach nicht wissen, welchen Datentyp es behalten und behandeln wird. Beispielsweise eine verknüpfte Liste wie die folgende:

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

Hier übergeben Sie beispielsweise Initialisierungsfunktionszeiger an andere Funktionen, die Ihren Datentyp in Ihre Liste kopieren und anschließend freigeben können. Also durch Nutzung void* Sie machen Ihre Liste allgemeiner.

Ich finde void* blieb nur wegen der Abwärtskompatibilität in C++, da Sie in C++ sicherere und ausgefeiltere Möglichkeiten haben, dasselbe Ergebnis wie Templates, Funktoren usw. zu erzielen, und Sie beim Programmieren von C++ nicht malloc verwenden müssen.

In Bezug auf C++ habe ich keine spezifischen nützlichen Beispiele.

  • Danke Artem. Das ist das Ding. auch im Fall von C kann dasselbe mit char* implementiert werden. Ich konnte mir kein Szenario vorstellen, in dem stattdessen kein char* verwendet werden kann. (gilt für malloc ).

    – Mac13

    24. Juni 2009 um 9:22 Uhr

  • Das C++-Idiom besteht darin, Vorlagen zu verwenden, und die Standardbibliothek bietet viele nützliche Vorlagen (einschließlich einer verknüpften Liste).

    – David Thornley

    26. Oktober 2009 um 13:56 Uhr

  • Verwenden void* Anstatt von char* bietet ein gewisses Maß an Typensicherheit. Sie sagen dem Compiler: “Ich sollte niemals berechtigt sein, einen dieser Zeiger zu dereferenzieren.”

    – Phil Müller

    26. Oktober 2009 um 13:56 Uhr

  • @David Meiner Meinung nach ist das C++-Idiom, um dasselbe zu erreichen inheritance und templates

    – Rohan Prabhu

    19. Februar 2011 um 19:26 Uhr

Wenn Sie eine Schnittstelle mit C-Code haben und ein C++-Objekt durchlaufen müssen, eine C-Bibliothek jedoch nur einen generischen Zeiger verwendet, müssen Sie ihn beim Abrufen des Zeigers in den richtigen Typ umwandeln.

Void-Zeiger sollten wahrscheinlich nicht sehr oft verwendet werden, aber sie können hilfreich sein, wenn Sie versuchen, eine Bibliotheksfunktion zu verwenden, die mit beliebigen Zeigern arbeitet und sich nicht wirklich darum kümmert, welche Daten durch diesen Speicher dargestellt werden.

  • Ich denke, Sie haben Recht, der C++-Calling-C-Anwendungsfall ist heutzutage der häufigste für void*.

    – sparc_spread

    8. Juli 2019 um 15:52 Uhr

Gerhards Benutzeravatar
Gerhard

void Zeiger sollten immer dann verwendet werden, wenn der Inhalt eines Datenblocks nicht wichtig ist. Beispielsweise wird beim Kopieren von Daten der Inhalt eines Speicherbereichs kopiert, aber das Format der Daten ist nicht wichtig.

Für Funktionen, die auf Speicherblöcken arbeiten, ohne den Inhalt verstehen zu müssen void Zeiger verdeutlicht das Design für Benutzer, damit sie wissen, dass die Funktion kein Datenformat berücksichtigt. Oft funktioniert ein codierter, um einen zu nehmen char * um Speicherblöcke zu behandeln, wenn die Funktion tatsächlich inhaltsunabhängig ist.

Joshs Benutzeravatar
Josch

In C++ habe ich festgestellt, dass der überzeugendste Anwendungsfall für void*-Zeiger darin besteht, Code die Option zu geben, beliebige „Benutzerdaten“ auf einem Objekt zu speichern, das sie bereits verwenden.

Angenommen, Sie haben eine Klasse geschrieben, die a repräsentiert Carzur Verwendung in Software, die nützliche Dinge mit macht Car Objekte (Verkehrssimulation, Mietwagenbestand, was auch immer). Nehmen wir nun an, Sie befinden sich in einer Situation, in der Ihre Anwendung den willkürlichen Inhalt des Stamms von a verfolgen möchte Car. Die Details dessen, was im Kofferraum aufbewahrt wird, sind für den nicht wichtig Car Klasse und kann alles sein – es hängt wirklich vom Zweck der Anwendung ab, die die Klasse Auto verwendet. Geben Sie den Zeiger void* ein.

class Car
{
    public:

        // Existing methods of your Car class

        void setContentsOfTrunk(void* contentsOfTrunk);
        void* contentsOfTrunk() const;

    private:

        void* m_contentsOfTrunk;
}

Nun, jede Anwendung mit Ihrem Car Klasse hat die Möglichkeit, ein beliebiges Datenobjekt an ein vorhandenes anzuhängen Car Objekt so, dass es von jedem Code erhalten werden kann, der die hat Car Objekt. Der Inhalt des Kofferraums „reist mit“. Car Objekt, wo immer es in Ihrem Code steht.

In diesem Fall gibt es zwei Alternativen zur Verwendung von void*.

Die erste besteht darin, Ihre Klasse basierend auf dem Typ des Stamminhaltsobjekts als Vorlage zu erstellen:

template <class TrunkContentsType>
class Car
{
    public:

        // Existing methods of your Car class

        void setContentsOfTrunk(TrunkContentsType contentsOfTrunk);
        TrunkContentsType contentsOfTrunk() const;

    private:

        TrunkContentsType m_contentsOfTrunk;
}

Dies erscheint unnötig invasiv. Die Art des Inhalts des Kofferraums ist nur für die Anwendung wichtig. Algorithmen und Datenstrukturen, die mit Car-Objekten arbeiten, kümmern sich nicht darum, was sich im Kofferraum befindet. Indem Sie die Klasse mit Vorlagen versehen, zwingen Sie Anwendungen, die die Klasse verwenden, einen Typ für Trunk-Inhalte auszuwählen, aber in vielen Fällen kümmern sich die Anwendungen auch nicht um Trunk-Inhalte.

Die zweite Alternative besteht darin, eine neue Klasse von Car abzuleiten, die ein Datenelement und Accessoren für Kofferrauminhalte hinzufügt:

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;
}

Das neue CarWithTrunkContents Die Klasse ist eine anwendungsspezifische Klasse, die ein Datenmitglied des Typs hinzufügt, den die Anwendung benötigt, um den Inhalt des Kofferraums im Auto zu speichern. Dies wirkt auch unnötig schwergewichtig. Warum müssen Sie eine ganz neue Klasse ableiten, um ein zusätzliches Datenelement hinzuzufügen, das das Verhalten der Klasse nicht beeinflusst? Und wenn es ziemlich üblich ist für Anwendungen, die das verwenden Car class Trunk-Inhalte speichern möchte, warum sollte man dann jede Anwendung zwingen, eine neue Klasse für ihre spezielle Art von Trunk-Inhalten abzuleiten?

Schließlich, während mein erfundenes Beispiel von Kofferrauminhalten vielleicht ein lebhaftes Bild von beliebigem Kofferrauminhalt zeichnet, der mit reist Car -Objekt, würden Sie in der Praxis wahrscheinlich einen noch allgemeineren Mechanismus zum Anhängen anwendungsspezifischer Daten an das bereitstellen Car:

class Car
{
    public:

        // Existing methods of your Car class

        void setUserData(void* userData);
        void* userData() const;

    private:

        void* m_userData;
}

Auf diese Weise kann eine Anwendung ein Objekt anhängen, das den Kofferrauminhalt darstellt, oder ein Objekt, das den Führerschein und die Zulassung darstellt, oder ein Objekt, das den Mietvertrag darstellt, oder was auch immer. Ich habe diese Art void*-Zeiger gesehen, die als „userData“ (dh vom Benutzer der Klasse verstanden), „blindData“ (dh die Klasse ist blind für den Inhalt des Objekts, das sie trägt) oder „applicationData“ ( dh Daten von Art und Zweck, die von der Anwendung definiert werden).

Eine GROSSARTIGE Möglichkeit, alles über void * und andere C-Themen zu erfahren, besteht darin, sich die erste Hälfte der fantastischen Stanford-Programmierparadigmen auf iTunes-U anzusehen. Es erklärt wirklich void * (C-Generika) und Zeiger im Allgemeinen fantastisch! Es hat mir definitiv geholfen, C besser zu lernen …

Eine der größten Anwendungen ist die Verwendung von void *, wenn Sie verschiedene Datentypen in einer Funktion akzeptieren möchten. (hier ein Beispiel: http://142.132.30.225/programming/node87.html)

Hier ist ein weiteres Beispiel dafür, wofür Sie sie verwenden können:

  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);

Wenn Sie sich die kostenlosen Stanford-Kurse nicht ansehen möchten, würde ich empfehlen, void pointer zu googeln und das gesamte Material dort zu lesen.

  • Ja, wir verstehen es, aber warum erstellen Sie nicht einfach Variablen mit dem entsprechenden Zeigertyp? Warum sich den Aufwand machen, herauszufinden, welche Art von Daten in einem Void* gespeichert sind? Warum verwendest du nicht einfach ein int* oder ein char*?

    – Kekoa

    22. Juni 2009 um 5:51 Uhr

  • void * sind am nützlichsten, wenn Sie eine generische Funktion erstellen möchten (eine, die verschiedene Typen akzeptiert/zurückgibt). Sie können eine Funktion haben, die Ints, Doubles oder Longs annehmen kann, ohne dass Sie für jede eine Funktion haben müssen!

    – mimu

    22. Juni 2009 um 6:38 Uhr

Benutzeravatar von quant_dev
quant_dev

Es wird häufig in numerischem Code verwendet, zum Beispiel könnte eine C-Root-Solver-Funktion so aussehen:

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

params wird von gegossen f zu einer Struktur, die es kennt, aber find_root nicht.

  • Ja, wir verstehen es, aber warum erstellen Sie nicht einfach Variablen mit dem entsprechenden Zeigertyp? Warum sich den Aufwand machen, herauszufinden, welche Art von Daten in einem Void* gespeichert sind? Warum verwendest du nicht einfach ein int* oder ein char*?

    – Kekoa

    22. Juni 2009 um 5:51 Uhr

  • void * sind am nützlichsten, wenn Sie eine generische Funktion erstellen möchten (eine, die verschiedene Typen akzeptiert/zurückgibt). Sie können eine Funktion haben, die Ints, Doubles oder Longs annehmen kann, ohne dass Sie für jede eine Funktion haben müssen!

    – mimu

    22. Juni 2009 um 6:38 Uhr

Benutzeravatar von Tomek Szpakowicz
Tomek Szpakowicz

Ein weiteres Beispiel für solche C-“Generika”, implementiert mit void *, ist eine Standard-qsort-Funktion:

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

Sie können ein Array beliebigen Typs sortieren: int, long, double, char * oder einige Strukturzeiger …

1408950cookie-checkWann verwendet man einen Void-Zeiger?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy