In stark typisierten Sprachen wie Java und C # scheint void
(oder Void
) als Rückgabetyp für eine Methode Folgendes zu bedeuten:
Diese Methode gibt nichts zurück. Nichts. Keine Rückkehr. Sie erhalten von dieser Methode nichts.
Was wirklich seltsam ist, ist, dass in C void
als Rückgabetyp oder sogar als Methodenparametertyp bedeutet:
Es könnte wirklich alles sein. Sie müssten den Quellcode lesen, um dies herauszufinden. Viel Glück. Wenn es ein Zeiger ist, sollten Sie wirklich wissen, was Sie tun.
Betrachten Sie die folgenden Beispiele in C:
void describe(void *thing)
{
Object *obj = thing;
printf("%s.\n", obj->description);
}
void *move(void *location, Direction direction)
{
void *next = NULL;
// logic!
return next;
}
Offensichtlich gibt die zweite Methode einen Zeiger zurück, der per Definition beliebig sein kann.
Da C älter als Java und C # ist, warum haben diese Sprachen die void
Bedeutung "nichts" angenommen, während C sie als "nichts oder irgendetwas (wenn ein Zeiger)" verwendete?
void
während das Codebeispielvoid*
etwas völlig anderes verwendet.Object
in einem Fall, um eine eindeutige Unterscheidung zu treffen.dynamic
Typ, der selten verwendet wird?Antworten:
Das Schlüsselwort
void
(kein Zeiger) bedeutet in diesen Sprachen "nichts". Das ist konsequent.Wie Sie bemerkt haben,
void*
bedeutet "Zeiger auf alles" in Sprachen, die rohe Zeiger unterstützen (C und C ++). Dies ist eine unglückliche Entscheidung, denn wie Sie bereits sagten, bedeutetvoid
dies zwei verschiedene Dinge.Ich konnte den historischen Grund für die Wiederverwendung nicht finden
void
, um "nichts" und "irgendetwas" in verschiedenen Kontexten zu bedeuten, C tut dies jedoch an mehreren anderen Stellen. Zum Beispielstatic
hat unterschiedliche Zwecke in unterschiedlichen Kontexten. Es ist offensichtlich ein Präzedenzfall in der Sprache C, Schlüsselwörter auf diese Weise wiederzuverwenden, unabhängig davon, was man von der Praxis hält.Java und C # unterscheiden sich genug, um eine saubere Pause zu machen, um einige dieser Probleme zu beheben. Java und „sicher“ C # auch nicht zulassen , dass rohe Zeiger und tun muß , nicht einfach C - Kompatibilität (Unsafe C # tut Zeiger erlauben , aber die überwiegende Mehrheit von C # -Code nicht in diese Kategorie fallen). Auf diese Weise können sie Änderungen vornehmen, ohne sich um die Abwärtskompatibilität sorgen zu müssen. Eine Möglichkeit besteht darin, eine Klasse
Object
an der Wurzel der Hierarchie einzuführen, von der alle Klassen erben, sodass eineObject
Referenz dieselbe Funktion erfüllt,void*
ohne dass Typprobleme und die Verwaltung des unformatierten Speichers unangenehm sind.quelle
They also do not allow raw pointers and do not need easy C compatibility.
Falsch. C # hat Zeiger. Sie sind normalerweise (und sollten es normalerweise sein) ausgeschaltet, aber sie sind da.void
Gründe für die Wiederverwendung : Ein offensichtlicher Grund wäre, die Einführung eines neuen Schlüsselworts zu vermeiden (und möglicherweise vorhandene Programme zu beschädigen). Hätten sie ein spezielles Schlüsselwort (z. B.unknown
) eingeführt,void*
wäre dies ein bedeutungsloses (und möglicherweise unzulässiges) Konstrukt undunknown
wäre nur in Form von legalunknown*
.void *
,void
bedeutet „nichts“, nicht „alles“. Sie können a nicht dereferenzierenvoid *
, Sie müssen es zuerst in einen anderen Zeigertyp umwandeln.void
undvoid*
verschiedene Arten sind (eigentlichvoid
ist "kein Typ"). Es macht genauso viel Sinn, zu sagen, "dasint
Inint*
bedeutet etwas". Nein, sobald Sie eine Zeigervariable deklariert haben, sagen Sie: "Dies ist eine Speicheradresse, die etwas aufnehmen kann." Im Fall vonvoid*
sagen Sie: "Diese Adresse enthält etwas, aber aus dem Zeigertyp lässt sich nichts ableiten."void
undvoid*
sind zwei verschiedene Dinge.void
In C bedeutet genau das Gleiche wie in Java, das Fehlen eines Rückgabewerts. Avoid*
ist ein Zeiger ohne Typ.Alle Zeiger in C müssen dereferenziert werden können. Wenn Sie eine dereferenziert hätten
void*
, welchen Typ würden Sie erwarten? Denken Sie daran, dass C-Zeiger keine Informationen zum Laufzeit-Typ enthalten. Daher muss der Typ zur Kompilierungszeit bekannt sein.In diesem Kontext können Sie eine dereferenzierte Datei nur logisch
void*
ignorieren. Dies ist genau das Verhalten, das dervoid
Typ angibt.quelle
gcc
einverstanden mit Ihnen. Wenn Sie versuchen, den Wert zu verwenden,gcc
erstellen Sie eine Fehlermeldung mit der Aufschrifterror: void value not ignored as it ought to be
.Vielleicht wäre es sinnvoller, sich
void
den Rückgabetyp vorzustellen. Dann würde Ihre zweite Methode "eine Methode, die einen untypisierten Zeiger zurückgibt" lesen .quelle
void
etwa*
der Rückgabe von Sprachen wie ActionScript 3 entspricht, was einfach "jeder Rückgabetyp" bedeutet.void
ist keine Wildcard. Ein Zeiger ist nur eine Speicheradresse. Wenn Sie einen Typ vor*
"Hier ist der Datentyp, den Sie an der Speicheradresse erwarten können, auf die dieser Zeiger verweist" setzen. Puttingvoid
vor dem*
sagt den Compiler „Ich habe den Typen nicht kennen; return mir nur den rohen Zeiger, und ich werde herausfinden , was mit den Daten zu tun , dass es auf.“void*
Punkt auf eine "Leere" verweist. Ein nackter Zeiger, auf den nichts zeigt. Sie können es trotzdem wie jeden anderen Zeiger setzen.Lassen Sie uns die Terminologie ein wenig verschärfen.
Aus dem Online-Standard C 2011 :
Ein
void
Ausdruck hat keinen Wert (obwohl er Nebenwirkungen haben kann). Wenn ich eine Funktion definiert habe, die zurückgegeben werden sollvoid
, wie folgt:dann der Anruf
bewertet nicht zu einem Wert; Ich kann das Ergebnis nicht zuordnen, da es kein Ergebnis gibt.
EIN Zeiger auf
void
ist im Wesentlichen ein "generischer" Zeigertyp. Sie könnenvoid *
jedem anderen Objektzeigertyp einen Wert zuweisen, ohne dass eine explizite Umwandlung erforderlich ist (weshalb alle C - Programmierer Sie anschreien, weil Sie das Ergebnis von umgewandelt haben)malloc
).Sie können a nicht direkt dereferenzieren
void *
. Sie müssen es zuerst einem anderen Objektzeigertyp zuweisen, bevor Sie auf das Objekt zugreifen können, auf das verwiesen wird.void
Zeiger werden verwendet, um (mehr oder weniger) generische Schnittstellen zu implementieren; Das kanonische Beispiel ist dasqsort
Bibliotheksfunktion, mit der Arrays aller Art sortiert werden können, sofern Sie eine typbezogene Vergleichsfunktion bereitstellen.Ja, die Verwendung des gleichen Schlüsselworts für zwei verschiedene Konzepte (kein Wert im Vergleich zum generischen Zeiger) ist verwirrend, aber es ist nicht so, als gäbe es keinen Präzedenzfall.
static
hat mehrere unterschiedliche Bedeutungen in C und C ++.quelle
Diese Aussage ist richtig. Es ist auch für C und C ++ korrekt.
Diese Aussage ist falsch.
void
Als Rückgabetyp in C oder C ++ bedeutet dies dasselbe wie in C # und Java. Sie verwechselnvoid
mitvoid*
. Sie sind ganz anders.Ja.
Ein Zeiger ist ein Wert, der dereferenziert werden kann . Durch die Referenzierung eines gültigen Zeigers wird ein Speicherort des Typs angegeben, auf den verwiesen wird . Ein Leerzeiger ist ein Zeiger, der keinen bestimmten Typ hat, auf den gezeigt wird. Es muss in einen spezifischeren Zeigertyp konvertiert werden , bevor der Wert dereferenziert wird , um einen Speicherort zu erzeugen .
Java hat keine leeren Zeiger; C # macht. Sie sind die gleichen wie in C und C ++ - ein Zeigerwert, dem kein bestimmter Typ zugeordnet ist, der in einen spezifischeren Typ konvertiert werden muss, bevor er dereferenziert wird, um einen Speicherort zu erzeugen.
Die Frage ist inkohärent, weil sie Unwahrheiten voraussetzt. Lassen Sie uns einige bessere Fragen stellen.
Um Programmierern vertraut zu sein, die aus Sprachen mit
void
Rückgabetyp zu Java oder C # kommen .Programmierern, die aus Sprachen mit Zeigertyp zu C # kommen, vertraut sein
void*
.quelle
Die erste Funktion gibt nichts zurück. Die zweite Funktion gibt einen ungültigen Zeiger zurück. Ich hätte diese zweite Funktion als deklariert
Könnte helfen, wenn Sie "nichtig" als "ohne Bedeutung" ansehen. Der Rückgabewert von
describe
ist "ohne Bedeutung". Die Rückgabe eines Wertes aus einer solchen Funktion ist unzulässig, da Sie dem Compiler mitgeteilt haben, dass der Rückgabewert ohne Bedeutung ist. Sie können den Rückgabewert dieser Funktion nicht erfassen, da er ohne Bedeutung ist. Sie können keine Variable vom Typ deklarieren,void
da sie ohne Bedeutung ist. Zum Beispielvoid nonsense;
ist Unsinn und ist in der Tat illegal. Beachten Sie auch, dass eine Reihe von Leerstellenvoid void_array[42];
ebenfalls Unsinn ist.Ein Zeiger auf void (
void*
) ist etwas anderes. Es ist ein Zeiger, kein Array. Lesen Sievoid*
als Bedeutung einen Zeiger, der auf etwas "ohne Bedeutung" zeigt. Der Zeiger ist "ohne Bedeutung", was bedeutet, dass es keinen Sinn macht, einen solchen Zeiger zu dereferenzieren. Dies zu versuchen ist in der Tat illegal. Code, der einen ungültigen Zeiger dereferenziert, wird nicht kompiliert.Also, wenn Sie einen leeren Zeiger nicht dereferenzieren können und keine Reihe von Leerräumen erzeugen können, wie können Sie sie verwenden? Die Antwort ist, dass ein Zeiger auf einen beliebigen Typ in und aus umgewandelt werden kann
void*
. Wenn Sie einen Zeiger aufvoid*
und einen Zeiger auf den ursprünglichen Typ zurücksetzen, erhalten Sie einen Wert, der dem ursprünglichen Zeiger entspricht. Der C-Standard garantiert dieses Verhalten. Der Hauptgrund dafür, dass Sie in C so viele leere Zeiger sehen, ist, dass diese Funktion eine Möglichkeit bietet, das Ausblenden von Informationen und die objektbasierte Programmierung in einer weitgehend nicht objektorientierten Sprache zu implementieren.Schließlich sehe der Grund , warum Sie oft
move
als deklariertvoid *move(...)
statt ,void* move(...)
weil neben dem Namen ein Sternchen setzen , anstatt die Art eine sehr gängige Praxis in C ist Der Grund dafür ist , dass die folgende Erklärung Sie in großen Schwierigkeiten bekommen:int* iptr, jptr;
Das würden Sie machen Ich glaube, Sie deklarieren zwei Zeiger auf einenint
. Du bist nicht. Diese Erklärung machtjptr
eineint
eher als einen Zeiger auf einint
. Die richtige Deklaration lautet:int *iptr, *jptr;
Sie können so tun, als ob der Stern zum Typ gehört, wenn Sie nur eine Variable pro Deklarationsanweisung deklarieren. Aber denken Sie daran, dass Sie so tun, als ob.quelle
void* null_ptr = 0;
).Einfach ausgedrückt gibt es keinen Unterschied . Lassen Sie mich einige Beispiele nennen.
Angenommen, ich habe eine Klasse namens Auto.
Ich glaube, hier beruht die Verwirrung darauf, wie Sie
void
vs definieren würdenvoid*
. Ein Rückgabetypvoid
bedeutet "Ich gebe nichts zurück", während ein Rückgabetypvoid*
"Ich gebe einen Zeiger vom Typ nichts zurück" bedeutet.Dies liegt daran, dass ein Zeiger ein Sonderfall ist. Ein Zeigerobjekt zeigt immer auf eine Stelle im Speicher, unabhängig davon, ob dort ein gültiges Objekt vorhanden ist oder nicht. Es
void* car
spielt keine Rolle, ob ich auf ein Byte, ein Wort, ein Auto oder ein Fahrrad verweise, da ichvoid*
auf etwas verweise, für das kein Typ definiert ist. Es liegt an uns, es später zu definieren.In Sprachen wie C # und Java wird der Speicher verwaltet und das Konzept der Zeiger in die Object-Klasse übernommen. Anstatt einen Verweis auf ein Objekt vom Typ "nichts" zu haben, wissen wir, dass unser Objekt zumindest vom Typ "Objekt" ist. Lass mich erklären warum.
Wenn Sie in herkömmlichem C / C ++ ein Objekt erstellen und seinen Zeiger löschen, ist es unmöglich, auf dieses Objekt zuzugreifen. Dies wird als Speicherverlust bezeichnet .
In C # / Java ist alles ein Objekt. Dadurch kann die Laufzeit jedes Objekt nachverfolgen. Es muss nicht unbedingt bekannt sein, dass mein Objekt ein Auto ist, es muss lediglich einige grundlegende Informationen enthalten, die bereits von der Object-Klasse behandelt werden.
Für uns Programmierer bedeutet dies, dass ein Großteil der Informationen, die wir in C / C ++ manuell bearbeiten müssten, von der Object-Klasse für uns verwaltet wird, sodass der Sonderfall, der der Zeiger ist, nicht mehr erforderlich ist.
quelle
Ich werde versuchen zu sagen, wie man sich diese Dinge in C vorstellen kann. Die offizielle Sprache in der C-Norm ist nicht so
void
einfach zu verstehen , und ich werde nicht versuchen, ganz damit übereinzustimmen.Der Typ
void
ist kein erstklassiger Bürger unter den Typen. Obwohl es sich um einen Objekttyp handelt, kann es sich nicht um den Typ eines Objekts (oder Werts), eines Felds oder eines Funktionsparameters handeln. Es kann sich jedoch um den Rückgabetyp einer Funktion und damit um den Typ eines Ausdrucks handeln (im Grunde genommen um Aufrufe solcher Funktionen, aber mit dem bedingten Operator gebildete Ausdrücke können auch den Typ void haben). Aber auch die minimale Verwendung vonvoid
als Werttyp, für die das Obige die Tür offen lässt, nämlich das Beenden einer Funktion, dievoid
mit einer Anweisung der Form zurückkehrt,return E;
in derE
ein Ausdruck vom Typ istvoid
, ist in C explizit verboten (in C ++ ist dies jedoch zulässig). aber aus Gründen, die für C) nicht zutreffen.Wenn Objekte vom Typ
void
zulässig wären, hätten sie 0 Bits (das ist die Informationsmenge im Wert eines Ausdrucks vomvoid
Typ). Es wäre kein großes Problem, solche Objekte zuzulassen, aber sie wären ziemlich nutzlos (Objekte der Größe 0 würden Schwierigkeiten beim Definieren von Zeigerarithmetik bereiten, was vielleicht am besten einfach verboten wäre). Die Menge der unterschiedlichen Werte eines solchen Objekts hätte ein (triviales) Element; sein Wert könnte angenommen werden, trivial, weil er keine Substanz enthält, aber nicht modifiziert werden kann (ohne einen anderen Wert). Die Norm wird daher verwirrt,void
wenn gesagt wird, dass sie eine leere Menge von Werten umfasst; Ein solcher Typ könnte nützlich sein, um Ausdrücke zu beschreiben, die die dies nicht könnenausgewertet werden (wie Sprünge oder nicht beendende Anrufe), obwohl die C-Sprache einen solchen Typ tatsächlich nicht verwendet.Als Werttyp nicht zulässig,
void
wurde auf mindestens zwei nicht direkt verwandte Arten verwendet, um "verboten" zu kennzeichnen: Die Angabe(void)
von Parametern in Funktionstypen bedeutet, dass Argumente für diese nicht zulässig sind (während "()" im Gegenteil alles bedeuten würde erlaubt ist, und ja, es ist sogar verboten , einenvoid
Ausdruck als Argument für Funktionen mit(void)
Parameterspezifikation bereitzustellen, und deklarierenvoid *p
bedeutet, dass der dereferenzierende Ausdruck*p
verboten ist (aber nicht, dass es sich um einen gültigen Ausdruck vom Typ handeltvoid
). Sie haben also Recht, dass diese Verwendungen vonvoid
nicht wirklich konsistent sindvoid
als gültige Art von Ausdrücken . Jedoch ein Objekt vom Typvoid*
ist eigentlich kein Zeiger auf Werte jeglicher Art, sondern ein Wert, der als Zeiger behandelt wird, obwohl er nicht dereferenziert werden kann und Zeigerarithmetik damit verboten ist. Das "als Zeiger behandelt" bedeutet dann wirklich nur, dass es von und zu jedem Zeigertyp ohne Informationsverlust umgewandelt werden kann. Es kann jedoch auch verwendet werden, um ganzzahlige Werte von und nach zu konvertieren, sodass es überhaupt nicht auf irgendetwas verweisen muss.quelle
void*
einem anderen Zeigertyp Informationen verlieren (oder sogar UB haben, an das ich mich nicht erinnern kann), aber wenn der Wert vonvoid*
aus dem Casting eines Zeigers desselben Typs stammt, auf den Sie jetzt casten, dann tut es nichtIn C # und Java wird jede Klasse von einer
Object
Klasse abgeleitet. Wenn wir also einen Verweis auf "etwas" übergeben möchten, können wir einen Verweis vom Typ verwendenObject
.Da wir in diesen Sprachen für diesen Zweck
void
keine Leerzeiger verwenden müssen , muss dies hier nicht "etwas oder nichts" bedeuten.quelle
In C ist es möglich, einen Zeiger auf beliebige Dinge zu haben [Zeiger verhalten sich als Referenztyp]. Ein Zeiger kann ein Objekt eines bekannten Typs oder ein Objekt eines beliebigen (unbekannten) Typs identifizieren. Die Schöpfer von C entschieden sich für die Verwendung der gleichen allgemeinen Syntax für "Zeiger auf einen beliebigen Typ" wie für "Zeiger auf einen bestimmten Typ". Da für die Syntax im letzteren Fall ein Token zur Angabe des Typs erforderlich ist, muss bei der ersteren Syntax etwas dort platziert werden, wo dieses Token hingehört, wenn der Typ bekannt wäre. C hat sich dafür entschieden, das Schlüsselwort "void" zu verwenden.
In Pascal ist es wie in C möglich, Zeiger auf beliebige Typen zu haben. populärere Dialekte von Pascal erlauben auch "Zeiger auf einen beliebigen Typ", aber anstatt die gleiche Syntax zu verwenden, die sie für "Zeiger auf einen bestimmten Typ" verwenden, bezeichnen sie den ersteren einfach als
Pointer
.In Java gibt es keine benutzerdefinierten Variablentypen. Alles ist entweder eine primitive oder eine Heap-Objektreferenz. Man kann Dinge vom Typ "Referenz auf ein Heap-Objekt eines bestimmten Typs" oder "Referenz auf ein Heap-Objekt eines beliebigen Typs" definieren. Ersteres verwendet einfach den Typnamen ohne spezielle Interpunktion, um anzuzeigen, dass es sich um eine Referenz handelt (da es nichts anderes sein kann). Letzterer verwendet den Typnamen
Object
.Die Verwendung von
void*
als Nomenklatur für einen Zeiger auf etwas von beliebigem Typ ist meines Wissens einzigartig für C und direkte Ableitungen wie C ++; Andere Sprachen, die ich kenne, behandeln das Konzept, indem sie einfach einen eindeutig benannten Typ verwenden.quelle
Sie können sich das Schlüsselwort
void
wie ein Leerzeichen vorstellenstruct
( In C99 sind leere Strukturen anscheinend nicht zulässig , in .NET und C ++ belegen sie mehr als 0 Speicher, und zuletzt erinnere ich mich, dass alles in Java eine Klasse ist):... was bedeutet, dass es ein Typ mit der Länge 0 ist. So funktioniert es, wenn Sie einen Funktionsaufruf ausführen, der zurückgibt
void
... anstatt 4 Bytes oder so auf dem Stapel für einen ganzzahligen Rückgabewert zuzuweisen, ändert es den Stapelzeiger überhaupt nicht (inkrementiert ihn um eine Länge von 0) ).Also, wenn Sie einige Variablen deklariert haben wie:
... und dann haben Sie vielleicht die Adresse dieser Variablen genommen
b
undc
könnten sich an derselben Stelle im Speicher befinden, weil sieb
die Länge Null hat. Avoid*
ist also ein Zeiger im Speicher auf den eingebauten Typ mit der Länge Null. Die Tatsache, dass Sie möglicherweise wissen, dass unmittelbar danach etwas für Sie von Nutzen sein kann, ist eine andere Angelegenheit und erfordert, dass Sie wirklich wissen, was Sie tun (und was gefährlich ist).quelle